openethereum/secret-store/src/key_server_cluster/admin_sessions/key_version_negotiation_ses...

1259 lines
46 KiB
Rust

// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
use ethereum_types::{Address, H256};
use ethkey::Secret;
use key_server_cluster::{
admin_sessions::ShareChangeSessionMeta,
cluster::Cluster,
cluster_sessions::{ClusterSession, SessionIdWithSubSession},
decryption_session::SessionImpl as DecryptionSession,
message::{
CommonKeyData, FailedKeyVersionContinueAction, KeyVersionNegotiationMessage, KeyVersions,
KeyVersionsError, Message, RequestKeyVersions,
},
signing_session_ecdsa::SessionImpl as EcdsaSigningSession,
signing_session_schnorr::SessionImpl as SchnorrSigningSession,
DocumentKeyShare, Error, NodeId, SessionId,
};
use parking_lot::{Condvar, Mutex};
use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
// TODO [Opt]: change sessions so that versions are sent by chunks.
/// Number of versions sent in single message.
const VERSIONS_PER_MESSAGE: usize = 32;
/// Key version negotiation transport.
pub trait SessionTransport {
/// Broadcast message to all nodes.
fn broadcast(&self, message: KeyVersionNegotiationMessage) -> Result<(), Error>;
/// Send message to given node.
fn send(&self, node: &NodeId, message: KeyVersionNegotiationMessage) -> Result<(), Error>;
}
/// Key version negotiation result computer.
pub trait SessionResultComputer: Send + Sync {
/// Compute result of session, if possible.
fn compute_result(
&self,
threshold: Option<usize>,
confirmations: &BTreeSet<NodeId>,
versions: &BTreeMap<H256, BTreeSet<NodeId>>,
) -> Option<Result<(H256, NodeId), Error>>;
}
/// Key discovery session API.
pub struct SessionImpl<T: SessionTransport> {
/// Session core.
core: SessionCore<T>,
/// Session data.
data: Mutex<SessionData>,
}
/// Action after key version is negotiated.
#[derive(Clone)]
pub enum ContinueAction {
/// 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.
EcdsaSign(Arc<EcdsaSigningSession>, H256),
}
/// Failed action after key version is negotiated.
#[derive(Clone, Debug, PartialEq)]
pub enum FailedContinueAction {
/// Decryption origin + requester.
Decrypt(Option<Address>, Address),
}
/// Immutable session data.
struct SessionCore<T: SessionTransport> {
/// Session meta.
pub meta: ShareChangeSessionMeta,
/// Sub-session id.
pub sub_session: Secret,
/// Key share.
pub key_share: Option<DocumentKeyShare>,
/// Session result computer.
pub result_computer: Arc<dyn SessionResultComputer>,
/// Session transport.
pub transport: T,
/// Session nonce.
pub nonce: u64,
/// SessionImpl completion condvar.
pub completed: Condvar,
}
/// Mutable session data.
struct SessionData {
/// Session state.
pub state: SessionState,
/// Initialization confirmations.
pub confirmations: Option<BTreeSet<NodeId>>,
/// Common key data that nodes have agreed upon.
pub key_share: Option<DocumentKeyShare>,
/// { Version => Nodes }
pub versions: Option<BTreeMap<H256, BTreeSet<NodeId>>>,
/// Session result.
pub result: Option<Result<Option<(H256, NodeId)>, Error>>,
/// Continue action.
pub continue_with: Option<ContinueAction>,
/// Failed continue action (reported in error message by master node).
pub failed_continue_with: Option<FailedContinueAction>,
}
/// SessionImpl creation parameters
pub struct SessionParams<T: SessionTransport> {
/// Session meta.
pub meta: ShareChangeSessionMeta,
/// Sub-session id.
pub sub_session: Secret,
/// Key share.
pub key_share: Option<DocumentKeyShare>,
/// Session result computer.
pub result_computer: Arc<dyn SessionResultComputer>,
/// Session transport to communicate to other cluster nodes.
pub transport: T,
/// Session nonce.
pub nonce: u64,
}
/// Signing session state.
#[derive(Debug, PartialEq)]
enum SessionState {
/// Waiting for initialization.
WaitingForInitialization,
/// Waiting for responses.
WaitingForResponses,
/// Session is completed.
Finished,
}
/// Isolated session transport.
pub struct IsolatedSessionTransport {
/// Cluster.
pub cluster: Arc<dyn Cluster>,
/// Key id.
pub key_id: SessionId,
/// Sub session id.
pub sub_session: Secret,
/// Session-level nonce.
pub nonce: u64,
}
/// Fastest session result computer. Computes first possible version that can be recovered on this node.
/// If there's no such version, selects version with the most support.
pub struct FastestResultComputer {
/// This node id.
self_node_id: NodeId,
/// Threshold (if known).
threshold: Option<usize>,
/// Count of all configured key server nodes.
configured_nodes_count: usize,
/// Count of all connected key server nodes.
connected_nodes_count: usize,
}
/// Selects version with most support, waiting for responses from all nodes.
pub struct LargestSupportResultComputer;
impl<T> SessionImpl<T>
where
T: SessionTransport,
{
/// Create new session.
pub fn new(params: SessionParams<T>) -> Self {
SessionImpl {
core: SessionCore {
meta: params.meta,
sub_session: params.sub_session,
key_share: params.key_share.clone(),
result_computer: params.result_computer,
transport: params.transport,
nonce: params.nonce,
completed: Condvar::new(),
},
data: Mutex::new(SessionData {
state: SessionState::WaitingForInitialization,
confirmations: None,
key_share: params.key_share.map(|key_share| DocumentKeyShare {
threshold: key_share.threshold,
author: key_share.author,
public: key_share.public,
..Default::default()
}),
versions: None,
result: None,
continue_with: None,
failed_continue_with: None,
}),
}
}
/// Return session meta.
pub fn meta(&self) -> &ShareChangeSessionMeta {
&self.core.meta
}
/// Return result computer reference.
pub fn version_holders(&self, version: &H256) -> Result<BTreeSet<NodeId>, Error> {
Ok(self
.data
.lock()
.versions
.as_ref()
.ok_or(Error::InvalidStateForRequest)?
.get(version)
.ok_or(Error::ServerKeyIsNotFound)?
.clone())
}
/// Set continue action.
pub fn set_continue_action(&self, action: ContinueAction) {
self.data.lock().continue_with = Some(action);
}
/// Take continue action.
pub fn take_continue_action(&self) -> Option<ContinueAction> {
self.data.lock().continue_with.take()
}
/// Take failed continue action.
pub fn take_failed_continue_action(&self) -> Option<FailedContinueAction> {
self.data.lock().failed_continue_with.take()
}
/// Wait for session completion.
pub fn wait(&self) -> Result<Option<(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")
}
/// Retrieve common key data (author, threshold, public), if available.
pub fn common_key_data(&self) -> Result<DocumentKeyShare, Error> {
self.data
.lock()
.key_share
.clone()
.ok_or(Error::InvalidStateForRequest)
}
/// Initialize session.
pub fn initialize(&self, connected_nodes: BTreeSet<NodeId>) -> Result<(), Error> {
// check state
let mut data = self.data.lock();
if data.state != SessionState::WaitingForInitialization {
return Err(Error::InvalidStateForRequest);
}
// update state
let mut confirmations = connected_nodes;
let mut versions: BTreeMap<H256, BTreeSet<NodeId>> = BTreeMap::new();
let received_own_confirmation = confirmations.remove(&self.core.meta.self_node_id);
if received_own_confirmation {
if let Some(key_share) = self.core.key_share.as_ref() {
for version in &key_share.versions {
versions
.entry(version.hash.clone())
.or_insert_with(Default::default)
.insert(self.core.meta.self_node_id.clone());
}
}
}
// update state
let no_confirmations_required = confirmations.is_empty();
data.state = SessionState::WaitingForResponses;
data.confirmations = Some(confirmations);
data.versions = Some(versions);
// try to complete session
Self::try_complete(&self.core, &mut *data);
if no_confirmations_required && data.state != SessionState::Finished {
return Err(Error::ServerKeyIsNotFound);
} else if data.state == SessionState::Finished {
return Ok(());
}
// send requests
let confirmations = data
.confirmations
.as_ref()
.expect("dilled couple of lines above; qed");
for connected_node in confirmations {
self.core.transport.send(
connected_node,
KeyVersionNegotiationMessage::RequestKeyVersions(RequestKeyVersions {
session: self.core.meta.id.clone().into(),
sub_session: self.core.sub_session.clone().into(),
session_nonce: self.core.nonce,
}),
)?;
}
Ok(())
}
/// Process single message.
pub fn process_message(
&self,
sender: &NodeId,
message: &KeyVersionNegotiationMessage,
) -> Result<(), Error> {
if self.core.nonce != message.session_nonce() {
return Err(Error::ReplayProtection);
}
match message {
&KeyVersionNegotiationMessage::RequestKeyVersions(ref message) => {
self.on_key_versions_request(sender, message)
}
&KeyVersionNegotiationMessage::KeyVersions(ref message) => {
self.on_key_versions(sender, message)
}
&KeyVersionNegotiationMessage::KeyVersionsError(ref message) => {
// remember failed continue action
if let Some(FailedKeyVersionContinueAction::Decrypt(
Some(ref origin),
ref requester,
)) = message.continue_with
{
self.data.lock().failed_continue_with = Some(FailedContinueAction::Decrypt(
Some(origin.clone().into()),
requester.clone().into(),
));
}
self.on_session_error(sender, message.error.clone());
Ok(())
}
}
}
/// Process key versions request.
pub fn on_key_versions_request(
&self,
sender: &NodeId,
_message: &RequestKeyVersions,
) -> Result<(), Error> {
debug_assert!(sender != &self.core.meta.self_node_id);
// check message
if *sender != self.core.meta.master_node_id {
return Err(Error::InvalidMessage);
}
// check state
let mut data = self.data.lock();
if data.state != SessionState::WaitingForInitialization {
return Err(Error::InvalidStateForRequest);
}
// send response
self.core.transport.send(
sender,
KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: self.core.meta.id.clone().into(),
sub_session: self.core.sub_session.clone().into(),
session_nonce: self.core.nonce,
key_common: self.core.key_share.as_ref().map(|key_share| CommonKeyData {
threshold: key_share.threshold,
author: key_share.author.into(),
public: key_share.public.into(),
}),
versions: self
.core
.key_share
.as_ref()
.map(|key_share| {
key_share
.versions
.iter()
.rev()
.filter(|v| v.id_numbers.contains_key(sender))
.chain(
key_share
.versions
.iter()
.rev()
.filter(|v| !v.id_numbers.contains_key(sender)),
)
.map(|v| v.hash.clone().into())
.take(VERSIONS_PER_MESSAGE)
.collect()
})
.unwrap_or_else(|| Default::default()),
}),
)?;
// update state
data.state = SessionState::Finished;
data.result = Some(Ok(None));
self.core.completed.notify_all();
Ok(())
}
/// Process key versions response.
pub fn on_key_versions(&self, sender: &NodeId, message: &KeyVersions) -> Result<(), Error> {
debug_assert!(sender != &self.core.meta.self_node_id);
// check state
let mut data = self.data.lock();
if data.state != SessionState::WaitingForResponses && data.state != SessionState::Finished {
return Err(Error::InvalidStateForRequest);
}
let reason = "this field is filled on master node when initializing; this is initialized master node; qed";
if !data.confirmations.as_mut().expect(reason).remove(sender) {
return Err(Error::InvalidMessage);
}
// remember versions that sender have
{
match message.key_common.as_ref() {
Some(key_common) if data.key_share.is_none() => {
data.key_share = Some(DocumentKeyShare {
threshold: key_common.threshold,
author: key_common.author.clone().into(),
public: key_common.public.clone().into(),
..Default::default()
});
}
Some(key_common) => {
let prev_key_share = data
.key_share
.as_ref()
.expect("data.key_share.is_none() is matched by previous branch; qed");
if prev_key_share.threshold != key_common.threshold
|| prev_key_share.author[..] != key_common.author[..]
|| prev_key_share.public[..] != key_common.public[..]
{
return Err(Error::InvalidMessage);
}
}
None if message.versions.is_empty() => (),
None => return Err(Error::InvalidMessage),
}
let versions = data.versions.as_mut().expect(reason);
for version in &message.versions {
versions
.entry(version.clone().into())
.or_insert_with(Default::default)
.insert(sender.clone());
}
}
// try to compute result
if data.state != SessionState::Finished {
Self::try_complete(&self.core, &mut *data);
}
Ok(())
}
/// Try to complete result && finish session.
fn try_complete(core: &SessionCore<T>, data: &mut SessionData) {
let reason = "this field is filled on master node when initializing; try_complete is only called on initialized master node; qed";
let confirmations = data.confirmations.as_ref().expect(reason);
let versions = data.versions.as_ref().expect(reason);
let threshold = data.key_share.as_ref().map(|key_share| key_share.threshold);
if let Some(result) =
core.result_computer
.compute_result(threshold, confirmations, versions)
{
// when the master node processing decryption service request, it starts with a key version negotiation session
// if the negotiation fails, only master node knows about it
// => if the error is fatal, only the master will know about it and report it to the contract && the request will never be rejected
// => let's broadcast fatal error so that every other node know about it, and, if it trusts to master node
// will report error to the contract
if let (Some(continue_with), Err(error)) =
(data.continue_with.as_ref(), result.as_ref())
{
let origin = match *continue_with {
ContinueAction::Decrypt(_, origin, _, _) => origin.clone(),
_ => None,
};
let requester = match *continue_with {
ContinueAction::Decrypt(ref session, _, _, _) => session
.requester()
.and_then(|r| r.address(&core.meta.id).ok()),
_ => None,
};
if origin.is_some() && requester.is_some() && !error.is_non_fatal() {
let requester = requester.expect("checked in above condition; qed");
data.failed_continue_with = Some(FailedContinueAction::Decrypt(
origin.clone(),
requester.clone(),
));
let send_result =
core.transport
.broadcast(KeyVersionNegotiationMessage::KeyVersionsError(
KeyVersionsError {
session: core.meta.id.clone().into(),
sub_session: core.sub_session.clone().into(),
session_nonce: core.nonce,
error: error.clone(),
continue_with: Some(FailedKeyVersionContinueAction::Decrypt(
origin.map(Into::into),
requester.into(),
)),
},
));
if let Err(send_error) = send_result {
warn!(target: "secretstore_net", "{}: failed to broadcast key version negotiation error {}: {}",
core.meta.self_node_id, error, send_error);
}
}
}
data.state = SessionState::Finished;
data.result = Some(result.map(Some));
core.completed.notify_all();
}
}
}
impl<T> ClusterSession for SessionImpl<T>
where
T: SessionTransport,
{
type Id = SessionIdWithSubSession;
fn type_name() -> &'static str {
"version negotiation"
}
fn id(&self) -> SessionIdWithSubSession {
SessionIdWithSubSession::new(self.core.meta.id.clone(), self.core.sub_session.clone())
}
fn is_finished(&self) -> bool {
self.data.lock().state == SessionState::Finished
}
fn on_session_timeout(&self) {
let mut data = self.data.lock();
if data.confirmations.is_some() {
data.confirmations
.as_mut()
.expect("checked a line above; qed")
.clear();
Self::try_complete(&self.core, &mut *data);
if data.state != SessionState::Finished {
warn!(target: "secretstore_net", "{}: key version negotiation session failed with timeout", self.core.meta.self_node_id);
data.result = Some(Err(Error::ConsensusTemporaryUnreachable));
self.core.completed.notify_all();
}
}
}
fn on_node_timeout(&self, node: &NodeId) {
self.on_session_error(node, Error::NodeDisconnected)
}
fn on_session_error(&self, node: &NodeId, error: Error) {
let mut data = self.data.lock();
if data.confirmations.is_some() {
let is_waiting_for_confirmation = data
.confirmations
.as_mut()
.expect("checked a line above; qed")
.remove(node);
if !is_waiting_for_confirmation {
return;
}
Self::try_complete(&self.core, &mut *data);
if data.state == SessionState::Finished {
return;
}
}
warn!(target: "secretstore_net", "{}: key version negotiation session failed because of {} from {}",
self.core.meta.self_node_id, error, node);
data.state = SessionState::Finished;
data.result = Some(Err(error));
self.core.completed.notify_all();
}
fn on_message(&self, sender: &NodeId, message: &Message) -> Result<(), Error> {
match *message {
Message::KeyVersionNegotiation(ref message) => self.process_message(sender, message),
_ => unreachable!("cluster checks message to be correct before passing; qed"),
}
}
}
impl SessionTransport for IsolatedSessionTransport {
fn broadcast(&self, message: KeyVersionNegotiationMessage) -> Result<(), Error> {
self.cluster
.broadcast(Message::KeyVersionNegotiation(message))
}
fn send(&self, node: &NodeId, message: KeyVersionNegotiationMessage) -> Result<(), Error> {
self.cluster
.send(node, Message::KeyVersionNegotiation(message))
}
}
impl FastestResultComputer {
pub fn new(
self_node_id: NodeId,
key_share: Option<&DocumentKeyShare>,
configured_nodes_count: usize,
connected_nodes_count: usize,
) -> Self {
let threshold = key_share.map(|ks| ks.threshold);
FastestResultComputer {
self_node_id,
threshold,
configured_nodes_count,
connected_nodes_count,
}
}
}
impl SessionResultComputer for FastestResultComputer {
fn compute_result(
&self,
threshold: Option<usize>,
confirmations: &BTreeSet<NodeId>,
versions: &BTreeMap<H256, BTreeSet<NodeId>>,
) -> Option<Result<(H256, NodeId), Error>> {
match self.threshold.or(threshold) {
// if there's no versions at all && we're not waiting for confirmations anymore
_ if confirmations.is_empty() && versions.is_empty() => {
Some(Err(Error::ServerKeyIsNotFound))
}
// if we have key share on this node
Some(threshold) => {
// select version this node have, with enough participants
let has_key_share = self.threshold.is_some();
let version = versions.iter().find(|&(_, ref n)| {
!has_key_share || n.contains(&self.self_node_id) && n.len() >= threshold + 1
});
// if there's no such version, wait for more confirmations
match version {
Some((version, nodes)) => Some(Ok((
version.clone(),
if has_key_share {
self.self_node_id.clone()
} else {
nodes.iter().cloned().nth(0).expect(
"version is only inserted when there's at least one owner; qed",
)
},
))),
None if !confirmations.is_empty() => None,
// otherwise - try to find any version
None => Some(
versions
.iter()
.find(|&(_, ref n)| n.len() >= threshold + 1)
.map(|(version, nodes)| {
Ok((version.clone(), nodes.iter().cloned().nth(0)
.expect("version is only inserted when there's at least one owner; qed")))
})
// if there's no version consensus among all connected nodes
// AND we're connected to ALL configured nodes
// OR there are less than required nodes for key restore
// => this means that we can't restore key with CURRENT configuration => respond with fatal error
// otherwise we could try later, after all nodes are connected
.unwrap_or_else(|| {
Err(
if self.configured_nodes_count == self.connected_nodes_count
|| self.configured_nodes_count < threshold + 1
{
Error::ConsensusUnreachable
} else {
Error::ConsensusTemporaryUnreachable
},
)
}),
),
}
}
// if we do not have share, then wait for all confirmations
None if !confirmations.is_empty() => None,
// ...and select version with largest support
None => Some(
versions
.iter()
.max_by_key(|&(_, ref n)| n.len())
.map(|(version, nodes)| {
Ok((
version.clone(),
nodes.iter().cloned().nth(0).expect(
"version is only inserted when there's at least one owner; qed",
),
))
})
.unwrap_or_else(|| {
Err(
if self.configured_nodes_count == self.connected_nodes_count {
Error::ConsensusUnreachable
} else {
Error::ConsensusTemporaryUnreachable
},
)
}),
),
}
}
}
impl SessionResultComputer for LargestSupportResultComputer {
fn compute_result(
&self,
_threshold: Option<usize>,
confirmations: &BTreeSet<NodeId>,
versions: &BTreeMap<H256, BTreeSet<NodeId>>,
) -> Option<Result<(H256, NodeId), Error>> {
if !confirmations.is_empty() {
return None;
}
if versions.is_empty() {
return Some(Err(Error::ServerKeyIsNotFound));
}
versions
.iter()
.max_by_key(|&(_, ref n)| n.len())
.map(|(version, nodes)| {
Ok((
version.clone(),
nodes
.iter()
.cloned()
.nth(0)
.expect("version is only inserted when there's at least one owner; qed"),
))
})
}
}
#[cfg(test)]
mod tests {
use super::{
ContinueAction, FailedContinueAction, FastestResultComputer, LargestSupportResultComputer,
SessionImpl, SessionParams, SessionResultComputer, SessionState, SessionTransport,
};
use ethereum_types::{H160, H512};
use ethkey::public_to_address;
use key_server_cluster::{
admin_sessions::ShareChangeSessionMeta,
cluster::{tests::DummyCluster, Cluster},
cluster_sessions::ClusterSession,
decryption_session::create_default_decryption_session,
math,
message::{
CommonKeyData, KeyVersionNegotiationMessage, KeyVersions, Message, RequestKeyVersions,
},
DocumentKeyShare, DocumentKeyShareVersion, DummyKeyStorage, Error, KeyStorage, NodeId,
SessionId,
};
use std::{
collections::{BTreeMap, BTreeSet, VecDeque},
sync::Arc,
};
struct DummyTransport {
cluster: Arc<DummyCluster>,
}
impl SessionTransport for DummyTransport {
fn broadcast(&self, message: KeyVersionNegotiationMessage) -> Result<(), Error> {
self.cluster
.broadcast(Message::KeyVersionNegotiation(message))
}
fn send(&self, node: &NodeId, message: KeyVersionNegotiationMessage) -> Result<(), Error> {
self.cluster
.send(node, Message::KeyVersionNegotiation(message))
}
}
struct Node {
pub cluster: Arc<DummyCluster>,
pub key_storage: Arc<DummyKeyStorage>,
pub session: SessionImpl<DummyTransport>,
}
struct MessageLoop {
pub session_id: SessionId,
pub nodes: BTreeMap<NodeId, Node>,
pub queue: VecDeque<(NodeId, NodeId, Message)>,
}
impl MessageLoop {
pub fn prepare_nodes(nodes_num: usize) -> BTreeMap<NodeId, Arc<DummyKeyStorage>> {
(0..nodes_num)
.map(|_| {
(
math::generate_random_point().unwrap(),
Arc::new(DummyKeyStorage::default()),
)
})
.collect()
}
pub fn empty(nodes_num: usize) -> Self {
Self::new(Self::prepare_nodes(nodes_num))
}
pub fn new(nodes: BTreeMap<NodeId, Arc<DummyKeyStorage>>) -> Self {
let master_node_id = nodes.keys().cloned().nth(0).unwrap();
let sub_sesion = math::generate_random_scalar().unwrap();
let all_nodes_ids: BTreeSet<_> = nodes.keys().cloned().collect();
MessageLoop {
session_id: Default::default(),
nodes: nodes
.iter()
.map(|(node_id, key_storage)| {
let cluster = Arc::new(DummyCluster::new(node_id.clone()));
cluster.add_nodes(all_nodes_ids.iter().cloned());
(
node_id.clone(),
Node {
cluster: cluster.clone(),
key_storage: key_storage.clone(),
session: SessionImpl::new(SessionParams {
meta: ShareChangeSessionMeta {
id: Default::default(),
self_node_id: node_id.clone(),
master_node_id: master_node_id.clone(),
configured_nodes_count: nodes.len(),
connected_nodes_count: nodes.len(),
},
sub_session: sub_sesion.clone(),
key_share: key_storage.get(&Default::default()).unwrap(),
result_computer: Arc::new(FastestResultComputer::new(
node_id.clone(),
key_storage.get(&Default::default()).unwrap().as_ref(),
nodes.len(),
nodes.len(),
)),
transport: DummyTransport { cluster: cluster },
nonce: 0,
}),
},
)
})
.collect(),
queue: VecDeque::new(),
}
}
pub fn node_id(&self, idx: usize) -> &NodeId {
self.nodes.keys().nth(idx).unwrap()
}
pub fn session(&self, idx: usize) -> &SessionImpl<DummyTransport> {
&self.nodes.values().nth(idx).unwrap().session
}
pub fn take_message(&mut self) -> Option<(NodeId, NodeId, Message)> {
self.nodes
.values()
.filter_map(|n| {
n.cluster
.take_message()
.map(|m| (n.session.meta().self_node_id.clone(), m.0, m.1))
})
.nth(0)
.or_else(|| self.queue.pop_front())
}
pub fn process_message(&mut self, msg: (NodeId, NodeId, Message)) -> Result<(), Error> {
match msg.2 {
Message::KeyVersionNegotiation(message) => {
self.nodes[&msg.1].session.process_message(&msg.0, &message)
}
_ => panic!("unexpected"),
}
}
pub fn run(&mut self) {
while let Some((from, to, message)) = self.take_message() {
self.process_message((from, to, message)).unwrap();
}
}
}
#[test]
fn negotiation_fails_if_initialized_twice() {
let ml = MessageLoop::empty(1);
assert_eq!(ml.session(0).initialize(BTreeSet::new()), Ok(()));
assert_eq!(
ml.session(0).initialize(BTreeSet::new()),
Err(Error::InvalidStateForRequest)
);
}
#[test]
fn negotiation_fails_if_message_contains_wrong_nonce() {
let ml = MessageLoop::empty(2);
assert_eq!(
ml.session(1).process_message(
ml.node_id(0),
&KeyVersionNegotiationMessage::RequestKeyVersions(RequestKeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 100,
})
),
Err(Error::ReplayProtection)
);
}
#[test]
fn negotiation_fails_if_versions_request_received_from_non_master() {
let ml = MessageLoop::empty(3);
assert_eq!(
ml.session(2).process_message(
ml.node_id(1),
&KeyVersionNegotiationMessage::RequestKeyVersions(RequestKeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
})
),
Err(Error::InvalidMessage)
);
}
#[test]
fn negotiation_fails_if_versions_request_received_twice() {
let ml = MessageLoop::empty(2);
assert_eq!(
ml.session(1).process_message(
ml.node_id(0),
&KeyVersionNegotiationMessage::RequestKeyVersions(RequestKeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
})
),
Ok(())
);
assert_eq!(
ml.session(1).process_message(
ml.node_id(0),
&KeyVersionNegotiationMessage::RequestKeyVersions(RequestKeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
})
),
Err(Error::InvalidStateForRequest)
);
}
#[test]
fn negotiation_fails_if_versions_received_before_initialization() {
let ml = MessageLoop::empty(2);
assert_eq!(
ml.session(1).process_message(
ml.node_id(0),
&KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
key_common: Some(CommonKeyData {
threshold: 10,
author: Default::default(),
public: Default::default(),
}),
versions: Vec::new(),
})
),
Err(Error::InvalidStateForRequest)
);
}
#[test]
fn negotiation_does_not_fails_if_versions_received_after_completion() {
let ml = MessageLoop::empty(3);
ml.session(0)
.initialize(ml.nodes.keys().cloned().collect())
.unwrap();
assert_eq!(
ml.session(0).data.lock().state,
SessionState::WaitingForResponses
);
let version_id = (*math::generate_random_scalar().unwrap()).clone();
assert_eq!(
ml.session(0).process_message(
ml.node_id(1),
&KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
key_common: Some(CommonKeyData {
threshold: 0,
author: Default::default(),
public: Default::default(),
}),
versions: vec![version_id.clone().into()]
})
),
Ok(())
);
assert_eq!(ml.session(0).data.lock().state, SessionState::Finished);
assert_eq!(
ml.session(0).process_message(
ml.node_id(2),
&KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
key_common: Some(CommonKeyData {
threshold: 0,
author: Default::default(),
public: Default::default(),
}),
versions: vec![version_id.clone().into()]
})
),
Ok(())
);
assert_eq!(ml.session(0).data.lock().state, SessionState::Finished);
}
#[test]
fn negotiation_fails_if_wrong_common_data_sent() {
fn run_test(key_common: CommonKeyData) {
let ml = MessageLoop::empty(3);
ml.session(0)
.initialize(ml.nodes.keys().cloned().collect())
.unwrap();
let version_id = (*math::generate_random_scalar().unwrap()).clone();
assert_eq!(
ml.session(0).process_message(
ml.node_id(1),
&KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
key_common: Some(CommonKeyData {
threshold: 1,
author: Default::default(),
public: Default::default(),
}),
versions: vec![version_id.clone().into()]
})
),
Ok(())
);
assert_eq!(
ml.session(0).process_message(
ml.node_id(2),
&KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
key_common: Some(key_common),
versions: vec![version_id.clone().into()]
})
),
Err(Error::InvalidMessage)
);
}
run_test(CommonKeyData {
threshold: 2,
author: Default::default(),
public: Default::default(),
});
run_test(CommonKeyData {
threshold: 1,
author: H160::from(1).into(),
public: Default::default(),
});
run_test(CommonKeyData {
threshold: 1,
author: H160::from(2).into(),
public: Default::default(),
});
}
#[test]
fn negotiation_fails_if_threshold_empty_when_versions_are_not_empty() {
let ml = MessageLoop::empty(2);
ml.session(0)
.initialize(ml.nodes.keys().cloned().collect())
.unwrap();
let version_id = (*math::generate_random_scalar().unwrap()).clone();
assert_eq!(
ml.session(0).process_message(
ml.node_id(1),
&KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
key_common: None,
versions: vec![version_id.clone().into()]
})
),
Err(Error::InvalidMessage)
);
}
#[test]
fn fast_negotiation_does_not_completes_instantly_when_enough_share_owners_are_connected() {
let nodes = MessageLoop::prepare_nodes(2);
let version_id = (*math::generate_random_scalar().unwrap()).clone();
nodes
.values()
.nth(0)
.unwrap()
.insert(
Default::default(),
DocumentKeyShare {
author: H160::from(2),
threshold: 1,
public: H512::from(3),
common_point: None,
encrypted_point: None,
versions: vec![DocumentKeyShareVersion {
hash: version_id,
id_numbers: vec![(
nodes.keys().cloned().nth(0).unwrap(),
math::generate_random_scalar().unwrap(),
)]
.into_iter()
.collect(),
secret_share: math::generate_random_scalar().unwrap(),
}],
},
)
.unwrap();
let ml = MessageLoop::new(nodes);
ml.session(0)
.initialize(ml.nodes.keys().cloned().collect())
.unwrap();
// we can't be sure that node has given key version because previous ShareAdd session could fail
assert!(ml.session(0).data.lock().state != SessionState::Finished);
// check that upon completion, commmon key data is known
assert_eq!(
ml.session(0).common_key_data(),
Ok(DocumentKeyShare {
author: H160::from(2),
threshold: 1,
public: H512::from(3),
..Default::default()
})
);
}
#[test]
fn fastest_computer_returns_missing_share_if_no_versions_returned() {
let computer = FastestResultComputer {
self_node_id: Default::default(),
threshold: None,
configured_nodes_count: 1,
connected_nodes_count: 1,
};
assert_eq!(
computer.compute_result(Some(10), &Default::default(), &Default::default()),
Some(Err(Error::ServerKeyIsNotFound))
);
}
#[test]
fn largest_computer_returns_missing_share_if_no_versions_returned() {
let computer = LargestSupportResultComputer;
assert_eq!(
computer.compute_result(Some(10), &Default::default(), &Default::default()),
Some(Err(Error::ServerKeyIsNotFound))
);
}
#[test]
fn fatal_error_is_not_broadcasted_if_started_without_origin() {
let mut ml = MessageLoop::empty(3);
ml.session(0).set_continue_action(ContinueAction::Decrypt(
create_default_decryption_session(),
None,
false,
false,
));
ml.session(0)
.initialize(ml.nodes.keys().cloned().collect())
.unwrap();
ml.run();
assert!(ml
.nodes
.values()
.all(|n| n.session.is_finished() && n.session.take_failed_continue_action().is_none()));
}
#[test]
fn fatal_error_is_broadcasted_if_started_with_origin() {
let mut ml = MessageLoop::empty(3);
ml.session(0).set_continue_action(ContinueAction::Decrypt(
create_default_decryption_session(),
Some(1.into()),
true,
true,
));
ml.session(0)
.initialize(ml.nodes.keys().cloned().collect())
.unwrap();
ml.run();
// on all nodes session is completed
assert!(ml.nodes.values().all(|n| n.session.is_finished()));
// slave nodes have non-empty failed continue action
assert!(ml
.nodes
.values()
.skip(1)
.all(|n| n.session.take_failed_continue_action()
== Some(FailedContinueAction::Decrypt(
Some(1.into()),
public_to_address(&2.into())
))));
}
}