732 lines
25 KiB
Rust
732 lines
25 KiB
Rust
// 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 std::collections::{BTreeSet, BTreeMap};
|
|
use bigint::hash::H256;
|
|
use ethkey::Secret;
|
|
use parking_lot::{Mutex, Condvar};
|
|
use key_server_cluster::{Error, SessionId, NodeId, DocumentKeyShare};
|
|
use key_server_cluster::cluster::Cluster;
|
|
use key_server_cluster::cluster_sessions::{SessionIdWithSubSession, ClusterSession};
|
|
use key_server_cluster::decryption_session::SessionImpl as DecryptionSession;
|
|
use key_server_cluster::signing_session::SessionImpl as SigningSession;
|
|
use key_server_cluster::message::{Message, KeyVersionNegotiationMessage, RequestKeyVersions, KeyVersions};
|
|
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
|
|
|
|
// 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 {
|
|
/// 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 + is_shadow_decryption.
|
|
Decrypt(Arc<DecryptionSession>, bool),
|
|
/// Signing session + message hash.
|
|
Sign(Arc<SigningSession>, H256),
|
|
}
|
|
|
|
/// 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<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>>,
|
|
/// Key threshold.
|
|
pub threshold: Option<usize>,
|
|
/// { Version => Nodes }
|
|
pub versions: Option<BTreeMap<H256, BTreeSet<NodeId>>>,
|
|
/// Session result.
|
|
pub result: Option<Result<(H256, NodeId), Error>>,
|
|
/// Continue action.
|
|
pub continue_with: Option<ContinueAction>,
|
|
}
|
|
|
|
/// 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<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<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>,
|
|
}
|
|
|
|
/// 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,
|
|
result_computer: params.result_computer,
|
|
transport: params.transport,
|
|
nonce: params.nonce,
|
|
completed: Condvar::new(),
|
|
},
|
|
data: Mutex::new(SessionData {
|
|
state: SessionState::WaitingForInitialization,
|
|
confirmations: None,
|
|
threshold: None,
|
|
versions: None,
|
|
result: None,
|
|
continue_with: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Return session meta.
|
|
pub fn meta(&self) -> &ShareChangeSessionMeta {
|
|
&self.core.meta
|
|
}
|
|
|
|
/// Return key threshold.
|
|
pub fn key_threshold(&self) -> Result<usize, Error> {
|
|
Ok(self.data.lock().threshold.clone().ok_or(Error::InvalidStateForRequest)?)
|
|
}
|
|
|
|
/// 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::KeyStorage("key version not found".into()))?
|
|
.clone())
|
|
}
|
|
|
|
/// Set continue action.
|
|
pub fn set_continue_action(&self, action: ContinueAction) {
|
|
self.data.lock().continue_with = Some(action);
|
|
}
|
|
|
|
/// Get continue action.
|
|
pub fn continue_action(&self) -> Option<ContinueAction> {
|
|
self.data.lock().continue_with.clone()
|
|
}
|
|
|
|
/// 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())
|
|
}
|
|
|
|
/// 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::MissingKeyShare);
|
|
} 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) => {
|
|
self.on_session_error(sender, Error::Io(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,
|
|
threshold: self.core.key_share.as_ref().map(|key_share| key_share.threshold),
|
|
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;
|
|
|
|
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.threshold.clone() {
|
|
Some(threshold) if data.threshold.is_none() => {
|
|
data.threshold = Some(threshold);
|
|
},
|
|
Some(threshold) if data.threshold.as_ref() == Some(&threshold) => (),
|
|
Some(_) => 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);
|
|
if let Some(result) = core.result_computer.compute_result(data.threshold.clone(), confirmations, versions) {
|
|
data.state = SessionState::Finished;
|
|
data.result = Some(result);
|
|
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!("{}: key version negotiation session failed with timeout", self.core.meta.self_node_id);
|
|
|
|
data.result = Some(Err(Error::ConsensusUnreachable));
|
|
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 {
|
|
Self::try_complete(&self.core, &mut *data);
|
|
if data.state != SessionState::Finished {
|
|
warn!("{}: key version negotiation session failed because {} connection has timeouted", self.core.meta.self_node_id, 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 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>) -> Self {
|
|
let threshold = key_share.map(|ks| ks.threshold);
|
|
FastestResultComputer {
|
|
self_node_id: self_node_id,
|
|
threshold: threshold,
|
|
}
|
|
}}
|
|
|
|
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::MissingKeyShare)),
|
|
// 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"))))
|
|
.unwrap_or(Err(Error::ConsensusUnreachable))),
|
|
}
|
|
},
|
|
// 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(Err(Error::ConsensusUnreachable))),
|
|
}
|
|
}
|
|
}
|
|
|
|
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::MissingKeyShare));
|
|
}
|
|
|
|
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 std::sync::Arc;
|
|
use std::collections::{VecDeque, BTreeMap, BTreeSet};
|
|
use key_server_cluster::{NodeId, SessionId, Error, KeyStorage, DummyKeyStorage, DocumentKeyShare, DocumentKeyShareVersion};
|
|
use key_server_cluster::math;
|
|
use key_server_cluster::cluster::Cluster;
|
|
use key_server_cluster::cluster::tests::DummyCluster;
|
|
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
|
|
use key_server_cluster::message::{Message, KeyVersionNegotiationMessage, RequestKeyVersions, KeyVersions};
|
|
use super::{SessionImpl, SessionTransport, SessionParams, FastestResultComputer, LargestSupportResultComputer,
|
|
SessionResultComputer, SessionState};
|
|
|
|
struct DummyTransport {
|
|
cluster: Arc<DummyCluster>,
|
|
}
|
|
|
|
impl SessionTransport for DummyTransport {
|
|
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(),
|
|
},
|
|
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(),
|
|
)),
|
|
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
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
threshold: Some(10),
|
|
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,
|
|
threshold: Some(0),
|
|
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,
|
|
threshold: Some(0),
|
|
versions: vec![version_id.clone().into()]
|
|
})), Ok(()));
|
|
assert_eq!(ml.session(0).data.lock().state, SessionState::Finished);
|
|
}
|
|
|
|
#[test]
|
|
fn negotiation_fails_if_wrong_threshold_sent() {
|
|
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,
|
|
threshold: Some(1),
|
|
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,
|
|
threshold: Some(2),
|
|
versions: vec![version_id.clone().into()]
|
|
})), Err(Error::InvalidMessage));
|
|
}
|
|
|
|
#[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,
|
|
threshold: 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: Default::default(),
|
|
threshold: 1,
|
|
public: Default::default(),
|
|
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);
|
|
}
|
|
|
|
#[test]
|
|
fn fastest_computer_returns_missing_share_if_no_versions_returned() {
|
|
let computer = FastestResultComputer {
|
|
self_node_id: Default::default(),
|
|
threshold: None,
|
|
};
|
|
assert_eq!(computer.compute_result(Some(10), &Default::default(), &Default::default()), Some(Err(Error::MissingKeyShare)));
|
|
}
|
|
|
|
#[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::MissingKeyShare)));
|
|
}
|
|
}
|