1293 lines
52 KiB
Rust
1293 lines
52 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::{Public, Secret, Signature};
|
|
use parking_lot::{Mutex, Condvar};
|
|
use key_server_cluster::{Error, SessionId, NodeId, DocumentKeyShare, DocumentKeyShareVersion, KeyStorage};
|
|
use key_server_cluster::cluster::Cluster;
|
|
use key_server_cluster::cluster_sessions::ClusterSession;
|
|
use key_server_cluster::math;
|
|
use key_server_cluster::message::{Message, ShareAddMessage, ShareAddConsensusMessage, ConsensusMessageOfShareAdd,
|
|
InitializeConsensusSessionOfShareAdd, KeyShareCommon, NewKeysDissemination, ShareAddError,
|
|
ConfirmConsensusInitialization};
|
|
use key_server_cluster::jobs::job_session::JobTransport;
|
|
use key_server_cluster::jobs::dummy_job::{DummyJob, DummyJobTransport};
|
|
use key_server_cluster::jobs::servers_set_change_access_job::{ServersSetChangeAccessJob, ServersSetChangeAccessRequest};
|
|
use key_server_cluster::jobs::consensus_session::{ConsensusSessionParams, ConsensusSessionState, ConsensusSession};
|
|
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
|
|
|
|
/// Share addition session transport.
|
|
pub trait SessionTransport: Clone + JobTransport<PartialJobRequest=ServersSetChangeAccessRequest, PartialJobResponse=bool> {
|
|
/// Get all connected nodes. Since ShareAdd session requires all cluster nodes to be connected, this set equals to all known cluster nodes set.
|
|
fn nodes(&self) -> BTreeSet<NodeId>;
|
|
/// Send message to given node.
|
|
fn send(&self, node: &NodeId, message: ShareAddMessage) -> Result<(), Error>;
|
|
/// Set data for master node (sent to slave nodes in consensus session initialization message).
|
|
fn set_master_data(&mut self, consensus_group: BTreeSet<NodeId>, id_numbers: BTreeMap<NodeId, Option<Secret>>);
|
|
}
|
|
|
|
/// Share addition session.
|
|
/// Based on "Efficient Multi-Party Digital Signature using Adaptive Secret Sharing for Low-Power Devices in Wireless Networks" paper:
|
|
/// http://www.wu.ece.ufl.edu/mypapers/msig.pdf
|
|
/// Brief overview:
|
|
/// 1) initialization: master node (which has received request for shares addition the message) asks all other nodes to support addition
|
|
/// 2) key refreshing distribution (KRD): node generates new random polynom && sends required data to all other nodes
|
|
/// 3) key refreshing verification (KRV): node verifies received data
|
|
/// 4) node updates its own key share using generated (&& received) data
|
|
pub struct SessionImpl<T: SessionTransport> {
|
|
/// Session core.
|
|
core: SessionCore<T>,
|
|
/// Session data.
|
|
data: Mutex<SessionData<T>>,
|
|
}
|
|
|
|
/// Immutable session data.
|
|
struct SessionCore<T: SessionTransport> {
|
|
/// Session metadata.
|
|
pub meta: ShareChangeSessionMeta,
|
|
/// Session-level nonce.
|
|
pub nonce: u64,
|
|
/// Original key share (for old nodes only).
|
|
pub key_share: Option<DocumentKeyShare>,
|
|
/// Session transport to communicate to other cluster nodes.
|
|
pub transport: T,
|
|
/// Key storage.
|
|
pub key_storage: Arc<KeyStorage>,
|
|
/// Administrator public key.
|
|
pub admin_public: Option<Public>,
|
|
/// SessionImpl completion condvar.
|
|
pub completed: Condvar,
|
|
}
|
|
|
|
/// Share add consensus session type.
|
|
type ShareAddChangeConsensusSession<T> = ConsensusSession<ServersSetChangeAccessJob, T, DummyJob, DummyJobTransport>;
|
|
|
|
/// Mutable session data.
|
|
struct SessionData<T: SessionTransport> {
|
|
/// Session state.
|
|
pub state: SessionState,
|
|
/// Key version to use for decryption.
|
|
pub version: Option<H256>,
|
|
/// Consensus session.
|
|
pub consensus_session: Option<ShareAddChangeConsensusSession<T>>,
|
|
/// NewKeyShare (for nodes being added).
|
|
pub new_key_share: Option<NewKeyShare>,
|
|
/// Nodes id numbers.
|
|
pub id_numbers: Option<BTreeMap<NodeId, Option<Secret>>>,
|
|
/// Secret subshares received from nodes.
|
|
pub secret_subshares: Option<BTreeMap<NodeId, Option<Secret>>>,
|
|
/// Share add change result.
|
|
pub result: Option<Result<(), Error>>,
|
|
}
|
|
|
|
/// New key share.
|
|
struct NewKeyShare {
|
|
/// NewKeyShare: threshold.
|
|
pub threshold: usize,
|
|
/// NewKeyShare: author.
|
|
pub author: Public,
|
|
/// NewKeyShare: joint public.
|
|
pub joint_public: Public,
|
|
/// NewKeyShare: Common (shared) encryption point.
|
|
pub common_point: Option<Public>,
|
|
/// NewKeyShare: Encrypted point.
|
|
pub encrypted_point: Option<Public>,
|
|
}
|
|
|
|
/// Session state.
|
|
#[derive(Debug, PartialEq)]
|
|
enum SessionState {
|
|
/// State when consensus is establishing.
|
|
ConsensusEstablishing,
|
|
/// Waiting for keys dissemination.
|
|
WaitingForKeysDissemination,
|
|
/// Session is completed.
|
|
Finished,
|
|
}
|
|
|
|
/// SessionImpl creation parameters
|
|
pub struct SessionParams<T: SessionTransport> {
|
|
/// Session metadata.
|
|
pub meta: ShareChangeSessionMeta,
|
|
/// Session transport.
|
|
pub transport: T,
|
|
/// Key storage.
|
|
pub key_storage: Arc<KeyStorage>,
|
|
/// Administrator public key.
|
|
pub admin_public: Option<Public>,
|
|
/// Session nonce.
|
|
pub nonce: u64,
|
|
}
|
|
|
|
/// Isolated ShareAdd session transport.
|
|
#[derive(Clone)]
|
|
pub struct IsolatedSessionTransport {
|
|
/// Key id.
|
|
session: SessionId,
|
|
/// Key version.
|
|
version: Option<H256>,
|
|
/// Session-level nonce.
|
|
nonce: u64,
|
|
/// Consensus group.
|
|
consensus_group: Option<BTreeSet<NodeId>>,
|
|
/// Id numbers of all new nodes.
|
|
id_numbers: Option<BTreeMap<NodeId, Option<Secret>>>,
|
|
/// Cluster.
|
|
cluster: Arc<Cluster>,
|
|
}
|
|
|
|
impl<T> SessionImpl<T> where T: SessionTransport {
|
|
/// Create new share addition session.
|
|
pub fn new(params: SessionParams<T>) -> Result<Self, Error> {
|
|
let key_share = params.key_storage
|
|
.get(¶ms.meta.id)
|
|
.map_err(|e| Error::KeyStorage(e.into()))?;
|
|
|
|
Ok(SessionImpl {
|
|
core: SessionCore {
|
|
meta: params.meta,
|
|
nonce: params.nonce,
|
|
key_share: key_share,
|
|
transport: params.transport,
|
|
key_storage: params.key_storage,
|
|
admin_public: params.admin_public,
|
|
completed: Condvar::new(),
|
|
},
|
|
data: Mutex::new(SessionData {
|
|
state: SessionState::ConsensusEstablishing,
|
|
version: None,
|
|
consensus_session: None,
|
|
new_key_share: None,
|
|
id_numbers: None,
|
|
secret_subshares: None,
|
|
result: None,
|
|
}),
|
|
})
|
|
}
|
|
|
|
/// Set pre-established consensus data.
|
|
pub fn set_consensus_output(&self, version: &H256, consensus_group: BTreeSet<NodeId>, mut new_nodes_map: BTreeMap<NodeId, Option<Secret>>) -> Result<(), Error> {
|
|
let mut data = self.data.lock();
|
|
|
|
// check state
|
|
if data.state != SessionState::ConsensusEstablishing || data.consensus_session.is_some() || data.id_numbers.is_some() || data.secret_subshares.is_some() {
|
|
return Err(Error::InvalidStateForRequest);
|
|
}
|
|
|
|
// key share version is required on ShareAdd master node
|
|
if let Some(key_share) = self.core.key_share.as_ref() {
|
|
if let Ok(key_version) = key_share.version(version) {
|
|
for (node, id_number) in &key_version.id_numbers {
|
|
{
|
|
let external_id_number = new_nodes_map.get(node);
|
|
match external_id_number {
|
|
Some(&Some(ref external_id_number)) => {
|
|
if external_id_number == id_number {
|
|
continue;
|
|
}
|
|
return Err(Error::ConsensusUnreachable);
|
|
},
|
|
Some(&None) => (),
|
|
None => return Err(Error::ConsensusUnreachable),
|
|
}
|
|
}
|
|
|
|
new_nodes_map.insert(node.clone(), Some(id_number.clone()));
|
|
}
|
|
|
|
// check that all id_numbers are filled
|
|
if new_nodes_map.values().any(Option::is_none) {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check passed consensus data
|
|
Self::check_nodes_map(&self.core, version, &consensus_group, &new_nodes_map)?;
|
|
|
|
// update data
|
|
data.version = Some(version.clone());
|
|
data.id_numbers = Some(new_nodes_map);
|
|
data.secret_subshares = Some(consensus_group.into_iter()
|
|
.map(|n| (n, None))
|
|
.collect());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Initialize share add session on master node.
|
|
pub fn initialize(&self, version: Option<H256>, new_nodes_set: Option<BTreeSet<NodeId>>, old_set_signature: Option<Signature>, new_set_signature: Option<Signature>) -> Result<(), Error> {
|
|
debug_assert_eq!(self.core.meta.self_node_id, self.core.meta.master_node_id);
|
|
|
|
let mut data = self.data.lock();
|
|
|
|
// check state
|
|
if data.state != SessionState::ConsensusEstablishing || data.consensus_session.is_some() {
|
|
return Err(Error::InvalidStateForRequest);
|
|
}
|
|
|
|
// if consensus is pre-established => start sending ShareAdd-specific messages
|
|
let is_consensus_pre_established = data.id_numbers.is_some();
|
|
if is_consensus_pre_established {
|
|
return Self::on_consensus_established(&self.core, &mut *data);
|
|
}
|
|
|
|
// else => prepare to start consensus session
|
|
// require all initialization params for consensus session
|
|
let version = version.ok_or(Error::InvalidMessage)?;
|
|
let old_set_signature = old_set_signature.ok_or(Error::InvalidMessage)?;
|
|
let new_set_signature = new_set_signature.ok_or(Error::InvalidMessage)?;
|
|
let new_nodes_set = new_nodes_set.ok_or(Error::InvalidMessage)?;
|
|
let admin_public = self.core.admin_public.as_ref().cloned().ok_or(Error::ConsensusUnreachable)?;
|
|
|
|
// key share version is required on ShareAdd master node
|
|
let key_share = self.core.key_share.as_ref().ok_or_else(|| Error::KeyStorage("key share is not found on master node".into()))?;
|
|
let key_version = key_share.version(&version).map_err(|e| Error::KeyStorage(e.into()))?;
|
|
|
|
// old nodes set is all non-isolated owners of version holders
|
|
let non_isolated_nodes = self.core.transport.nodes();
|
|
let old_nodes_set: BTreeSet<_> = key_version.id_numbers.keys()
|
|
.filter(|n| non_isolated_nodes.contains(n))
|
|
.cloned()
|
|
.collect();
|
|
|
|
// new nodes map contains previous id_numbers for old nodes && random number for new nodes
|
|
let mut new_nodes_map = BTreeMap::new();
|
|
for new_node in new_nodes_set.into_iter().filter(|n| non_isolated_nodes.contains(n)) {
|
|
new_nodes_map.insert(new_node, match key_version.id_numbers.get(&new_node) {
|
|
Some(old_id_number) => Some(old_id_number.clone()),
|
|
None => Some(math::generate_random_scalar()?),
|
|
});
|
|
}
|
|
|
|
// let's select consensus group
|
|
let consensus_group: BTreeSet<_> = ::std::iter::once(self.core.meta.self_node_id.clone())
|
|
.chain(old_nodes_set.iter()
|
|
.filter(|n| **n != self.core.meta.self_node_id && non_isolated_nodes.contains(*n))
|
|
.take(key_share.threshold)
|
|
.cloned())
|
|
.collect();
|
|
|
|
// now check nodes map
|
|
Self::check_nodes_map(&self.core, &version, &consensus_group, &new_nodes_map)?;
|
|
|
|
// prepare consensus session transport
|
|
let mut consensus_transport = self.core.transport.clone();
|
|
consensus_transport.set_master_data(consensus_group.clone(), new_nodes_map.clone());
|
|
|
|
// create && initialize consensus session
|
|
let mut consensus_session = ConsensusSession::new(ConsensusSessionParams {
|
|
meta: self.core.meta.clone().into_consensus_meta(new_nodes_map.len())?,
|
|
consensus_executor: ServersSetChangeAccessJob::new_on_master(admin_public,
|
|
old_nodes_set.clone(),
|
|
new_nodes_map.keys().cloned().collect(),
|
|
old_set_signature,
|
|
new_set_signature),
|
|
consensus_transport: consensus_transport,
|
|
})?;
|
|
consensus_session.initialize(new_nodes_map.keys().cloned().collect())?;
|
|
|
|
// update data
|
|
data.version = Some(version);
|
|
data.consensus_session = Some(consensus_session);
|
|
data.id_numbers = Some(new_nodes_map);
|
|
data.secret_subshares = Some(consensus_group.into_iter().map(|n| (n, None)).collect());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Process single message.
|
|
pub fn process_message(&self, sender: &NodeId, message: &ShareAddMessage) -> Result<(), Error> {
|
|
if self.core.nonce != message.session_nonce() {
|
|
return Err(Error::ReplayProtection);
|
|
}
|
|
|
|
match message {
|
|
&ShareAddMessage::ShareAddConsensusMessage(ref message) =>
|
|
self.on_consensus_message(sender, message),
|
|
&ShareAddMessage::KeyShareCommon(ref message) =>
|
|
self.on_common_key_share_data(sender, message),
|
|
&ShareAddMessage::NewKeysDissemination(ref message) =>
|
|
self.on_new_keys_dissemination(sender, message),
|
|
&ShareAddMessage::ShareAddError(ref message) => {
|
|
self.on_session_error(sender, Error::Io(message.error.clone().into()));
|
|
Ok(())
|
|
},
|
|
}
|
|
}
|
|
|
|
/// When consensus-related message is received.
|
|
pub fn on_consensus_message(&self, sender: &NodeId, message: &ShareAddConsensusMessage) -> Result<(), Error> {
|
|
debug_assert!(self.core.meta.id == *message.session);
|
|
debug_assert!(sender != &self.core.meta.self_node_id);
|
|
|
|
// start slave consensus session if needed
|
|
let mut data = self.data.lock();
|
|
match &message.message {
|
|
&ConsensusMessageOfShareAdd::InitializeConsensusSession(ref message)
|
|
if data.consensus_session.is_none() && sender == &self.core.meta.master_node_id => {
|
|
let admin_public = self.core.admin_public.as_ref().cloned().ok_or(Error::ConsensusUnreachable)?;
|
|
data.consensus_session = Some(ConsensusSession::new(ConsensusSessionParams {
|
|
meta: self.core.meta.clone().into_consensus_meta(message.new_nodes_map.len())?,
|
|
consensus_executor: ServersSetChangeAccessJob::new_on_slave(admin_public),
|
|
consensus_transport: self.core.transport.clone(),
|
|
})?);
|
|
},
|
|
_ => (),
|
|
};
|
|
|
|
// process consensus message
|
|
let (is_establishing_consensus, is_consensus_established, version, new_nodes_map, consensus_group) = {
|
|
let consensus_session = data.consensus_session.as_mut().ok_or(Error::InvalidMessage)?;
|
|
let is_establishing_consensus = consensus_session.state() == ConsensusSessionState::EstablishingConsensus;
|
|
|
|
let (version, new_nodes_map, consensus_group) = match &message.message {
|
|
&ConsensusMessageOfShareAdd::InitializeConsensusSession(ref message) => {
|
|
consensus_session.on_consensus_partial_request(sender, ServersSetChangeAccessRequest::from(message))?;
|
|
|
|
let version = message.version.clone().into();
|
|
let consensus_group = message.consensus_group.iter().cloned().map(Into::into).collect();
|
|
let new_nodes_map: BTreeMap<_, _> = message.new_nodes_map.iter()
|
|
.map(|(n, nn)| (n.clone().into(), Some(nn.clone().into())))
|
|
.collect();
|
|
|
|
// check that all id_numbers are filled
|
|
if new_nodes_map.values().any(Option::is_none) {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
|
|
// check old set of nodes
|
|
Self::check_nodes_map(&self.core, &version, &consensus_group, &new_nodes_map)?;
|
|
|
|
(Some(version), Some(new_nodes_map), Some(consensus_group))
|
|
},
|
|
&ConsensusMessageOfShareAdd::ConfirmConsensusInitialization(ref message) => {
|
|
consensus_session.on_consensus_partial_response(sender, message.is_confirmed)?;
|
|
(None, None, None)
|
|
},
|
|
};
|
|
|
|
(
|
|
is_establishing_consensus,
|
|
consensus_session.state() == ConsensusSessionState::ConsensusEstablished,
|
|
version,
|
|
new_nodes_map,
|
|
consensus_group,
|
|
)
|
|
};
|
|
|
|
// update data
|
|
if let Some(version) = version {
|
|
data.version = Some(version);
|
|
}
|
|
if let Some(new_nodes_map) = new_nodes_map {
|
|
data.id_numbers = Some(new_nodes_map);
|
|
}
|
|
if let Some(consensus_group) = consensus_group {
|
|
data.secret_subshares = Some(consensus_group.into_iter().map(|n| (n, None)).collect());
|
|
}
|
|
|
|
// if consensus is stablished, proceed
|
|
if !is_establishing_consensus || !is_consensus_established || self.core.meta.self_node_id != self.core.meta.master_node_id {
|
|
return Ok(());
|
|
}
|
|
|
|
Self::on_consensus_established(&self.core, &mut *data)
|
|
}
|
|
|
|
/// When common key share data is received by new node.
|
|
pub fn on_common_key_share_data(&self, sender: &NodeId, message: &KeyShareCommon) -> Result<(), Error> {
|
|
debug_assert!(self.core.meta.id == *message.session);
|
|
debug_assert!(sender != &self.core.meta.self_node_id);
|
|
|
|
// only master can send this message
|
|
if sender != &self.core.meta.master_node_id {
|
|
return Err(Error::InvalidMessage);
|
|
}
|
|
|
|
let mut data = self.data.lock();
|
|
|
|
// check state
|
|
if data.state != SessionState::ConsensusEstablishing || data.id_numbers.is_none() {
|
|
return Ok(());
|
|
}
|
|
|
|
// we only expect this message once
|
|
if data.new_key_share.is_some() {
|
|
return Err(Error::InvalidStateForRequest);
|
|
}
|
|
|
|
// check if we actually waiting for this message
|
|
{
|
|
let version = data.version.as_ref().ok_or(Error::InvalidStateForRequest)?;
|
|
let key_version = self.core.key_share.as_ref().and_then(|ks| ks.version(version).ok());
|
|
if key_version.is_some() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// update data
|
|
data.state = SessionState::WaitingForKeysDissemination;
|
|
data.new_key_share = Some(NewKeyShare {
|
|
threshold: message.threshold,
|
|
author: message.author.clone().into(),
|
|
joint_public: message.joint_public.clone().into(),
|
|
common_point: message.common_point.clone().map(Into::into),
|
|
encrypted_point: message.encrypted_point.clone().map(Into::into),
|
|
});
|
|
|
|
let id_numbers = data.id_numbers.as_mut()
|
|
.expect("common key share data is expected after initialization; id_numers are filled during initialization; qed");
|
|
for (node, id_number) in &message.id_numbers {
|
|
let id_number: Secret = id_number.clone().into();
|
|
{
|
|
let local_id_number = id_numbers.get(&node.clone().into());
|
|
match local_id_number {
|
|
Some(&Some(ref local_id_number)) => {
|
|
if *local_id_number == id_number {
|
|
continue;
|
|
}
|
|
|
|
return Err(Error::ConsensusUnreachable);
|
|
},
|
|
Some(&None) => (),
|
|
None => continue, // can happen for isolated nodes
|
|
}
|
|
}
|
|
|
|
id_numbers.insert(node.clone().into(), Some(id_number));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// When keys dissemination message is received.
|
|
pub fn on_new_keys_dissemination(&self, sender: &NodeId, message: &NewKeysDissemination) -> Result<(), Error> {
|
|
debug_assert!(self.core.meta.id == *message.session);
|
|
debug_assert!(sender != &self.core.meta.self_node_id);
|
|
|
|
let mut data = self.data.lock();
|
|
|
|
// check state
|
|
if data.state == SessionState::ConsensusEstablishing && data.secret_subshares.is_some() {
|
|
data.state = SessionState::WaitingForKeysDissemination;
|
|
} else if data.state != SessionState::WaitingForKeysDissemination {
|
|
return Err(Error::InvalidStateForRequest);
|
|
}
|
|
|
|
// update data
|
|
let explanation = "secret_subshares is filled during initialization; keys are disseminated after initialization; qed";
|
|
{
|
|
match data.secret_subshares.as_ref().expect(explanation).get(sender) {
|
|
None => return Err(Error::InvalidMessage),
|
|
Some(&Some(_)) => return Err(Error::InvalidMessage),
|
|
Some(&None) => (),
|
|
};
|
|
|
|
let secret_subshare = Self::compute_secret_subshare(&self.core, &mut *data, sender, &message.secret_subshare.clone().into())?;
|
|
*data.secret_subshares.as_mut().expect(explanation)
|
|
.get_mut(sender)
|
|
.expect("checked couple of lines above; qed") = Some(secret_subshare);
|
|
}
|
|
|
|
// if we have received subshare from master node, it means that we should start dissemination
|
|
if sender == &self.core.meta.master_node_id {
|
|
Self::on_consensus_established(&self.core, &mut *data)?;
|
|
}
|
|
|
|
// check if shares from all nodes are received
|
|
if data.secret_subshares.as_ref().expect(explanation).values().any(|v| v.is_none()) {
|
|
return Ok(())
|
|
}
|
|
|
|
// TODO [Trust]: find a way to verificate keys
|
|
Self::complete_session(&self.core, &mut *data)
|
|
}
|
|
|
|
/// Check nodes map.
|
|
fn check_nodes_map(core: &SessionCore<T>, version: &H256, consensus_group: &BTreeSet<NodeId>, new_nodes_map: &BTreeMap<NodeId, Option<Secret>>) -> Result<(), Error> {
|
|
// check if this node has given version
|
|
let has_this_version = match core.key_share.as_ref() {
|
|
Some(key_share) => key_share.version(version).is_ok(),
|
|
None => false,
|
|
};
|
|
|
|
// check && update passed data
|
|
match has_this_version {
|
|
true => {
|
|
// check if version exists
|
|
let explanation = "has_this_version is true; it is true if we have given version of the key; qed";
|
|
let key_share = core.key_share.as_ref().expect(explanation);
|
|
let key_version = key_share.version(version).expect(explanation);
|
|
|
|
// there must be exactly thresold + 1 nodes in consensus group
|
|
if consensus_group.len() != key_share.threshold + 1 {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
|
|
// every non-isolated node must be a part of new_nodes_set
|
|
let non_isolated_nodes = core.transport.nodes();
|
|
if key_version.id_numbers.keys().any(|n| non_isolated_nodes.contains(n) && !new_nodes_map.contains_key(n)) {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
|
|
// there must be at least one new node in new_nodes_map
|
|
if key_version.id_numbers.len() >= new_nodes_map.len() {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
},
|
|
false => {
|
|
// if we do not have a share, we should not be a part of consenus group
|
|
// but we must be on new nodes set, since this is a ShareAdd session
|
|
if consensus_group.contains(&core.meta.self_node_id) ||
|
|
!new_nodes_map.contains_key(&core.meta.self_node_id) {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
},
|
|
}
|
|
|
|
// master node must always be a part of consensus group
|
|
if !consensus_group.contains(&core.meta.master_node_id) {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
|
|
// master node must always be a part of new_nodes_map
|
|
if !new_nodes_map.contains_key(&core.meta.master_node_id) {
|
|
return Err(Error::ConsensusUnreachable);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Start sending ShareAdd-specific messages, when consensus is established.
|
|
fn on_consensus_established(core: &SessionCore<T>, data: &mut SessionData<T>) -> Result<(), Error> {
|
|
// update state
|
|
data.state = SessionState::WaitingForKeysDissemination;
|
|
|
|
// if we're not a part of consensus group, wait for secret subshares
|
|
let explanation = "secret_subshares is a result of consensus job; consensus is established; qed";
|
|
let is_consensus_group_node = data.secret_subshares.as_ref().expect(explanation).contains_key(&core.meta.self_node_id);
|
|
if !is_consensus_group_node {
|
|
return Ok(());
|
|
}
|
|
|
|
// else if master => send shared data to every new node
|
|
if core.meta.self_node_id == core.meta.master_node_id {
|
|
Self::disseminate_common_share_data(core, data)?;
|
|
}
|
|
|
|
// ...and then disseminate keys
|
|
Self::disseminate_keys(core, data)?;
|
|
|
|
// ..and check if session could be completed
|
|
if data.secret_subshares.as_ref().expect(explanation).values().any(|v| v.is_none()) {
|
|
return Ok(())
|
|
}
|
|
|
|
// TODO [Trust]: find a way to verificate keys
|
|
Self::complete_session(core, data)
|
|
}
|
|
|
|
/// Send common share data to evey new node.
|
|
fn disseminate_common_share_data(core: &SessionCore<T>, data: &SessionData<T>) -> Result<(), Error> {
|
|
let explanation = "disseminate_common_share_data is only called on master node; master node has specified version of the key; qed";
|
|
let old_key_share = core.key_share.as_ref().expect(explanation);
|
|
let old_key_version = old_key_share.version(data.version.as_ref().expect(explanation)).expect(explanation);
|
|
let consensus_group = data.secret_subshares.as_ref()
|
|
.expect("disseminate_common_share_data is only called on master node; consensus group is created during initialization on master node; qed");
|
|
let nodes = data.id_numbers.as_ref()
|
|
.expect("nodes are filled during consensus establishing; common share data sent after consensus is established; qed")
|
|
.keys()
|
|
.filter(|n| !consensus_group.contains_key(n));
|
|
for new_node in nodes {
|
|
core.transport.send(new_node, ShareAddMessage::KeyShareCommon(KeyShareCommon {
|
|
session: core.meta.id.clone().into(),
|
|
session_nonce: core.nonce,
|
|
threshold: old_key_share.threshold,
|
|
author: old_key_share.author.clone().into(),
|
|
joint_public: old_key_share.public.clone().into(),
|
|
common_point: old_key_share.common_point.clone().map(Into::into),
|
|
encrypted_point: old_key_share.encrypted_point.clone().map(Into::into),
|
|
id_numbers: old_key_version.id_numbers.iter().map(|(k, v)| (k.clone().into(), v.clone().into())).collect(),
|
|
}))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Disseminate key refreshing data.
|
|
fn disseminate_keys(core: &SessionCore<T>, data: &mut SessionData<T>) -> Result<(), Error> {
|
|
// generate random polynom with secret share as absolute term
|
|
let explanation = "disseminate_keys is only called on consensus group nodes; consensus group nodes have specified version of the key; qed";
|
|
let key_share = core.key_share.as_ref().expect(explanation);
|
|
let key_version = key_share.version(data.version.as_ref().expect(explanation)).expect(explanation);
|
|
let mut secret_share_polynom = math::generate_random_polynom(key_share.threshold)?;
|
|
secret_share_polynom[0] = key_version.secret_share.clone();
|
|
|
|
// calculate secret subshare for every new node (including this node)
|
|
let explanation = "disseminate_keys is called after initialization has completed; this field is filled during initialization; qed";
|
|
for (new_node, new_node_number) in data.id_numbers.as_ref().expect(explanation).iter() {
|
|
let new_node_number = new_node_number.as_ref().ok_or(Error::InvalidMessage)?;
|
|
let secret_subshare = math::compute_polynom(&secret_share_polynom, new_node_number)?;
|
|
if new_node != &core.meta.self_node_id {
|
|
core.transport.send(new_node, ShareAddMessage::NewKeysDissemination(NewKeysDissemination {
|
|
session: core.meta.id.clone().into(),
|
|
session_nonce: core.nonce,
|
|
secret_subshare: secret_subshare.into(),
|
|
}))?;
|
|
} else {
|
|
let secret_subshare = Self::compute_secret_subshare(core, data, new_node, &secret_subshare)?;
|
|
*data.secret_subshares.as_mut().expect(explanation)
|
|
.get_mut(&core.meta.self_node_id)
|
|
.expect("disseminate_keys is only calle on consensus group nodes; there's entry for every consensus node in secret_subshares; qed")
|
|
= Some(secret_subshare);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Compute secret subshare from passed secret value.
|
|
fn compute_secret_subshare(core: &SessionCore<T>, data: &SessionData<T>, sender: &NodeId, secret_value: &Secret) -> Result<Secret, Error> {
|
|
let explanation = "this field is a result of consensus job; compute_secret_subshare is called after consensus is established";
|
|
let id_numbers = data.id_numbers.as_ref().expect(explanation);
|
|
let secret_subshares = data.secret_subshares.as_ref().expect(explanation);
|
|
let threshold = core.key_share.as_ref().map(|ks| ks.threshold)
|
|
.unwrap_or_else(|| data.new_key_share.as_ref()
|
|
.expect("computation occurs after receiving key share threshold if not having one already; qed")
|
|
.threshold);
|
|
|
|
let explanation = "id_numbers are checked to have Some value for every consensus group node when consensus is establishe; qed";
|
|
let sender_id_number = id_numbers[sender].as_ref().expect(explanation);
|
|
let other_id_numbers = secret_subshares.keys().filter(|k| *k != sender).map(|n| id_numbers[n].as_ref().expect(explanation));
|
|
math::compute_secret_subshare(threshold, secret_value, sender_id_number, other_id_numbers)
|
|
}
|
|
|
|
/// Complete session.
|
|
fn complete_session(core: &SessionCore<T>, data: &mut SessionData<T>) -> Result<(), Error> {
|
|
// if already completed, do nothing
|
|
if data.state == SessionState::Finished {
|
|
return Ok(());
|
|
}
|
|
|
|
// compose updated key share
|
|
let explanation = "this field is a result of consensus job; complete_session is called after consensus is established";
|
|
let id_numbers = data.id_numbers.as_ref().expect(explanation);
|
|
let secret_subshares = data.secret_subshares.as_ref()
|
|
.expect("nodes are filled during consensus establishing; session is completed after consensus is established; qed");
|
|
let secret_share = math::compute_secret_share(secret_subshares.values().map(|ss| ss.as_ref()
|
|
.expect("complete_session is only called when subshares from all nodes are received; qed")))?;
|
|
|
|
let refreshed_key_version = DocumentKeyShareVersion::new(id_numbers.clone().into_iter().map(|(k, v)| (k.clone(),
|
|
v.expect("id_numbers are checked to have Some value for every consensus group node when consensus is establishe; qed"))).collect(),
|
|
secret_share);
|
|
let mut refreshed_key_share = core.key_share.as_ref().cloned().unwrap_or_else(|| {
|
|
let new_key_share = data.new_key_share.as_ref()
|
|
.expect("this is new node; on new nodes this field is filled before KRD; session is completed after KRD; qed");
|
|
DocumentKeyShare {
|
|
author: new_key_share.author.clone(),
|
|
threshold: new_key_share.threshold,
|
|
public: new_key_share.joint_public.clone(),
|
|
common_point: new_key_share.common_point.clone(),
|
|
encrypted_point: new_key_share.encrypted_point.clone(),
|
|
versions: Vec::new(),
|
|
}
|
|
});
|
|
refreshed_key_share.versions.push(refreshed_key_version);
|
|
|
|
// save encrypted data to the key storage
|
|
data.state = SessionState::Finished;
|
|
if core.key_share.is_some() {
|
|
core.key_storage.update(core.meta.id.clone(), refreshed_key_share.clone())
|
|
} else {
|
|
core.key_storage.insert(core.meta.id.clone(), refreshed_key_share.clone())
|
|
}.map_err(|e| Error::KeyStorage(e.into()))?;
|
|
|
|
// signal session completion
|
|
data.state = SessionState::Finished;
|
|
data.result = Some(Ok(()));
|
|
core.completed.notify_all();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<T> ClusterSession for SessionImpl<T> where T: SessionTransport {
|
|
type Id = SessionId;
|
|
|
|
fn type_name() -> &'static str {
|
|
"share add"
|
|
}
|
|
|
|
fn id(&self) -> SessionId {
|
|
self.core.meta.id.clone()
|
|
}
|
|
|
|
fn is_finished(&self) -> bool {
|
|
self.data.lock().state == SessionState::Finished
|
|
}
|
|
|
|
fn on_session_timeout(&self) {
|
|
self.on_session_error(&self.core.meta.self_node_id, Error::NodeDisconnected)
|
|
}
|
|
|
|
fn on_node_timeout(&self, node: &NodeId) {
|
|
self.on_session_error(node, Error::NodeDisconnected)
|
|
}
|
|
|
|
fn on_session_error(&self, node: &NodeId, error: Error) {
|
|
// error in generation session is considered fatal
|
|
// => broadcast error if error occured on this node
|
|
if *node == self.core.meta.self_node_id {
|
|
for node in self.core.transport.nodes() {
|
|
// do not bother processing send error, as we already processing error
|
|
let _ = self.core.transport.send(&node, ShareAddMessage::ShareAddError(ShareAddError {
|
|
session: self.core.meta.id.clone().into(),
|
|
session_nonce: self.core.nonce,
|
|
error: error.clone().into(),
|
|
}));
|
|
}
|
|
}
|
|
|
|
let mut data = self.data.lock();
|
|
|
|
warn!("{}: share add session failed: {} on {}", 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::ShareAdd(ref message) => self.process_message(sender, message),
|
|
_ => unreachable!("cluster checks message to be correct before passing; qed"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IsolatedSessionTransport {
|
|
pub fn new(session_id: SessionId, version: Option<H256>, nonce: u64, cluster: Arc<Cluster>) -> Self {
|
|
IsolatedSessionTransport {
|
|
session: session_id,
|
|
version: version,
|
|
nonce: nonce,
|
|
cluster: cluster,
|
|
id_numbers: None,
|
|
consensus_group: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl JobTransport for IsolatedSessionTransport {
|
|
type PartialJobRequest = ServersSetChangeAccessRequest;
|
|
type PartialJobResponse = bool;
|
|
|
|
fn send_partial_request(&self, node: &NodeId, request: ServersSetChangeAccessRequest) -> Result<(), Error> {
|
|
let explanation = "partial requests are sent from master node only; on master node this field is filled during creation; qed";
|
|
let id_numbers = self.id_numbers.as_ref().expect(explanation);
|
|
|
|
self.cluster.send(node, Message::ShareAdd(ShareAddMessage::ShareAddConsensusMessage(ShareAddConsensusMessage {
|
|
session: self.session.clone().into(),
|
|
session_nonce: self.nonce,
|
|
message: ConsensusMessageOfShareAdd::InitializeConsensusSession(InitializeConsensusSessionOfShareAdd {
|
|
version: self.version.clone().expect(explanation).into(),
|
|
consensus_group: self.consensus_group.as_ref().expect(explanation).iter().cloned().map(Into::into).collect(),
|
|
old_nodes_set: request.old_servers_set.into_iter().map(Into::into).collect(),
|
|
new_nodes_map: request.new_servers_set.into_iter()
|
|
.filter_map(|n| id_numbers.get(&n)
|
|
.map(|id| (n.into(), id.clone()
|
|
.expect("partial requests are sent from master node only after consensus is established;
|
|
on master id_numbers are initialized with Some id_number for every consensus group node; qed").into())))
|
|
.collect(),
|
|
old_set_signature: request.old_set_signature.into(),
|
|
new_set_signature: request.new_set_signature.into(),
|
|
}),
|
|
})))
|
|
}
|
|
|
|
fn send_partial_response(&self, node: &NodeId, response: bool) -> Result<(), Error> {
|
|
self.cluster.send(node, Message::ShareAdd(ShareAddMessage::ShareAddConsensusMessage(ShareAddConsensusMessage {
|
|
session: self.session.clone().into(),
|
|
session_nonce: self.nonce,
|
|
message: ConsensusMessageOfShareAdd::ConfirmConsensusInitialization(ConfirmConsensusInitialization {
|
|
is_confirmed: response,
|
|
}),
|
|
})))
|
|
}
|
|
}
|
|
|
|
impl SessionTransport for IsolatedSessionTransport {
|
|
fn nodes(&self) -> BTreeSet<NodeId> {
|
|
self.cluster.nodes()
|
|
}
|
|
|
|
fn set_master_data(&mut self, consensus_group: BTreeSet<NodeId>, id_numbers: BTreeMap<NodeId, Option<Secret>>) {
|
|
self.consensus_group = Some(consensus_group);
|
|
self.id_numbers = Some(id_numbers);
|
|
}
|
|
|
|
fn send(&self, node: &NodeId, message: ShareAddMessage) -> Result<(), Error> {
|
|
self.cluster.send(node, Message::ShareAdd(message))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use std::sync::Arc;
|
|
use std::collections::{VecDeque, BTreeMap, BTreeSet};
|
|
use ethkey::{Random, Generator, Public, KeyPair, Signature, sign};
|
|
use bigint::hash::H256;
|
|
use key_server_cluster::{NodeId, SessionId, Error, KeyStorage, DummyKeyStorage};
|
|
use key_server_cluster::cluster::Cluster;
|
|
use key_server_cluster::cluster::tests::DummyCluster;
|
|
use key_server_cluster::cluster_sessions::ClusterSession;
|
|
use key_server_cluster::generation_session::tests::{Node as GenerationNode, generate_nodes_ids};
|
|
use key_server_cluster::math;
|
|
use key_server_cluster::message::Message;
|
|
use key_server_cluster::servers_set_change_session::tests::generate_key;
|
|
use key_server_cluster::jobs::servers_set_change_access_job::ordered_nodes_hash;
|
|
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
|
|
use super::{SessionImpl, SessionParams, IsolatedSessionTransport};
|
|
|
|
struct Node {
|
|
pub cluster: Arc<DummyCluster>,
|
|
pub key_storage: Arc<DummyKeyStorage>,
|
|
pub session: SessionImpl<IsolatedSessionTransport>,
|
|
}
|
|
|
|
struct MessageLoop {
|
|
pub admin_key_pair: KeyPair,
|
|
pub original_key_pair: KeyPair,
|
|
pub old_nodes_set: BTreeSet<NodeId>,
|
|
pub new_nodes_set: BTreeSet<NodeId>,
|
|
pub old_set_signature: Signature,
|
|
pub new_set_signature: Signature,
|
|
pub nodes: BTreeMap<NodeId, Node>,
|
|
pub queue: VecDeque<(NodeId, NodeId, Message)>,
|
|
pub version: H256,
|
|
}
|
|
|
|
fn create_session(mut meta: ShareChangeSessionMeta, admin_public: Public, self_node_id: NodeId, cluster: Arc<Cluster>, key_storage: Arc<KeyStorage>) -> SessionImpl<IsolatedSessionTransport> {
|
|
let session_id = meta.id.clone();
|
|
meta.self_node_id = self_node_id;
|
|
let key_version = key_storage.get(&session_id).unwrap().map(|ks| ks.versions.iter().last().unwrap().hash.clone());
|
|
|
|
SessionImpl::new(SessionParams {
|
|
meta: meta.clone(),
|
|
transport: IsolatedSessionTransport::new(session_id, key_version, 1, cluster),
|
|
key_storage: key_storage,
|
|
admin_public: Some(admin_public),
|
|
nonce: 1,
|
|
}).unwrap()
|
|
}
|
|
|
|
fn create_node(meta: ShareChangeSessionMeta, admin_public: Public, node: GenerationNode, added_nodes: &BTreeSet<NodeId>) -> Node {
|
|
node.cluster.add_nodes(added_nodes.iter().cloned());
|
|
Node {
|
|
cluster: node.cluster.clone(),
|
|
key_storage: node.key_storage.clone(),
|
|
session: create_session(meta, admin_public, node.session.node().clone(), node.cluster, node.key_storage),
|
|
}
|
|
}
|
|
|
|
/// This only works for schemes where threshold = 1
|
|
pub fn check_secret_is_preserved(joint_key_pair: KeyPair, nodes: BTreeMap<NodeId, Arc<DummyKeyStorage>>) {
|
|
let n = nodes.len();
|
|
let document_secret_plain = math::generate_random_point().unwrap();
|
|
for n1 in 0..n {
|
|
for n2 in n1+1..n {
|
|
let share1 = nodes.values().nth(n1).unwrap().get(&SessionId::default()).unwrap();
|
|
let share2 = nodes.values().nth(n2).unwrap().get(&SessionId::default()).unwrap();
|
|
let id_number1 = share1.as_ref().unwrap().last_version().unwrap().id_numbers[nodes.keys().nth(n1).unwrap()].clone();
|
|
let id_number2 = share1.as_ref().unwrap().last_version().unwrap().id_numbers[nodes.keys().nth(n2).unwrap()].clone();
|
|
|
|
// now encrypt and decrypt data
|
|
let (document_secret_decrypted, document_secret_decrypted_test) =
|
|
math::tests::do_encryption_and_decryption(1,
|
|
joint_key_pair.public(),
|
|
&[id_number1, id_number2],
|
|
&[share1.unwrap().last_version().unwrap().secret_share.clone(),
|
|
share2.unwrap().last_version().unwrap().secret_share.clone()],
|
|
Some(joint_key_pair.secret()),
|
|
document_secret_plain.clone());
|
|
|
|
assert_eq!(document_secret_plain, document_secret_decrypted_test);
|
|
assert_eq!(document_secret_plain, document_secret_decrypted);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MessageLoop {
|
|
pub fn new(t: usize, master_node_id: NodeId, old_nodes_set: BTreeSet<NodeId>, new_nodes_set: BTreeSet<NodeId>) -> Self {
|
|
// generate admin key pair
|
|
let admin_key_pair = Random.generate().unwrap();
|
|
let admin_public = admin_key_pair.public().clone();
|
|
|
|
// run initial generation session
|
|
let gml = generate_key(t, old_nodes_set.clone());
|
|
|
|
// compute original secret key
|
|
let version = gml.nodes.values().nth(0).unwrap().key_storage.get(&Default::default()).unwrap().unwrap().versions[0].hash.clone();
|
|
let original_key_pair = gml.compute_key_pair(t);
|
|
|
|
// prepare sessions on all nodes
|
|
let meta = ShareChangeSessionMeta {
|
|
id: SessionId::default(),
|
|
self_node_id: NodeId::default(),
|
|
master_node_id: master_node_id,
|
|
};
|
|
let new_nodes = new_nodes_set.iter()
|
|
.filter(|n| !old_nodes_set.contains(&n))
|
|
.map(|new_node_id| {
|
|
let new_node_cluster = Arc::new(DummyCluster::new(new_node_id.clone()));
|
|
let new_node_key_storage = Arc::new(DummyKeyStorage::default());
|
|
let new_node_session = create_session(meta.clone(), admin_public.clone(), new_node_id.clone(), new_node_cluster.clone(), new_node_key_storage.clone());
|
|
new_node_cluster.add_nodes(new_nodes_set.iter().cloned());
|
|
Node {
|
|
cluster: new_node_cluster,
|
|
key_storage: new_node_key_storage,
|
|
session: new_node_session,
|
|
}
|
|
});
|
|
let old_nodes = gml.nodes.into_iter().map(|gn| create_node(meta.clone(), admin_public.clone(), gn.1, &new_nodes_set));
|
|
let nodes = old_nodes.chain(new_nodes).map(|n| (n.session.core.meta.self_node_id.clone(), n)).collect();
|
|
|
|
let old_set_signature = sign(admin_key_pair.secret(), &ordered_nodes_hash(&old_nodes_set)).unwrap();
|
|
let new_set_signature = sign(admin_key_pair.secret(), &ordered_nodes_hash(&new_nodes_set)).unwrap();
|
|
MessageLoop {
|
|
admin_key_pair: admin_key_pair,
|
|
original_key_pair: original_key_pair,
|
|
version: version,
|
|
old_nodes_set: old_nodes_set.clone(),
|
|
new_nodes_set: new_nodes_set.clone(),
|
|
old_set_signature: old_set_signature,
|
|
new_set_signature: new_set_signature,
|
|
nodes: nodes,
|
|
queue: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn new_additional(master_node_id: NodeId, ml: MessageLoop, new_nodes_set: BTreeSet<NodeId>) -> Self {
|
|
let version = ml.nodes.values().nth(0).unwrap().key_storage.get(&Default::default()).unwrap().unwrap().versions.last().unwrap().hash.clone();
|
|
|
|
// prepare sessions on all nodes
|
|
let meta = ShareChangeSessionMeta {
|
|
id: SessionId::default(),
|
|
self_node_id: NodeId::default(),
|
|
master_node_id: master_node_id,
|
|
};
|
|
let old_nodes_set = ml.nodes.keys().cloned().collect();
|
|
let nodes = ml.nodes.iter()
|
|
.map(|(n, nd)| {
|
|
let node_cluster = nd.cluster.clone();
|
|
let node_key_storage = nd.key_storage.clone();
|
|
let node_session = create_session(meta.clone(), ml.admin_key_pair.public().clone(), n.clone(), node_cluster.clone(), node_key_storage.clone());
|
|
node_cluster.add_nodes(new_nodes_set.iter().cloned());
|
|
(n.clone(), Node {
|
|
cluster: node_cluster,
|
|
key_storage: node_key_storage,
|
|
session: node_session,
|
|
})
|
|
}).chain(new_nodes_set.difference(&old_nodes_set).map(|n| {
|
|
let new_node_cluster = Arc::new(DummyCluster::new(n.clone()));
|
|
let new_node_key_storage = Arc::new(DummyKeyStorage::default());
|
|
let new_node_session = create_session(meta.clone(), ml.admin_key_pair.public().clone(), n.clone(), new_node_cluster.clone(), new_node_key_storage.clone());
|
|
new_node_cluster.add_nodes(new_nodes_set.iter().cloned());
|
|
(n.clone(), Node {
|
|
cluster: new_node_cluster,
|
|
key_storage: new_node_key_storage,
|
|
session: new_node_session,
|
|
})
|
|
})).collect();
|
|
|
|
let old_set_signature = sign(ml.admin_key_pair.secret(), &ordered_nodes_hash(&old_nodes_set)).unwrap();
|
|
let new_set_signature = sign(ml.admin_key_pair.secret(), &ordered_nodes_hash(&new_nodes_set)).unwrap();
|
|
MessageLoop {
|
|
admin_key_pair: ml.admin_key_pair,
|
|
original_key_pair: ml.original_key_pair,
|
|
version: version,
|
|
old_nodes_set: old_nodes_set.clone(),
|
|
new_nodes_set: new_nodes_set.clone(),
|
|
old_set_signature: old_set_signature,
|
|
new_set_signature: new_set_signature,
|
|
nodes: nodes,
|
|
queue: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn update_signature(&mut self) {
|
|
self.old_set_signature = sign(self.admin_key_pair.secret(), &ordered_nodes_hash(&self.old_nodes_set)).unwrap();
|
|
self.new_set_signature = sign(self.admin_key_pair.secret(), &ordered_nodes_hash(&self.new_nodes_set)).unwrap();
|
|
}
|
|
|
|
pub fn run(&mut self) {
|
|
while let Some((from, to, message)) = self.take_message() {
|
|
self.process_message((from, to, message)).unwrap();
|
|
}
|
|
}
|
|
|
|
pub fn take_message(&mut self) -> Option<(NodeId, NodeId, Message)> {
|
|
self.nodes.values()
|
|
.filter_map(|n| n.cluster.take_message().map(|m| (n.session.core.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 { match msg.2 {
|
|
Message::ShareAdd(ref message) =>
|
|
self.nodes[&msg.1].session.process_message(&msg.0, message),
|
|
_ => unreachable!("only servers set change messages are expected"),
|
|
} } {
|
|
Ok(_) => Ok(()),
|
|
Err(Error::TooEarlyForRequest) => {
|
|
self.queue.push_back(msg);
|
|
Ok(())
|
|
},
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn node_add_fails_if_nodes_removed() {
|
|
let old_nodes_set = generate_nodes_ids(3);
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let node_to_remove_id = old_nodes_set.iter().cloned().nth(1).unwrap();
|
|
let mut new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(generate_nodes_ids(1)).collect();
|
|
new_nodes_set.remove(&node_to_remove_id);
|
|
let ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())
|
|
).unwrap_err(), Error::ConsensusUnreachable);
|
|
}
|
|
|
|
#[test]
|
|
fn node_add_fails_if_no_nodes_added() {
|
|
let old_nodes_set = generate_nodes_ids(3);
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let new_nodes_set = old_nodes_set.clone();
|
|
let ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())
|
|
).unwrap_err(), Error::ConsensusUnreachable);
|
|
}
|
|
|
|
#[test]
|
|
fn node_add_fails_if_started_on_adding_node() {
|
|
let old_nodes_set = generate_nodes_ids(3);
|
|
let nodes_to_add_set = generate_nodes_ids(1);
|
|
let master_node_id = nodes_to_add_set.iter().cloned().nth(0).unwrap();
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(nodes_to_add_set.into_iter()).collect();
|
|
let ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())
|
|
).unwrap_err(), Error::KeyStorage("key share is not found on master node".into()));
|
|
}
|
|
|
|
#[test]
|
|
fn node_add_fails_if_initialized_twice() {
|
|
let old_nodes_set = generate_nodes_ids(3);
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(generate_nodes_ids(1)).collect();
|
|
let ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set.clone()),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())
|
|
), Ok(()));
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())
|
|
), Err(Error::InvalidStateForRequest));
|
|
}
|
|
|
|
#[test]
|
|
fn node_add_fails_if_started_without_signatures() {
|
|
let old_nodes_set = generate_nodes_ids(3);
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(generate_nodes_ids(1)).collect();
|
|
let ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(None, None, None, None), Err(Error::InvalidMessage));
|
|
}
|
|
|
|
#[test]
|
|
fn nodes_added_using_share_add() {
|
|
let test_cases = vec![(3, 1), (3, 3)];
|
|
for (n, nodes_to_add) in test_cases {
|
|
// generate key && prepare ShareAdd sessions
|
|
let old_nodes_set = generate_nodes_ids(n);
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(generate_nodes_ids(nodes_to_add)).collect();
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let mut ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
|
|
// initialize session on master node && run to completion
|
|
ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())).unwrap();
|
|
ml.run();
|
|
|
|
// check that session has completed on all nodes
|
|
assert!(ml.nodes.values().all(|n| n.session.is_finished()));
|
|
|
|
// check that secret is still the same as before adding the share
|
|
check_secret_is_preserved(ml.original_key_pair.clone(), ml.nodes.iter().map(|(k, v)| (k.clone(), v.key_storage.clone())).collect());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nodes_added_using_share_add_with_isolated_nodes() {
|
|
let (n, nodes_to_add) = (3, 3);
|
|
|
|
// generate key && prepare ShareAdd sessions
|
|
let old_nodes_set = generate_nodes_ids(n);
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(generate_nodes_ids(nodes_to_add)).collect();
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let isolated_node_id = old_nodes_set.iter().cloned().nth(1).unwrap();
|
|
let mut ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set, new_nodes_set.clone());
|
|
|
|
// now let's isolate 1 of 3 nodes owning key share
|
|
ml.nodes.remove(&isolated_node_id);
|
|
ml.old_nodes_set.remove(&isolated_node_id);
|
|
ml.new_nodes_set.remove(&isolated_node_id);
|
|
for (_, node) in ml.nodes.iter_mut() {
|
|
node.cluster.remove_node(&isolated_node_id);
|
|
}
|
|
ml.update_signature();
|
|
|
|
// initialize session on master node && run to completion
|
|
ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())).unwrap();
|
|
ml.run();
|
|
|
|
// check that session has completed on all nodes
|
|
assert!(ml.nodes.values().all(|n| n.session.is_finished()));
|
|
|
|
// check that secret is still the same as before adding the share
|
|
check_secret_is_preserved(ml.original_key_pair.clone(), ml.nodes
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.key_storage.clone()))
|
|
.collect());
|
|
}
|
|
|
|
#[test]
|
|
fn nodes_add_to_the_node_with_obsolete_version() {
|
|
let (n, nodes_to_add) = (3, 3);
|
|
|
|
// generate key (2-of-3) && prepare ShareAdd sessions
|
|
let old_nodes_set = generate_nodes_ids(n);
|
|
let newest_nodes_set = generate_nodes_ids(nodes_to_add);
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(newest_nodes_set.clone()).collect();
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let isolated_node_id = old_nodes_set.iter().cloned().nth(1).unwrap();
|
|
let oldest_nodes_set: BTreeSet<_> = old_nodes_set.iter().filter(|n| **n != isolated_node_id).cloned().collect();
|
|
let mut ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set.clone(), new_nodes_set.clone());
|
|
let isolated_key_storage = ml.nodes[&isolated_node_id].key_storage.clone();
|
|
|
|
// now let's isolate 1 of 3 nodes owning key share
|
|
ml.nodes.remove(&isolated_node_id);
|
|
ml.old_nodes_set.remove(&isolated_node_id);
|
|
ml.new_nodes_set.remove(&isolated_node_id);
|
|
for (_, node) in ml.nodes.iter_mut() {
|
|
node.cluster.remove_node(&isolated_node_id);
|
|
}
|
|
ml.update_signature();
|
|
|
|
// initialize session on master node && run to completion (2-of-5)
|
|
ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())).unwrap();
|
|
ml.run();
|
|
|
|
// now let's add back old node so that key becames 2-of-6
|
|
let new_nodes_set: BTreeSet<_> = ml.nodes.keys().cloned().chain(::std::iter::once(isolated_node_id.clone())).collect();
|
|
let mut ml = MessageLoop::new_additional(master_node_id.clone(), ml, new_nodes_set.clone());
|
|
ml.nodes.get_mut(&isolated_node_id).unwrap().key_storage = isolated_key_storage.clone();
|
|
ml.nodes.get_mut(&isolated_node_id).unwrap().session.core.key_share = isolated_key_storage.get(&Default::default()).unwrap();
|
|
ml.nodes.get_mut(&isolated_node_id).unwrap().session.core.key_storage = isolated_key_storage;
|
|
|
|
// initialize session on master node && run to completion (2-of65)
|
|
ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())).unwrap();
|
|
ml.run();
|
|
|
|
// check that session has completed on all nodes
|
|
assert!(ml.nodes.values().all(|n| n.session.is_finished()));
|
|
|
|
// check that secret is still the same as before adding the share
|
|
check_secret_is_preserved(ml.original_key_pair.clone(), ml.nodes
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.key_storage.clone()))
|
|
.collect());
|
|
|
|
// check that all oldest nodes have versions A, B, C
|
|
// isolated node has version A, C
|
|
// new nodes have versions B, C
|
|
let oldest_key_share = ml.nodes[oldest_nodes_set.iter().nth(0).unwrap()].key_storage.get(&Default::default()).unwrap().unwrap();
|
|
debug_assert_eq!(oldest_key_share.versions.len(), 3);
|
|
let version_a = oldest_key_share.versions[0].hash.clone();
|
|
let version_b = oldest_key_share.versions[1].hash.clone();
|
|
let version_c = oldest_key_share.versions[2].hash.clone();
|
|
debug_assert!(version_a != version_b && version_b != version_c);
|
|
|
|
debug_assert!(oldest_nodes_set.iter().all(|n| vec![version_a.clone(), version_b.clone(), version_c.clone()] ==
|
|
ml.nodes[n].key_storage.get(&Default::default()).unwrap().unwrap().versions.iter().map(|v| v.hash.clone()).collect::<Vec<_>>()));
|
|
debug_assert!(::std::iter::once(&isolated_node_id).all(|n| vec![version_a.clone(), version_c.clone()] ==
|
|
ml.nodes[n].key_storage.get(&Default::default()).unwrap().unwrap().versions.iter().map(|v| v.hash.clone()).collect::<Vec<_>>()));
|
|
debug_assert!(newest_nodes_set.iter().all(|n| vec![version_b.clone(), version_c.clone()] ==
|
|
ml.nodes[n].key_storage.get(&Default::default()).unwrap().unwrap().versions.iter().map(|v| v.hash.clone()).collect::<Vec<_>>()));
|
|
}
|
|
|
|
#[test]
|
|
fn nodes_add_fails_when_not_enough_share_owners_are_connected() {
|
|
let (n, nodes_to_add) = (3, 3);
|
|
|
|
// generate key (2-of-3) && prepare ShareAdd sessions
|
|
let old_nodes_set = generate_nodes_ids(n);
|
|
let new_nodes_set: BTreeSet<_> = old_nodes_set.clone().into_iter().chain(generate_nodes_ids(nodes_to_add)).collect();
|
|
let master_node_id = old_nodes_set.iter().cloned().nth(0).unwrap();
|
|
let isolated_node_id1 = old_nodes_set.iter().cloned().nth(1).unwrap();
|
|
let isolated_node_id2 = old_nodes_set.iter().cloned().nth(2).unwrap();
|
|
let mut ml = MessageLoop::new(1, master_node_id.clone(), old_nodes_set.clone(), new_nodes_set.clone());
|
|
|
|
// now let's isolate 2 of 3 nodes owning key share
|
|
ml.nodes.remove(&isolated_node_id1);
|
|
ml.nodes.remove(&isolated_node_id2);
|
|
ml.old_nodes_set.remove(&isolated_node_id1);
|
|
ml.new_nodes_set.remove(&isolated_node_id1);
|
|
ml.old_nodes_set.remove(&isolated_node_id2);
|
|
ml.new_nodes_set.remove(&isolated_node_id2);
|
|
for (_, node) in ml.nodes.iter_mut() {
|
|
node.cluster.remove_node(&isolated_node_id1);
|
|
node.cluster.remove_node(&isolated_node_id2);
|
|
}
|
|
ml.update_signature();
|
|
|
|
// initialize session on master node && run to completion (2-of-5)
|
|
assert_eq!(ml.nodes[&master_node_id].session.initialize(Some(ml.version), Some(new_nodes_set),
|
|
Some(ml.old_set_signature.clone()),
|
|
Some(ml.new_set_signature.clone())).map(|_| ()), Err(Error::ConsensusUnreachable));
|
|
}
|
|
}
|