Secretstore DKG protocol draft + distributed encryption/decryption tests (#4725)
* ECDKG protocol prototype * added test for enc/dec math * get rid of decryption_session * added licenses * fix after merge * get rid of unused serde dependency * doc * decryption session [without commutative enc] * failed_dec_session * fixed tests * added commen * added more decryption session tests * helper to localize an issue * more computations to localize error * decryption_session::SessionParams * added tests for EC math to localize problem
This commit is contained in:
parent
b7862ac23a
commit
fddbc9e5cb
@ -51,6 +51,12 @@ impl fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<String> for Error {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<::secp256k1::Error> for Error {
|
impl From<::secp256k1::Error> for Error {
|
||||||
fn from(e: ::secp256k1::Error) -> Error {
|
fn from(e: ::secp256k1::Error) -> Error {
|
||||||
match e {
|
match e {
|
||||||
|
@ -36,6 +36,17 @@ pub fn public_add(public: &mut Public, other: &Public) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inplace sub one public key from another (EC point - EC point)
|
||||||
|
pub fn public_sub(public: &mut Public, other: &Public) -> Result<(), Error> {
|
||||||
|
let mut key_neg_other = to_secp256k1_public(other)?;
|
||||||
|
key_neg_other.mul_assign(&SECP256K1, &key::MINUS_ONE_KEY)?;
|
||||||
|
|
||||||
|
let mut key_public = to_secp256k1_public(public)?;
|
||||||
|
key_public.add_assign(&SECP256K1, &key_neg_other)?;
|
||||||
|
set_public(public, &key_public);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Return base point of secp256k1
|
/// Return base point of secp256k1
|
||||||
pub fn generation_point() -> Public {
|
pub fn generation_point() -> Public {
|
||||||
let mut public_sec_raw = [0u8; 65];
|
let mut public_sec_raw = [0u8; 65];
|
||||||
@ -64,3 +75,35 @@ fn set_public(public: &mut Public, key_public: &key::PublicKey) {
|
|||||||
let key_public_serialized = key_public.serialize_vec(&SECP256K1, false);
|
let key_public_serialized = key_public.serialize_vec(&SECP256K1, false);
|
||||||
public.copy_from_slice(&key_public_serialized[1..65]);
|
public.copy_from_slice(&key_public_serialized[1..65]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::super::{Random, Generator};
|
||||||
|
use super::{public_add, public_sub};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_addition_is_commutative() {
|
||||||
|
let public1 = Random.generate().unwrap().public().clone();
|
||||||
|
let public2 = Random.generate().unwrap().public().clone();
|
||||||
|
|
||||||
|
let mut left = public1.clone();
|
||||||
|
public_add(&mut left, &public2).unwrap();
|
||||||
|
|
||||||
|
let mut right = public2.clone();
|
||||||
|
public_add(&mut right, &public1).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_addition_is_reversible_with_subtraction() {
|
||||||
|
let public1 = Random.generate().unwrap().public().clone();
|
||||||
|
let public2 = Random.generate().unwrap().public().clone();
|
||||||
|
|
||||||
|
let mut sum = public1.clone();
|
||||||
|
public_add(&mut sum, &public2).unwrap();
|
||||||
|
public_sub(&mut sum, &public2).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sum, public1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -130,3 +130,54 @@ impl Deref for Secret {
|
|||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
use super::super::{Random, Generator};
|
||||||
|
use super::Secret;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiplicating_secret_inversion_with_secret_gives_one() {
|
||||||
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
let mut inversion = secret.clone();
|
||||||
|
inversion.inv().unwrap();
|
||||||
|
inversion.mul(&secret).unwrap();
|
||||||
|
assert_eq!(inversion, Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_inversion_is_reversible_with_inversion() {
|
||||||
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
let mut inversion = secret.clone();
|
||||||
|
inversion.inv().unwrap();
|
||||||
|
inversion.inv().unwrap();
|
||||||
|
assert_eq!(inversion, secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_pow() {
|
||||||
|
let secret = Random.generate().unwrap().secret().clone();
|
||||||
|
|
||||||
|
let mut pow0 = secret.clone();
|
||||||
|
pow0.pow(0).unwrap();
|
||||||
|
assert_eq!(pow0, Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap());
|
||||||
|
|
||||||
|
let mut pow1 = secret.clone();
|
||||||
|
pow1.pow(1).unwrap();
|
||||||
|
assert_eq!(pow1, secret);
|
||||||
|
|
||||||
|
let mut pow2 = secret.clone();
|
||||||
|
pow2.pow(2).unwrap();
|
||||||
|
let mut pow2_expected = secret.clone();
|
||||||
|
pow2_expected.mul(&secret).unwrap();
|
||||||
|
assert_eq!(pow2, pow2_expected);
|
||||||
|
|
||||||
|
let mut pow3 = secret.clone();
|
||||||
|
pow3.pow(3).unwrap();
|
||||||
|
let mut pow3_expected = secret.clone();
|
||||||
|
pow3_expected.mul(&secret).unwrap();
|
||||||
|
pow3_expected.mul(&secret).unwrap();
|
||||||
|
assert_eq!(pow3, pow3_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
90
secret_store/src/key_server_cluster/cluster.rs
Normal file
90
secret_store/src/key_server_cluster/cluster.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// 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 key_server_cluster::{Error, NodeId};
|
||||||
|
use key_server_cluster::message::Message;
|
||||||
|
|
||||||
|
/// Cluster access for single encryption/decryption participant.
|
||||||
|
pub trait Cluster {
|
||||||
|
/// Broadcast message to all other nodes.
|
||||||
|
fn broadcast(&self, message: Message) -> Result<(), Error>;
|
||||||
|
/// Send message to given node.
|
||||||
|
fn send(&self, to: &NodeId, message: Message) -> Result<(), Error>;
|
||||||
|
/// Blacklist node, close connection and remove all pending messages.
|
||||||
|
fn blacklist(&self, node: &NodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use key_server_cluster::{NodeId, Error};
|
||||||
|
use key_server_cluster::message::Message;
|
||||||
|
use key_server_cluster::cluster::Cluster;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DummyCluster {
|
||||||
|
id: NodeId,
|
||||||
|
data: Mutex<DummyClusterData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct DummyClusterData {
|
||||||
|
nodes: Vec<NodeId>,
|
||||||
|
messages: VecDeque<(NodeId, Message)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DummyCluster {
|
||||||
|
pub fn new(id: NodeId) -> Self {
|
||||||
|
DummyCluster {
|
||||||
|
id: id,
|
||||||
|
data: Mutex::new(DummyClusterData::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node(&self) -> NodeId {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_node(&self, node: NodeId) {
|
||||||
|
self.data.lock().nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_message(&self) -> Option<(NodeId, Message)> {
|
||||||
|
self.data.lock().messages.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cluster for DummyCluster {
|
||||||
|
fn broadcast(&self, message: Message) -> Result<(), Error> {
|
||||||
|
let mut data = self.data.lock();
|
||||||
|
let all_nodes: Vec<_> = data.nodes.iter().cloned().filter(|n| n != &self.id).collect();
|
||||||
|
for node in all_nodes {
|
||||||
|
data.messages.push_back((node, message.clone()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&self, to: &NodeId, message: Message) -> Result<(), Error> {
|
||||||
|
debug_assert!(&self.id != to);
|
||||||
|
self.data.lock().messages.push_back((to.clone(), message));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blacklist(&self, _node: &NodeId) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
707
secret_store/src/key_server_cluster/decryption_session.rs
Normal file
707
secret_store/src/key_server_cluster/decryption_session.rs
Normal file
@ -0,0 +1,707 @@
|
|||||||
|
// 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::collections::{BTreeSet, BTreeMap};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use ethkey::{self, Secret, Public, Signature};
|
||||||
|
use key_server_cluster::{Error, AclStorage, EncryptedData, NodeId, SessionId};
|
||||||
|
use key_server_cluster::cluster::Cluster;
|
||||||
|
use key_server_cluster::math;
|
||||||
|
use key_server_cluster::message::{Message, InitializeDecryptionSession, ConfirmDecryptionInitialization,
|
||||||
|
RequestPartialDecryption, PartialDecryption};
|
||||||
|
|
||||||
|
/// Distributed decryption session.
|
||||||
|
/// Based on "ECDKG: A Distributed Key Generation Protocol Based on Elliptic Curve Discrete Logarithm" paper:
|
||||||
|
/// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.124.4128&rep=rep1&type=pdf
|
||||||
|
/// Brief overview:
|
||||||
|
/// 1) initialization: master node (which has received request for decrypting the secret) requests all other nodes to decrypt the secret
|
||||||
|
/// 2) ACL check: all nodes which have received the request are querying ACL-contract to check if requestor has access to the document
|
||||||
|
/// 3) partial decryption: every node which has succussfully checked access for the requestor do a partial decryption
|
||||||
|
/// 4) decryption: master node receives all partial decryptions of the secret and restores the secret
|
||||||
|
pub struct Session {
|
||||||
|
/// Encryption session id.
|
||||||
|
id: SessionId,
|
||||||
|
/// Decryption session access key.
|
||||||
|
access_key: Secret,
|
||||||
|
/// Public identifier of this node.
|
||||||
|
self_node_id: NodeId,
|
||||||
|
/// Encrypted data.
|
||||||
|
encrypted_data: EncryptedData,
|
||||||
|
/// ACL storate to check access to the resource.
|
||||||
|
acl_storage: Arc<AclStorage>,
|
||||||
|
/// Cluster which allows this node to send messages to other nodes in the cluster.
|
||||||
|
cluster: Arc<Cluster>,
|
||||||
|
/// Mutable session data.
|
||||||
|
data: Mutex<SessionData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Session creation parameters
|
||||||
|
pub struct SessionParams {
|
||||||
|
/// Session identifier.
|
||||||
|
pub id: SessionId,
|
||||||
|
/// Session access key.
|
||||||
|
pub access_key: Secret,
|
||||||
|
/// Id of node, on which this session is running.
|
||||||
|
pub self_node_id: Public,
|
||||||
|
/// Encrypted data (result of running encryption_session::Session).
|
||||||
|
pub encrypted_data: EncryptedData,
|
||||||
|
/// ACL storage.
|
||||||
|
pub acl_storage: Arc<AclStorage>,
|
||||||
|
/// Cluster
|
||||||
|
pub cluster: Arc<Cluster>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Mutable data of encryption (distributed key generation) session.
|
||||||
|
struct SessionData {
|
||||||
|
/// Current state of the session.
|
||||||
|
state: SessionState,
|
||||||
|
|
||||||
|
// === Values, filled when session initialization just starts ===
|
||||||
|
/// Reference to the node, which has started this session.
|
||||||
|
master: Option<NodeId>,
|
||||||
|
/// Public key of requestor.
|
||||||
|
requestor: Option<Public>,
|
||||||
|
|
||||||
|
// === Values, filled during session initialization ===
|
||||||
|
/// Nodes, which have been requested for decryption initialization.
|
||||||
|
requested_nodes: BTreeSet<NodeId>,
|
||||||
|
/// Nodes, which have responded with reject to initialization request.
|
||||||
|
rejected_nodes: BTreeSet<NodeId>,
|
||||||
|
/// Nodes, which have responded with confirm to initialization request.
|
||||||
|
confirmed_nodes: BTreeSet<NodeId>,
|
||||||
|
|
||||||
|
// === Values, filled during partial decryption ===
|
||||||
|
/// Shadow points, received from nodes as a response to partial decryption request.
|
||||||
|
shadow_points: BTreeMap<NodeId, Public>,
|
||||||
|
|
||||||
|
/// === Values, filled during final decryption ===
|
||||||
|
/// Decrypted secret
|
||||||
|
decrypted_secret: Option<Public>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NodeData {
|
||||||
|
/// Node-generated shadow point.
|
||||||
|
shadow_point: Option<Public>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum SessionState {
|
||||||
|
/// Every node starts in this state.
|
||||||
|
WaitingForInitialization,
|
||||||
|
/// Master node waits for other nodes to confirm decryption.
|
||||||
|
WaitingForInitializationConfirm,
|
||||||
|
/// Waiting for partial decrypion request.
|
||||||
|
WaitingForPartialDecryptionRequest,
|
||||||
|
/// Waiting for partial decryption responses.
|
||||||
|
WaitingForPartialDecryption,
|
||||||
|
/// Decryption session is finished for this node.
|
||||||
|
Finished,
|
||||||
|
/// Decryption session is failed for this node.
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
/// Create new decryption session.
|
||||||
|
pub fn new(params: SessionParams) -> Result<Self, Error> {
|
||||||
|
check_encrypted_data(¶ms.self_node_id, ¶ms.encrypted_data)?;
|
||||||
|
|
||||||
|
Ok(Session {
|
||||||
|
id: params.id,
|
||||||
|
access_key: params.access_key,
|
||||||
|
self_node_id: params.self_node_id,
|
||||||
|
encrypted_data: params.encrypted_data,
|
||||||
|
acl_storage: params.acl_storage,
|
||||||
|
cluster: params.cluster,
|
||||||
|
data: Mutex::new(SessionData {
|
||||||
|
state: SessionState::WaitingForInitialization,
|
||||||
|
master: None,
|
||||||
|
requestor: None,
|
||||||
|
requested_nodes: BTreeSet::new(),
|
||||||
|
rejected_nodes: BTreeSet::new(),
|
||||||
|
confirmed_nodes: BTreeSet::new(),
|
||||||
|
shadow_points: BTreeMap::new(),
|
||||||
|
decrypted_secret: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this node Id.
|
||||||
|
pub fn node(&self) -> &NodeId {
|
||||||
|
&self.self_node_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this session access key.
|
||||||
|
pub fn access_key(&self) -> &Secret {
|
||||||
|
&self.access_key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current session state.
|
||||||
|
pub fn state(&self) -> SessionState {
|
||||||
|
self.data.lock().state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get decrypted secret
|
||||||
|
pub fn decrypted_secret(&self) -> Option<Public> {
|
||||||
|
self.data.lock().decrypted_secret.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize decryption session.
|
||||||
|
pub fn initialize(&self, requestor_signature: Signature) -> Result<(), Error> {
|
||||||
|
let mut data = self.data.lock();
|
||||||
|
|
||||||
|
// check state
|
||||||
|
if data.state != SessionState::WaitingForInitialization {
|
||||||
|
return Err(Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover requestor signature
|
||||||
|
let requestor_public = ethkey::recover(&requestor_signature, &self.id)?;
|
||||||
|
|
||||||
|
// update state
|
||||||
|
data.master = Some(self.node().clone());
|
||||||
|
data.state = SessionState::WaitingForInitializationConfirm;
|
||||||
|
data.requestor = Some(requestor_public.clone());
|
||||||
|
data.requested_nodes.extend(self.encrypted_data.id_numbers.keys().cloned());
|
||||||
|
|
||||||
|
// ..and finally check access on our's own
|
||||||
|
let is_requestor_allowed_to_read = self.acl_storage.check(&requestor_public, &self.id).unwrap_or(false);
|
||||||
|
process_initialization_response(&self.encrypted_data, &mut *data, self.node(), is_requestor_allowed_to_read)?;
|
||||||
|
|
||||||
|
// check if we have enough nodes to decrypt data
|
||||||
|
match data.state {
|
||||||
|
// not enough nodes => pass initialization message to all other nodes
|
||||||
|
SessionState::WaitingForInitializationConfirm => {
|
||||||
|
for node in self.encrypted_data.id_numbers.keys().filter(|n| *n != self.node()) {
|
||||||
|
self.cluster.send(node, Message::InitializeDecryptionSession(InitializeDecryptionSession {
|
||||||
|
session: self.id.clone(),
|
||||||
|
sub_session: self.access_key.clone(),
|
||||||
|
requestor_signature: requestor_signature.clone(),
|
||||||
|
}))?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// we can decrypt data on our own
|
||||||
|
SessionState::WaitingForPartialDecryption => unimplemented!(),
|
||||||
|
// we can not decrypt data
|
||||||
|
SessionState::Failed => (),
|
||||||
|
// cannot reach other states
|
||||||
|
_ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When session initialization message is received.
|
||||||
|
pub fn on_initialize_session(&self, sender: NodeId, message: InitializeDecryptionSession) -> Result<(), Error> {
|
||||||
|
debug_assert!(self.id == message.session);
|
||||||
|
debug_assert!(self.access_key == message.sub_session);
|
||||||
|
debug_assert!(&sender != self.node());
|
||||||
|
|
||||||
|
let mut data = self.data.lock();
|
||||||
|
|
||||||
|
// check state
|
||||||
|
if data.state != SessionState::WaitingForInitialization {
|
||||||
|
return Err(Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover requestor signature
|
||||||
|
let requestor_public = ethkey::recover(&message.requestor_signature, &self.id)?;
|
||||||
|
|
||||||
|
// check access
|
||||||
|
let is_requestor_allowed_to_read = self.acl_storage.check(&requestor_public, &self.id).unwrap_or(false);
|
||||||
|
data.state = if is_requestor_allowed_to_read { SessionState::WaitingForPartialDecryptionRequest }
|
||||||
|
else { SessionState::Failed };
|
||||||
|
data.requestor = Some(requestor_public);
|
||||||
|
|
||||||
|
// respond to master node
|
||||||
|
data.master = Some(sender.clone());
|
||||||
|
self.cluster.send(&sender, Message::ConfirmDecryptionInitialization(ConfirmDecryptionInitialization {
|
||||||
|
session: self.id.clone(),
|
||||||
|
sub_session: self.access_key.clone(),
|
||||||
|
is_confirmed: is_requestor_allowed_to_read,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When session initialization confirmation message is reeived.
|
||||||
|
pub fn on_confirm_initialization(&self, sender: NodeId, message: ConfirmDecryptionInitialization) -> Result<(), Error> {
|
||||||
|
debug_assert!(self.id == message.session);
|
||||||
|
debug_assert!(self.access_key == message.sub_session);
|
||||||
|
debug_assert!(&sender != self.node());
|
||||||
|
|
||||||
|
let mut data = self.data.lock();
|
||||||
|
|
||||||
|
// check state
|
||||||
|
if data.state != SessionState::WaitingForInitializationConfirm {
|
||||||
|
// if there were enough confirmations/rejections before this message
|
||||||
|
// we have already moved to the next state
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
|
process_initialization_response(&self.encrypted_data, &mut *data, &sender, message.is_confirmed)?;
|
||||||
|
|
||||||
|
// check if we have enough nodes to decrypt data
|
||||||
|
match data.state {
|
||||||
|
// we do not yet have enough nodes for decryption
|
||||||
|
SessionState::WaitingForInitializationConfirm => Ok(()),
|
||||||
|
// we have enough nodes for decryption
|
||||||
|
SessionState::WaitingForPartialDecryption => {
|
||||||
|
let confirmed_nodes: BTreeSet<_> = data.confirmed_nodes.clone();
|
||||||
|
for node in data.confirmed_nodes.iter().filter(|n| n != &self.node()) {
|
||||||
|
self.cluster.send(node, Message::RequestPartialDecryption(RequestPartialDecryption {
|
||||||
|
session: self.id.clone(),
|
||||||
|
sub_session: self.access_key.clone(),
|
||||||
|
nodes: confirmed_nodes.clone(),
|
||||||
|
}))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(data.confirmed_nodes.remove(self.node()));
|
||||||
|
|
||||||
|
let shadow_point = {
|
||||||
|
let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryption follows initialization; qed");
|
||||||
|
do_partial_decryption(self.node(), &requestor, &data.confirmed_nodes, &self.access_key, &self.encrypted_data)?
|
||||||
|
};
|
||||||
|
data.shadow_points.insert(self.node().clone(), shadow_point);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
// we can not have enough nodes for decryption
|
||||||
|
SessionState::Failed => Ok(()),
|
||||||
|
// cannot reach other states
|
||||||
|
_ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When partial decryption is requested.
|
||||||
|
pub fn on_partial_decryption_requested(&self, sender: NodeId, message: RequestPartialDecryption) -> Result<(), Error> {
|
||||||
|
debug_assert!(self.id == message.session);
|
||||||
|
debug_assert!(self.access_key == message.sub_session);
|
||||||
|
debug_assert!(&sender != self.node());
|
||||||
|
|
||||||
|
// check message
|
||||||
|
if message.nodes.len() != self.encrypted_data.threshold + 1 {
|
||||||
|
return Err(Error::InvalidMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = self.data.lock();
|
||||||
|
|
||||||
|
// check state
|
||||||
|
if data.master != Some(sender) {
|
||||||
|
return Err(Error::InvalidMessage);
|
||||||
|
}
|
||||||
|
if data.state != SessionState::WaitingForPartialDecryptionRequest {
|
||||||
|
return Err(Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate shadow point
|
||||||
|
let shadow_point = {
|
||||||
|
let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryptionRequest follows initialization; qed");
|
||||||
|
do_partial_decryption(self.node(), &requestor, &message.nodes, &self.access_key, &self.encrypted_data)?
|
||||||
|
};
|
||||||
|
self.cluster.send(&sender, Message::PartialDecryption(PartialDecryption {
|
||||||
|
session: self.id.clone(),
|
||||||
|
sub_session: self.access_key.clone(),
|
||||||
|
shadow_point: shadow_point,
|
||||||
|
}))?;
|
||||||
|
|
||||||
|
// update sate
|
||||||
|
data.state = SessionState::Finished;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When partial decryption is received.
|
||||||
|
pub fn on_partial_decryption(&self, sender: NodeId, message: PartialDecryption) -> Result<(), Error> {
|
||||||
|
debug_assert!(self.id == message.session);
|
||||||
|
debug_assert!(self.access_key == message.sub_session);
|
||||||
|
debug_assert!(&sender != self.node());
|
||||||
|
|
||||||
|
let mut data = self.data.lock();
|
||||||
|
|
||||||
|
// check state
|
||||||
|
if data.state != SessionState::WaitingForPartialDecryption {
|
||||||
|
return Err(Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !data.confirmed_nodes.remove(&sender) {
|
||||||
|
return Err(Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
data.shadow_points.insert(sender, message.shadow_point);
|
||||||
|
|
||||||
|
// check if we have enough shadow points to decrypt the secret
|
||||||
|
if data.shadow_points.len() != self.encrypted_data.threshold + 1 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt the secret using shadow points
|
||||||
|
let joint_shadow_point = math::compute_joint_shadow_point(data.shadow_points.values())?;
|
||||||
|
let decrypted_secret = math::decrypt_with_joint_shadow(&self.access_key, &self.encrypted_data.encrypted_point, &joint_shadow_point)?;
|
||||||
|
data.decrypted_secret = Some(decrypted_secret);
|
||||||
|
data.state = SessionState::Finished;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_encrypted_data(self_node_id: &Public, encrypted_data: &EncryptedData) -> Result<(), Error> {
|
||||||
|
use key_server_cluster::encryption_session::{check_cluster_nodes, check_threshold};
|
||||||
|
|
||||||
|
let nodes = encrypted_data.id_numbers.keys().cloned().collect();
|
||||||
|
check_cluster_nodes(self_node_id, &nodes)?;
|
||||||
|
check_threshold(encrypted_data.threshold, &nodes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_initialization_response(encrypted_data: &EncryptedData, data: &mut SessionData, node: &NodeId, check_result: bool) -> Result<(), Error> {
|
||||||
|
if !data.requested_nodes.remove(node) {
|
||||||
|
return Err(Error::InvalidMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
match check_result {
|
||||||
|
true => {
|
||||||
|
data.confirmed_nodes.insert(node.clone());
|
||||||
|
|
||||||
|
// check if we have enough nodes to do a decryption?
|
||||||
|
if data.confirmed_nodes.len() == encrypted_data.threshold + 1 {
|
||||||
|
data.state = SessionState::WaitingForPartialDecryption;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
data.rejected_nodes.insert(node.clone());
|
||||||
|
|
||||||
|
// check if we still can receive enough confirmations to do a decryption?
|
||||||
|
if encrypted_data.id_numbers.len() - data.rejected_nodes.len() < encrypted_data.threshold + 1 {
|
||||||
|
data.state = SessionState::Failed;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants: &BTreeSet<NodeId>, access_key: &Secret, encrypted_data: &EncryptedData) -> Result<Public, Error> {
|
||||||
|
let node_id_number = &encrypted_data.id_numbers[node];
|
||||||
|
let node_secret_share = &encrypted_data.secret_share;
|
||||||
|
let other_id_numbers = participants.iter()
|
||||||
|
.filter(|id| *id != node)
|
||||||
|
.map(|id| &encrypted_data.id_numbers[id]);
|
||||||
|
// TODO: commutative encryption using _requestor_public
|
||||||
|
let node_shadow = math::compute_node_shadow(node_id_number, node_secret_share, other_id_numbers)?;
|
||||||
|
math::compute_node_shadow_point(access_key, &encrypted_data.common_point, &node_shadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use super::super::super::acl_storage::DummyAclStorage;
|
||||||
|
use ethkey::{self, Random, Generator, Public, Secret};
|
||||||
|
use key_server_cluster::{NodeId, EncryptedData, SessionId, Error};
|
||||||
|
use key_server_cluster::cluster::tests::DummyCluster;
|
||||||
|
use key_server_cluster::decryption_session::{Session, SessionParams, SessionState};
|
||||||
|
use key_server_cluster::message::{self, Message};
|
||||||
|
|
||||||
|
const SECRET_PLAIN: &'static str = "d2b57ae7619e070af0af6bc8c703c0cd27814c54d5d6a999cacac0da34ede279ca0d9216e85991029e54e2f0c92ee0bd30237725fa765cbdbfc4529489864c5f";
|
||||||
|
|
||||||
|
fn prepare_decryption_sessions() -> (Vec<Arc<DummyCluster>>, Vec<Arc<DummyAclStorage>>, Vec<Session>) {
|
||||||
|
// prepare encrypted data + cluster configuration for scheme 4-of-5
|
||||||
|
let session_id = SessionId::default();
|
||||||
|
let access_key = Random.generate().unwrap().secret().clone();
|
||||||
|
let secret_shares = vec![
|
||||||
|
Secret::from_str("834cb736f02d9c968dfaf0c37658a1d86ff140554fc8b59c9fdad5a8cf810eec").unwrap(),
|
||||||
|
Secret::from_str("5a3c1d90fafafa66bb808bcc464354a98b05e6b2c95b5f609d4511cdd1b17a0b").unwrap(),
|
||||||
|
Secret::from_str("71bf61e7848e08e3a8486c308ce521bdacfebcf9116a0151447eb301f3a2d0e9").unwrap(),
|
||||||
|
Secret::from_str("80c0e5e2bea66fa9b2e07f7ce09630a9563e8242446d5ee63221feb09c4338f4").unwrap(),
|
||||||
|
Secret::from_str("c06546b5669877ba579ca437a5602e89425c53808c708d44ccd6afcaa4610fad").unwrap(),
|
||||||
|
];
|
||||||
|
let id_numbers: Vec<(NodeId, Secret)> = vec![
|
||||||
|
("b486d3840218837b035c66196ecb15e6b067ca20101e11bd5e626288ab6806ecc70b8307012626bd512bad1559112d11d21025cef48cc7a1d2f3976da08f36c8".into(),
|
||||||
|
Secret::from_str("281b6bf43cb86d0dc7b98e1b7def4a80f3ce16d28d2308f934f116767306f06c").unwrap()),
|
||||||
|
("1395568277679f7f583ab7c0992da35f26cde57149ee70e524e49bdae62db3e18eb96122501e7cbb798b784395d7bb5a499edead0706638ad056d886e56cf8fb".into(),
|
||||||
|
Secret::from_str("00125d85a05e5e63e214cb60fe63f132eec8a103aa29266b7e6e6c5b7597230b").unwrap()),
|
||||||
|
("99e82b163b062d55a64085bacfd407bb55f194ba5fb7a1af9c34b84435455520f1372e0e650a4f91aed0058cb823f62146ccb5599c8d13372c300dea866b69fc".into(),
|
||||||
|
Secret::from_str("f43ac0fba42a5b6ed95707d2244659e89ba877b1c9b82c0d0a9dcf834e80fc62").unwrap()),
|
||||||
|
("7e05df9dd077ec21ed4bc45c9fe9e0a43d65fa4be540630de615ced5e95cf5c3003035eb713317237d7667feeeb64335525158f5f7411f67aca9645169ea554c".into(),
|
||||||
|
Secret::from_str("5a324938dfb2516800487d25ab7289ba8ec38811f77c3df602e4e65e3c9acd9f").unwrap()),
|
||||||
|
("321977760d1d8e15b047a309e4c7fe6f355c10bb5a06c68472b676926427f69f229024fa2692c10da167d14cdc77eb95d0fce68af0a0f704f0d3db36baa83bb2".into(),
|
||||||
|
Secret::from_str("12cf422d50002d04e52bd4906fd7f5f235f051ca36abfe37e061f8da248008d8").unwrap()),
|
||||||
|
];
|
||||||
|
let common_point: Public = "6962be696e1bcbba8e64cc7fddf140f854835354b5804f3bb95ae5a2799130371b589a131bd39699ac7174ccb35fc4342dab05331202209582fc8f3a40916ab0".into();
|
||||||
|
let encrypted_point: Public = "b07031982bde9890e12eff154765f03c56c3ab646ad47431db5dd2d742a9297679c4c65b998557f8008469afd0c43d40b6c5f6c6a1c7354875da4115237ed87a".into();
|
||||||
|
let encrypted_datas: Vec<_> = (0..5).map(|i| EncryptedData {
|
||||||
|
threshold: 3,
|
||||||
|
id_numbers: id_numbers.clone().into_iter().collect(),
|
||||||
|
secret_share: secret_shares[i].clone(),
|
||||||
|
common_point: common_point.clone(),
|
||||||
|
encrypted_point: encrypted_point.clone(),
|
||||||
|
}).collect();
|
||||||
|
let acl_storages: Vec<_> = (0..5).map(|_| Arc::new(DummyAclStorage::default())).collect();
|
||||||
|
let clusters: Vec<_> = (0..5).map(|i| Arc::new(DummyCluster::new(id_numbers.iter().nth(i).clone().unwrap().0))).collect();
|
||||||
|
let sessions: Vec<_> = (0..5).map(|i| Session::new(SessionParams {
|
||||||
|
id: session_id.clone(),
|
||||||
|
access_key: access_key.clone(),
|
||||||
|
self_node_id: id_numbers.iter().nth(i).clone().unwrap().0,
|
||||||
|
encrypted_data: encrypted_datas[i].clone(),
|
||||||
|
acl_storage: acl_storages[i].clone(),
|
||||||
|
cluster: clusters[i].clone()
|
||||||
|
}).unwrap()).collect();
|
||||||
|
|
||||||
|
(clusters, acl_storages, sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_messages_exchange(clusters: &[Arc<DummyCluster>], sessions: &[Session]) {
|
||||||
|
do_messages_exchange_until(clusters, sessions, |_, _, _| false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_messages_exchange_until<F>(clusters: &[Arc<DummyCluster>], sessions: &[Session], mut cond: F) where F: FnMut(&NodeId, &NodeId, &Message) -> bool {
|
||||||
|
while let Some((from, to, message)) = clusters.iter().filter_map(|c| c.take_message().map(|(to, msg)| (c.node(), to, msg))).next() {
|
||||||
|
let session = &sessions[sessions.iter().position(|s| s.node() == &to).unwrap()];
|
||||||
|
if cond(&from, &to, &message) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
match message {
|
||||||
|
Message::InitializeDecryptionSession(message) => session.on_initialize_session(from, message).unwrap(),
|
||||||
|
Message::ConfirmDecryptionInitialization(message) => session.on_confirm_initialization(from, message).unwrap(),
|
||||||
|
Message::RequestPartialDecryption(message) => session.on_partial_decryption_requested(from, message).unwrap(),
|
||||||
|
Message::PartialDecryption(message) => session.on_partial_decryption(from, message).unwrap(),
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_construct_in_cluster_of_single_node() {
|
||||||
|
let mut nodes = BTreeMap::new();
|
||||||
|
let self_node_id = Random.generate().unwrap().public().clone();
|
||||||
|
nodes.insert(self_node_id, Random.generate().unwrap().secret().clone());
|
||||||
|
match Session::new(SessionParams {
|
||||||
|
id: SessionId::default(),
|
||||||
|
access_key: Random.generate().unwrap().secret().clone(),
|
||||||
|
self_node_id: self_node_id.clone(),
|
||||||
|
encrypted_data: EncryptedData {
|
||||||
|
threshold: 0,
|
||||||
|
id_numbers: nodes,
|
||||||
|
secret_share: Random.generate().unwrap().secret().clone(),
|
||||||
|
common_point: Random.generate().unwrap().public().clone(),
|
||||||
|
encrypted_point: Random.generate().unwrap().public().clone(),
|
||||||
|
},
|
||||||
|
acl_storage: Arc::new(DummyAclStorage::default()),
|
||||||
|
cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
|
||||||
|
}) {
|
||||||
|
Err(Error::InvalidNodesCount) => (),
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_construct_if_not_a_part_of_cluster() {
|
||||||
|
let mut nodes = BTreeMap::new();
|
||||||
|
let self_node_id = Random.generate().unwrap().public().clone();
|
||||||
|
nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone());
|
||||||
|
nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone());
|
||||||
|
match Session::new(SessionParams {
|
||||||
|
id: SessionId::default(),
|
||||||
|
access_key: Random.generate().unwrap().secret().clone(),
|
||||||
|
self_node_id: self_node_id.clone(),
|
||||||
|
encrypted_data: EncryptedData {
|
||||||
|
threshold: 0,
|
||||||
|
id_numbers: nodes,
|
||||||
|
secret_share: Random.generate().unwrap().secret().clone(),
|
||||||
|
common_point: Random.generate().unwrap().public().clone(),
|
||||||
|
encrypted_point: Random.generate().unwrap().public().clone(),
|
||||||
|
},
|
||||||
|
acl_storage: Arc::new(DummyAclStorage::default()),
|
||||||
|
cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
|
||||||
|
}) {
|
||||||
|
Err(Error::InvalidNodesConfiguration) => (),
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_construct_if_threshold_is_wrong() {
|
||||||
|
let mut nodes = BTreeMap::new();
|
||||||
|
let self_node_id = Random.generate().unwrap().public().clone();
|
||||||
|
nodes.insert(self_node_id.clone(), Random.generate().unwrap().secret().clone());
|
||||||
|
nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone());
|
||||||
|
match Session::new(SessionParams {
|
||||||
|
id: SessionId::default(),
|
||||||
|
access_key: Random.generate().unwrap().secret().clone(),
|
||||||
|
self_node_id: self_node_id.clone(),
|
||||||
|
encrypted_data: EncryptedData {
|
||||||
|
threshold: 2,
|
||||||
|
id_numbers: nodes,
|
||||||
|
secret_share: Random.generate().unwrap().secret().clone(),
|
||||||
|
common_point: Random.generate().unwrap().public().clone(),
|
||||||
|
encrypted_point: Random.generate().unwrap().public().clone(),
|
||||||
|
},
|
||||||
|
acl_storage: Arc::new(DummyAclStorage::default()),
|
||||||
|
cluster: Arc::new(DummyCluster::new(self_node_id.clone())),
|
||||||
|
}) {
|
||||||
|
Err(Error::InvalidThreshold) => (),
|
||||||
|
_ => panic!("unexpected"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_initialize_when_already_initialized() {
|
||||||
|
let (_, _, sessions) = prepare_decryption_sessions();
|
||||||
|
assert_eq!(sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap(), ());
|
||||||
|
assert_eq!(sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap_err(), Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_accept_initialization_when_already_initialized() {
|
||||||
|
let (_, _, sessions) = prepare_decryption_sessions();
|
||||||
|
assert_eq!(sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap(), ());
|
||||||
|
assert_eq!(sessions[0].on_initialize_session(sessions[1].node().clone(), message::InitializeDecryptionSession {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(),
|
||||||
|
}).unwrap_err(), Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_partial_decrypt_if_not_waiting() {
|
||||||
|
let (_, _, sessions) = prepare_decryption_sessions();
|
||||||
|
assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(),
|
||||||
|
}).unwrap(), ());
|
||||||
|
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(),
|
||||||
|
}).unwrap(), ());
|
||||||
|
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(),
|
||||||
|
}).unwrap_err(), Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_partial_decrypt_if_requested_by_slave() {
|
||||||
|
let (_, _, sessions) = prepare_decryption_sessions();
|
||||||
|
assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(),
|
||||||
|
}).unwrap(), ());
|
||||||
|
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[2].node().clone(), message::RequestPartialDecryption {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(),
|
||||||
|
}).unwrap_err(), Error::InvalidMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_partial_decrypt_if_wrong_number_of_nodes_participating() {
|
||||||
|
let (_, _, sessions) = prepare_decryption_sessions();
|
||||||
|
assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(),
|
||||||
|
}).unwrap(), ());
|
||||||
|
assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
nodes: sessions.iter().map(|s| s.node().clone()).take(2).collect(),
|
||||||
|
}).unwrap_err(), Error::InvalidMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_accept_partial_decrypt_if_not_waiting() {
|
||||||
|
let (_, _, sessions) = prepare_decryption_sessions();
|
||||||
|
assert_eq!(sessions[0].on_partial_decryption(sessions[1].node().clone(), message::PartialDecryption {
|
||||||
|
session: SessionId::default(),
|
||||||
|
sub_session: sessions[0].access_key().clone(),
|
||||||
|
shadow_point: Random.generate().unwrap().public().clone(),
|
||||||
|
}).unwrap_err(), Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_to_accept_partial_decrypt_twice() {
|
||||||
|
let (clusters, _, sessions) = prepare_decryption_sessions();
|
||||||
|
sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap();
|
||||||
|
|
||||||
|
let mut pd_from = None;
|
||||||
|
let mut pd_msg = None;
|
||||||
|
do_messages_exchange_until(&clusters, &sessions, |from, _, msg| match msg {
|
||||||
|
&Message::PartialDecryption(ref msg) => {
|
||||||
|
pd_from = Some(from.clone());
|
||||||
|
pd_msg = Some(msg.clone());
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(sessions[0].on_partial_decryption(pd_from.clone().unwrap(), pd_msg.clone().unwrap()).unwrap(), ());
|
||||||
|
assert_eq!(sessions[0].on_partial_decryption(pd_from.unwrap(), pd_msg.unwrap()).unwrap_err(), Error::InvalidStateForRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn complete_dec_session() {
|
||||||
|
let (clusters, _, sessions) = prepare_decryption_sessions();
|
||||||
|
|
||||||
|
// now let's try to do a decryption
|
||||||
|
let key_pair = Random.generate().unwrap();
|
||||||
|
let signature = ethkey::sign(key_pair.secret(), &SessionId::default()).unwrap();
|
||||||
|
sessions[0].initialize(signature).unwrap();
|
||||||
|
|
||||||
|
do_messages_exchange(&clusters, &sessions);
|
||||||
|
|
||||||
|
// now check that:
|
||||||
|
// 1) 4 of 5 sessions are in Finished state
|
||||||
|
assert_eq!(sessions.iter().filter(|s| s.state() == SessionState::Finished).count(), 4);
|
||||||
|
// 2) 1 session is in WaitingForPartialDecryptionRequest state
|
||||||
|
assert_eq!(sessions.iter().filter(|s| s.state() == SessionState::WaitingForPartialDecryptionRequest).count(), 1);
|
||||||
|
// 3) 1 session has decrypted key value
|
||||||
|
assert!(sessions.iter().skip(1).all(|s| s.decrypted_secret().is_none()));
|
||||||
|
assert_eq!(sessions[0].decrypted_secret(), Some(SECRET_PLAIN.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_dec_session() {
|
||||||
|
let (clusters, acl_storages, sessions) = prepare_decryption_sessions();
|
||||||
|
|
||||||
|
// now let's try to do a decryption
|
||||||
|
let key_pair = Random.generate().unwrap();
|
||||||
|
let signature = ethkey::sign(key_pair.secret(), &SessionId::default()).unwrap();
|
||||||
|
sessions[0].initialize(signature).unwrap();
|
||||||
|
|
||||||
|
// we need 4 out of 5 nodes to agree to do a decryption
|
||||||
|
// let's say that 2 of these nodes are disagree
|
||||||
|
acl_storages[1].prohibit(key_pair.public().clone(), SessionId::default());
|
||||||
|
acl_storages[2].prohibit(key_pair.public().clone(), SessionId::default());
|
||||||
|
|
||||||
|
do_messages_exchange(&clusters, &sessions);
|
||||||
|
|
||||||
|
// now check that:
|
||||||
|
// 1) 3 of 5 sessions are in Failed state
|
||||||
|
assert_eq!(sessions.iter().filter(|s| s.state() == SessionState::Failed).count(), 3);
|
||||||
|
// 2) 2 of 5 sessions are in WaitingForPartialDecryptionRequest state
|
||||||
|
assert_eq!(sessions.iter().filter(|s| s.state() == SessionState::WaitingForPartialDecryptionRequest).count(), 2);
|
||||||
|
// 3) 0 sessions have decrypted key value
|
||||||
|
assert!(sessions.iter().all(|s| s.decrypted_secret().is_none()));
|
||||||
|
}
|
||||||
|
}
|
1197
secret_store/src/key_server_cluster/encryption_session.rs
Normal file
1197
secret_store/src/key_server_cluster/encryption_session.rs
Normal file
File diff suppressed because it is too large
Load Diff
338
secret_store/src/key_server_cluster/math.rs
Normal file
338
secret_store/src/key_server_cluster/math.rs
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
// 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 ethkey::{Public, Secret, Random, Generator, math};
|
||||||
|
use key_server_cluster::Error;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Encryption result.
|
||||||
|
pub struct EncryptedSecret {
|
||||||
|
/// Common encryption point.
|
||||||
|
pub common_point: Public,
|
||||||
|
/// Ecnrypted point.
|
||||||
|
pub encrypted_point: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate random scalar
|
||||||
|
pub fn generate_random_scalar() -> Result<Secret, Error> {
|
||||||
|
Ok(Random.generate()?.secret().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate random point
|
||||||
|
pub fn generate_random_point() -> Result<Public, Error> {
|
||||||
|
Ok(Random.generate()?.public().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update point by multiplying to random scalar
|
||||||
|
pub fn update_random_point(point: &mut Public) -> Result<(), Error> {
|
||||||
|
Ok(math::public_mul_secret(point, &generate_random_scalar()?)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate random polynom of threshold degree
|
||||||
|
pub fn generate_random_polynom(threshold: usize) -> Result<Vec<Secret>, Error> {
|
||||||
|
let mut polynom: Vec<_> = Vec::with_capacity(threshold + 1);
|
||||||
|
for _ in 0..threshold + 1 {
|
||||||
|
polynom.push(generate_random_scalar()?);
|
||||||
|
}
|
||||||
|
debug_assert_eq!(polynom.len(), threshold + 1);
|
||||||
|
Ok(polynom)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute value of polynom, using `node_number` as argument
|
||||||
|
pub fn compute_polynom(polynom: &[Secret], node_number: &Secret) -> Result<Secret, Error> {
|
||||||
|
debug_assert!(!polynom.is_empty());
|
||||||
|
|
||||||
|
let mut result = polynom[0].clone();
|
||||||
|
for i in 1..polynom.len() {
|
||||||
|
// calculate pow(node_number, i)
|
||||||
|
let mut appendum = node_number.clone();
|
||||||
|
appendum.pow(i)?;
|
||||||
|
|
||||||
|
// calculate coeff * pow(point, i)
|
||||||
|
appendum.mul(&polynom[i])?;
|
||||||
|
|
||||||
|
// calculate result + coeff * pow(point, i)
|
||||||
|
result.add(&appendum)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate public keys for other participants.
|
||||||
|
pub fn public_values_generation(threshold: usize, derived_point: &Public, polynom1: &[Secret], polynom2: &[Secret]) -> Result<Vec<Public>, Error> {
|
||||||
|
debug_assert_eq!(polynom1.len(), threshold + 1);
|
||||||
|
debug_assert_eq!(polynom2.len(), threshold + 1);
|
||||||
|
|
||||||
|
// compute t+1 public values
|
||||||
|
let mut publics = Vec::with_capacity(threshold + 1);
|
||||||
|
for i in 0..threshold + 1 {
|
||||||
|
let coeff1 = &polynom1[i];
|
||||||
|
|
||||||
|
let mut multiplication1 = math::generation_point();
|
||||||
|
math::public_mul_secret(&mut multiplication1, &coeff1)?;
|
||||||
|
|
||||||
|
let coeff2 = &polynom2[i];
|
||||||
|
let mut multiplication2 = derived_point.clone();
|
||||||
|
math::public_mul_secret(&mut multiplication2, &coeff2)?;
|
||||||
|
|
||||||
|
math::public_add(&mut multiplication1, &multiplication2)?;
|
||||||
|
|
||||||
|
publics.push(multiplication1);
|
||||||
|
}
|
||||||
|
debug_assert_eq!(publics.len(), threshold + 1);
|
||||||
|
|
||||||
|
Ok(publics)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check keys passed by other participants.
|
||||||
|
pub fn keys_verification(threshold: usize, derived_point: &Public, number_id: &Secret, secret1: &Secret, secret2: &Secret, publics: &[Public]) -> Result<bool, Error> {
|
||||||
|
// calculate left part
|
||||||
|
let mut multiplication1 = math::generation_point();
|
||||||
|
math::public_mul_secret(&mut multiplication1, secret1)?;
|
||||||
|
|
||||||
|
let mut multiplication2 = derived_point.clone();
|
||||||
|
math::public_mul_secret(&mut multiplication2, secret2)?;
|
||||||
|
|
||||||
|
math::public_add(&mut multiplication1, &multiplication2)?;
|
||||||
|
let left = multiplication1;
|
||||||
|
|
||||||
|
// calculate right part
|
||||||
|
let mut right = publics[0].clone();
|
||||||
|
for i in 1..threshold + 1 {
|
||||||
|
let mut secret_pow = number_id.clone();
|
||||||
|
secret_pow.pow(i)?;
|
||||||
|
|
||||||
|
let mut public_k = publics[i].clone();
|
||||||
|
math::public_mul_secret(&mut public_k, &secret_pow)?;
|
||||||
|
|
||||||
|
math::public_add(&mut right, &public_k)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(left == right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute secret share.
|
||||||
|
pub fn compute_secret_share<'a, I>(mut secret_values: I) -> Result<Secret, Error> where I: Iterator<Item=&'a Secret> {
|
||||||
|
let mut secret_share = secret_values.next().expect("compute_secret_share is called when cluster has at least one node; qed").clone();
|
||||||
|
while let Some(secret_value) = secret_values.next() {
|
||||||
|
secret_share.add(secret_value)?;
|
||||||
|
}
|
||||||
|
Ok(secret_share)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute public key share.
|
||||||
|
pub fn compute_public_share(self_secret_value: &Secret) -> Result<Public, Error> {
|
||||||
|
let mut public_share = math::generation_point();
|
||||||
|
math::public_mul_secret(&mut public_share, self_secret_value)?;
|
||||||
|
Ok(public_share)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute joint public key.
|
||||||
|
pub fn compute_joint_public<'a, I>(mut public_shares: I) -> Result<Public, Error> where I: Iterator<Item=&'a Public> {
|
||||||
|
let mut joint_public = public_shares.next().expect("compute_joint_public is called when cluster has at least one node; qed").clone();
|
||||||
|
while let Some(public_share) = public_shares.next() {
|
||||||
|
math::public_add(&mut joint_public, &public_share)?;
|
||||||
|
}
|
||||||
|
Ok(joint_public)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Compute joint secret key.
|
||||||
|
pub fn compute_joint_secret<'a, I>(mut secret_coeffs: I) -> Result<Secret, Error> where I: Iterator<Item=&'a Secret> {
|
||||||
|
let mut joint_secret = secret_coeffs.next().expect("compute_joint_private is called when cluster has at least one node; qed").clone();
|
||||||
|
while let Some(secret_coeff) = secret_coeffs.next() {
|
||||||
|
joint_secret.add(secret_coeff)?;
|
||||||
|
}
|
||||||
|
Ok(joint_secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt secret with joint public key.
|
||||||
|
pub fn encrypt_secret(secret: Public, joint_public: &Public) -> Result<EncryptedSecret, Error> {
|
||||||
|
// this is performed by KS-cluster client (or KS master)
|
||||||
|
let key_pair = Random.generate()?;
|
||||||
|
|
||||||
|
// k * T
|
||||||
|
let mut common_point = math::generation_point();
|
||||||
|
math::public_mul_secret(&mut common_point, key_pair.secret())?;
|
||||||
|
|
||||||
|
// M + k * y
|
||||||
|
let mut encrypted_point = joint_public.clone();
|
||||||
|
math::public_mul_secret(&mut encrypted_point, key_pair.secret())?;
|
||||||
|
math::public_add(&mut encrypted_point, &secret)?;
|
||||||
|
|
||||||
|
Ok(EncryptedSecret {
|
||||||
|
common_point: common_point,
|
||||||
|
encrypted_point: encrypted_point,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute shadow for the node.
|
||||||
|
pub fn compute_node_shadow<'a, I>(node_number: &Secret, node_secret_share: &Secret, mut other_nodes_numbers: I) -> Result<Secret, Error> where I: Iterator<Item=&'a Secret> {
|
||||||
|
let other_node_number = other_nodes_numbers.next().expect("compute_node_shadow is called when at least two nodes are required to decrypt secret; qed");
|
||||||
|
let mut shadow = node_number.clone();
|
||||||
|
shadow.sub(other_node_number)?;
|
||||||
|
shadow.inv()?;
|
||||||
|
shadow.mul(other_node_number)?;
|
||||||
|
while let Some(other_node_number) = other_nodes_numbers.next() {
|
||||||
|
let mut shadow_element = node_number.clone();
|
||||||
|
shadow_element.sub(other_node_number)?;
|
||||||
|
shadow_element.inv()?;
|
||||||
|
shadow_element.mul(other_node_number)?;
|
||||||
|
shadow.mul(&shadow_element)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
shadow.mul(&node_secret_share)?;
|
||||||
|
Ok(shadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute shadow point for the node.
|
||||||
|
pub fn compute_node_shadow_point(access_key: &Secret, common_point: &Public, node_shadow: &Secret) -> Result<Public, Error> {
|
||||||
|
let mut shadow_key = access_key.clone();
|
||||||
|
shadow_key.mul(node_shadow)?;
|
||||||
|
let mut node_shadow_point = common_point.clone();
|
||||||
|
math::public_mul_secret(&mut node_shadow_point, &shadow_key)?;
|
||||||
|
Ok(node_shadow_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute joint shadow point.
|
||||||
|
pub fn compute_joint_shadow_point<'a, I>(mut nodes_shadow_points: I) -> Result<Public, Error> where I: Iterator<Item=&'a Public> {
|
||||||
|
let mut joint_shadow_point = nodes_shadow_points.next().expect("compute_joint_shadow_point is called when at least two nodes are required to decrypt secret; qed").clone();
|
||||||
|
while let Some(node_shadow_point) = nodes_shadow_points.next() {
|
||||||
|
math::public_add(&mut joint_shadow_point, &node_shadow_point)?;
|
||||||
|
}
|
||||||
|
Ok(joint_shadow_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Compute joint shadow point (version for tests).
|
||||||
|
pub fn compute_joint_shadow_point_test<'a, I>(access_key: &Secret, common_point: &Public, mut nodes_shadows: I) -> Result<Public, Error> where I: Iterator<Item=&'a Secret> {
|
||||||
|
let mut joint_shadow = nodes_shadows.next().expect("compute_joint_shadow_point_test is called when at least two nodes are required to decrypt secret; qed").clone();
|
||||||
|
while let Some(node_shadow) = nodes_shadows.next() {
|
||||||
|
joint_shadow.add(node_shadow)?;
|
||||||
|
}
|
||||||
|
joint_shadow.mul(access_key)?;
|
||||||
|
|
||||||
|
let mut joint_shadow_point = common_point.clone();
|
||||||
|
math::public_mul_secret(&mut joint_shadow_point, &joint_shadow)?;
|
||||||
|
Ok(joint_shadow_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt data using joint shadow point.
|
||||||
|
pub fn decrypt_with_joint_shadow(access_key: &Secret, encrypted_point: &Public, joint_shadow_point: &Public) -> Result<Public, Error> {
|
||||||
|
let mut inv_access_key = access_key.clone();
|
||||||
|
inv_access_key.inv()?;
|
||||||
|
|
||||||
|
let mut decrypted_point = joint_shadow_point.clone();
|
||||||
|
math::public_mul_secret(&mut decrypted_point, &inv_access_key)?;
|
||||||
|
math::public_add(&mut decrypted_point, encrypted_point)?;
|
||||||
|
|
||||||
|
Ok(decrypted_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt data using joint secret (version for tests).
|
||||||
|
pub fn decrypt_with_joint_secret(encrypted_point: &Public, common_point: &Public, joint_secret: &Secret) -> Result<Public, Error> {
|
||||||
|
let mut common_point_mul = common_point.clone();
|
||||||
|
math::public_mul_secret(&mut common_point_mul, joint_secret)?;
|
||||||
|
|
||||||
|
let mut decrypted_point = encrypted_point.clone();
|
||||||
|
math::public_sub(&mut decrypted_point, &common_point_mul)?;
|
||||||
|
|
||||||
|
Ok(decrypted_point)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use ethkey::KeyPair;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn do_encryption_and_decryption(t: usize, joint_public: &Public, id_numbers: &[Secret], secret_shares: &[Secret], joint_secret: Option<&Secret>, document_secret_plain: Public) -> (Public, Public) {
|
||||||
|
// === PART2: encryption using joint public key ===
|
||||||
|
|
||||||
|
// the next line is executed on KeyServer-client
|
||||||
|
let encrypted_secret = encrypt_secret(document_secret_plain.clone(), &joint_public).unwrap();
|
||||||
|
|
||||||
|
// === PART3: decryption ===
|
||||||
|
|
||||||
|
// next line is executed on KeyServer client
|
||||||
|
let access_key = generate_random_scalar().unwrap();
|
||||||
|
|
||||||
|
// use t + 1 nodes to compute joint shadow point
|
||||||
|
let nodes_shadows: Vec<_> = (0..t + 1).map(|i|
|
||||||
|
compute_node_shadow(&id_numbers[i], &secret_shares[i], id_numbers.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|&(j, _)| j != i)
|
||||||
|
.take(t)
|
||||||
|
.map(|(_, id_number)| id_number)).unwrap()).collect();
|
||||||
|
let nodes_shadow_points: Vec<_> = nodes_shadows.iter().map(|s| compute_node_shadow_point(&access_key, &encrypted_secret.common_point, s).unwrap()).collect();
|
||||||
|
assert_eq!(nodes_shadows.len(), t + 1);
|
||||||
|
assert_eq!(nodes_shadow_points.len(), t + 1);
|
||||||
|
|
||||||
|
let joint_shadow_point = compute_joint_shadow_point(nodes_shadow_points.iter()).unwrap();
|
||||||
|
let joint_shadow_point_test = compute_joint_shadow_point_test(&access_key, &encrypted_secret.common_point, nodes_shadows.iter()).unwrap();
|
||||||
|
assert_eq!(joint_shadow_point, joint_shadow_point_test);
|
||||||
|
|
||||||
|
// decrypt encrypted secret using joint shadow point
|
||||||
|
let document_secret_decrypted = decrypt_with_joint_shadow(&access_key, &encrypted_secret.encrypted_point, &joint_shadow_point).unwrap();
|
||||||
|
|
||||||
|
// decrypt encrypted secret using joint secret [just for test]
|
||||||
|
let document_secret_decrypted_test = match joint_secret {
|
||||||
|
Some(joint_secret) => decrypt_with_joint_secret(&encrypted_secret.encrypted_point, &encrypted_secret.common_point, joint_secret).unwrap(),
|
||||||
|
None => document_secret_decrypted.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(document_secret_decrypted, document_secret_decrypted_test)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_encryption_math_session() {
|
||||||
|
let test_cases = [(1, 3)];
|
||||||
|
for &(t, n) in &test_cases {
|
||||||
|
// === PART1: DKG ===
|
||||||
|
|
||||||
|
// data, gathered during initialization
|
||||||
|
let id_numbers: Vec<_> = (0..n).map(|_| generate_random_scalar().unwrap()).collect();
|
||||||
|
|
||||||
|
// data, generated during keys dissemination
|
||||||
|
let polynoms1: Vec<_> = (0..n).map(|_| generate_random_polynom(t).unwrap()).collect();
|
||||||
|
let secrets1: Vec<_> = (0..n).map(|i| (0..n).map(|j| compute_polynom(&polynoms1[i], &id_numbers[j]).unwrap()).collect::<Vec<_>>()).collect();
|
||||||
|
|
||||||
|
// data, generated during keys generation
|
||||||
|
let public_shares: Vec<_> = (0..n).map(|i| compute_public_share(&polynoms1[i][0]).unwrap()).collect();
|
||||||
|
let secret_shares: Vec<_> = (0..n).map(|i| compute_secret_share(secrets1.iter().map(|s| &s[i])).unwrap()).collect();
|
||||||
|
|
||||||
|
// joint public key, as a result of DKG
|
||||||
|
let joint_public = compute_joint_public(public_shares.iter()).unwrap();
|
||||||
|
|
||||||
|
// compute joint private key [just for test]
|
||||||
|
let joint_secret = compute_joint_secret(polynoms1.iter().map(|p| &p[0])).unwrap();
|
||||||
|
let joint_key_pair = KeyPair::from_secret(joint_secret.clone()).unwrap();
|
||||||
|
assert_eq!(&joint_public, joint_key_pair.public());
|
||||||
|
|
||||||
|
// check secret shares computation [just for test]
|
||||||
|
let secret_shares_polynom: Vec<_> = (0..t + 1).map(|k| compute_secret_share(polynoms1.iter().map(|p| &p[k])).unwrap()).collect();
|
||||||
|
let secret_shares_calculated_from_polynom: Vec<_> = id_numbers.iter().map(|id_number| compute_polynom(&*secret_shares_polynom, id_number).unwrap()).collect();
|
||||||
|
assert_eq!(secret_shares, secret_shares_calculated_from_polynom);
|
||||||
|
|
||||||
|
// now encrypt and decrypt data
|
||||||
|
let document_secret_plain = generate_random_point().unwrap();
|
||||||
|
let (document_secret_decrypted, document_secret_decrypted_test) =
|
||||||
|
do_encryption_and_decryption(t, &joint_public, &id_numbers, &secret_shares, Some(&joint_secret), document_secret_plain.clone());
|
||||||
|
|
||||||
|
assert_eq!(document_secret_plain, document_secret_decrypted_test);
|
||||||
|
assert_eq!(document_secret_plain, document_secret_decrypted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
secret_store/src/key_server_cluster/message.rs
Normal file
168
secret_store/src/key_server_cluster/message.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// 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::collections::{BTreeSet, BTreeMap};
|
||||||
|
use ethkey::{Public, Secret, Signature};
|
||||||
|
use key_server_cluster::{NodeId, SessionId};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// All possible messages that can be sent during DKG.
|
||||||
|
pub enum Message {
|
||||||
|
/// Initialize new DKG session.
|
||||||
|
InitializeSession(InitializeSession),
|
||||||
|
/// Confirm DKG session initialization.
|
||||||
|
ConfirmInitialization(ConfirmInitialization),
|
||||||
|
/// Broadcast data, calculated during session initialization phase.
|
||||||
|
CompleteInitialization(CompleteInitialization),
|
||||||
|
/// Generated keys are sent to every node.
|
||||||
|
KeysDissemination(KeysDissemination),
|
||||||
|
/// Complaint against another node is broadcasted.
|
||||||
|
Complaint(Complaint),
|
||||||
|
/// Complaint response is broadcasted.
|
||||||
|
ComplaintResponse(ComplaintResponse),
|
||||||
|
/// Broadcast self public key portion.
|
||||||
|
PublicKeyShare(PublicKeyShare),
|
||||||
|
|
||||||
|
/// Initialize decryption session.
|
||||||
|
InitializeDecryptionSession(InitializeDecryptionSession),
|
||||||
|
/// Confirm/reject decryption session initialization.
|
||||||
|
ConfirmDecryptionInitialization(ConfirmDecryptionInitialization),
|
||||||
|
/// Request partial decryption from node.
|
||||||
|
RequestPartialDecryption(RequestPartialDecryption),
|
||||||
|
/// Partial decryption is completed
|
||||||
|
PartialDecryption(PartialDecryption),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Initialize new DKG session.
|
||||||
|
pub struct InitializeSession {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Derived generation point. Starting from originator, every node must multiply this
|
||||||
|
/// point by random scalar (unknown by other nodes). At the end of initialization
|
||||||
|
/// `point` will be some (k1 * k2 * ... * kn) * G = `point` where `(k1 * k2 * ... * kn)`
|
||||||
|
/// is unknown for every node.
|
||||||
|
pub derived_point: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Confirm DKG session initialization.
|
||||||
|
pub struct ConfirmInitialization {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Derived generation point.
|
||||||
|
pub derived_point: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Broadcast generated point to every other node.
|
||||||
|
pub struct CompleteInitialization {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// All session participants along with their identification numbers.
|
||||||
|
pub nodes: BTreeMap<NodeId, Secret>,
|
||||||
|
/// Decryption threshold. During decryption threshold-of-route.len() nodes must came to
|
||||||
|
/// consensus to successfully decrypt message.
|
||||||
|
pub threshold: usize,
|
||||||
|
/// Derived generation point.
|
||||||
|
pub derived_point: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Generated keys are sent to every node.
|
||||||
|
pub struct KeysDissemination {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Secret 1.
|
||||||
|
pub secret1: Secret,
|
||||||
|
/// Secret 2.
|
||||||
|
pub secret2: Secret,
|
||||||
|
/// Public values.
|
||||||
|
pub publics: Vec<Public>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Complaint against node is broadcasted.
|
||||||
|
pub struct Complaint {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Public values.
|
||||||
|
pub against: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Node is responding to complaint.
|
||||||
|
pub struct ComplaintResponse {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Secret 1.
|
||||||
|
pub secret1: Secret,
|
||||||
|
/// Secret 2.
|
||||||
|
pub secret2: Secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Node is sharing its public key share.
|
||||||
|
pub struct PublicKeyShare {
|
||||||
|
/// Session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Public key share.
|
||||||
|
pub public_share: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Node is requested to decrypt data, encrypted in given session.
|
||||||
|
pub struct InitializeDecryptionSession {
|
||||||
|
/// Encryption session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Decryption session Id.
|
||||||
|
pub sub_session: Secret,
|
||||||
|
/// Requestor signature.
|
||||||
|
pub requestor_signature: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Node is responding to decryption request.
|
||||||
|
pub struct ConfirmDecryptionInitialization {
|
||||||
|
/// Encryption session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Decryption session Id.
|
||||||
|
pub sub_session: Secret,
|
||||||
|
/// Is node confirmed to make a decryption?.
|
||||||
|
pub is_confirmed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Node is requested to do a partial decryption.
|
||||||
|
pub struct RequestPartialDecryption {
|
||||||
|
/// Encryption session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Decryption session Id.
|
||||||
|
pub sub_session: Secret,
|
||||||
|
/// Nodes that are agreed to do a decryption.
|
||||||
|
pub nodes: BTreeSet<NodeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Node has partially decrypted the secret.
|
||||||
|
pub struct PartialDecryption {
|
||||||
|
/// Encryption session Id.
|
||||||
|
pub session: SessionId,
|
||||||
|
/// Decryption session Id.
|
||||||
|
pub sub_session: Secret,
|
||||||
|
/// Partially decrypted secret.
|
||||||
|
pub shadow_point: Public,
|
||||||
|
}
|
76
secret_store/src/key_server_cluster/mod.rs
Normal file
76
secret_store/src/key_server_cluster/mod.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
#![allow(dead_code)] // TODO: remove me
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use ethkey::{self, Public, Secret, Signature};
|
||||||
|
use super::types::all::DocumentAddress;
|
||||||
|
|
||||||
|
pub use super::acl_storage::AclStorage;
|
||||||
|
|
||||||
|
pub type NodeId = Public;
|
||||||
|
pub type SessionId = DocumentAddress;
|
||||||
|
pub type SessionIdSignature = Signature;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
/// Errors which can occur during encryption/decryption session
|
||||||
|
pub enum Error {
|
||||||
|
/// Invalid number of nodes.
|
||||||
|
/// There must be at least two nodes participating in encryption.
|
||||||
|
/// There must be at least one node participating in decryption.
|
||||||
|
InvalidNodesCount,
|
||||||
|
/// Node which is required to start encryption/decryption session is not a part of cluster.
|
||||||
|
InvalidNodesConfiguration,
|
||||||
|
/// Invalid threshold value has been passed.
|
||||||
|
/// Threshold value must be in [0; n - 1], where n is a number of nodes participating in the encryption.
|
||||||
|
InvalidThreshold,
|
||||||
|
/// Current state of encryption/decryption session does not allow to proceed request.
|
||||||
|
/// This means that either there is some comm-failure or node is misbehaving/cheating.
|
||||||
|
InvalidStateForRequest,
|
||||||
|
/// Some data in passed message was recognized as invalid.
|
||||||
|
/// This means that node is misbehaving/cheating.
|
||||||
|
InvalidMessage,
|
||||||
|
/// Cryptographic error.
|
||||||
|
EthKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
/// Data, which is stored on every node after DKG && encryption is completed.
|
||||||
|
pub struct EncryptedData {
|
||||||
|
/// Decryption threshold (at least threshold + 1 nodes are required to decrypt data).
|
||||||
|
threshold: usize,
|
||||||
|
/// Nodes ids numbers.
|
||||||
|
id_numbers: BTreeMap<NodeId, Secret>,
|
||||||
|
/// Node secret share.
|
||||||
|
secret_share: Secret,
|
||||||
|
/// Common (shared) encryption point.
|
||||||
|
common_point: Public,
|
||||||
|
/// Encrypted point.
|
||||||
|
encrypted_point: Public,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ethkey::Error> for Error {
|
||||||
|
fn from(err: ethkey::Error) -> Self {
|
||||||
|
Error::EthKey(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cluster;
|
||||||
|
mod decryption_session;
|
||||||
|
mod encryption_session;
|
||||||
|
mod math;
|
||||||
|
mod message;
|
@ -26,6 +26,7 @@ extern crate ethcore_ipc as ipc;
|
|||||||
extern crate ethcrypto;
|
extern crate ethcrypto;
|
||||||
extern crate ethkey;
|
extern crate ethkey;
|
||||||
|
|
||||||
|
mod key_server_cluster;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
mod traits {
|
mod traits {
|
||||||
|
Loading…
Reference in New Issue
Block a user