diff --git a/Cargo.toml b/Cargo.toml index a99b60c..d991789 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,10 @@ license = "MIT" [dependencies] bincode = "1.2.0" byteorder = "1.3.2" -failure = "0.1.6" hex_fmt = "0.3" rand = "0.6.5" serde = { version = "1.0.102", features = ["derive", "rc"] } tiny-keccak = { version = "2.0.1", features = ["sha3"]} -bls = { package = "blsttc", version = "6.0.0" } \ No newline at end of file +thiserror = "1.0.31" +bls = { package = "blsttc", version = "6.0.0" } +log = "0.4.13" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a07cb94 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,38 @@ +// Copyright 2022 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use crate::sdkg::Error as DkgError; +use thiserror::Error; + +/// All the Dkg Errors +#[derive(Error, Debug)] +pub enum Error { + /// SDKG faults and errors + #[error("Dkg Error")] + Sdkg(#[from] DkgError), + /// Encoding + #[error("Failed to encode with bincode")] + Encoding(#[from] bincode::Error), + /// Invalid DkgState init input parameters + #[error("Failed to initialize DkgState: secret key is not in provided pub key set")] + NotInPubKeySet, + /// Invalid signature + #[error("Invalid signature")] + InvalidSignature, + /// Got vote from an unknown sender id + #[error("Unknown sender")] + UnknownSender, + /// Got a faulty vote, some nodes are dishonest or dysfunctional + #[error("Faulty vote {0}")] + FaultyVote(String), + /// Unexpectedly failed to generate secret key share + #[error("Unexpectedly failed to generate secret key share")] + FailedToGenerateSecretKeyShare, +} + +pub type Result = std::result::Result; diff --git a/src/knowledge.rs b/src/knowledge.rs new file mode 100644 index 0000000..e37afe0 --- /dev/null +++ b/src/knowledge.rs @@ -0,0 +1,96 @@ +// Copyright 2022 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use log::debug; +use std::collections::{BTreeMap, BTreeSet}; +use thiserror::Error; + +use crate::vote::{DkgVote, IdAck, IdPart, NodeId}; + +pub(crate) struct Knowledge { + pub parts: BTreeSet, + pub part_acks: BTreeMap>, + pub agreed_with_all_acks: BTreeSet, +} + +#[derive(Error, Debug)] +pub(crate) enum KnowledgeFault { + #[error("Missing Parts to deal with Acks in received vote")] + MissingParts, + #[error("Parts in received vote differ from ours")] + IncompatibleParts, + #[error("Missing Acks to deal with AllAcks in received vote")] + MissingAcks, + #[error("Acks received in vote differ from ours")] + IncompatibleAcks, +} + +impl Knowledge { + fn new() -> Self { + Knowledge { + parts: BTreeSet::new(), + part_acks: BTreeMap::new(), + agreed_with_all_acks: BTreeSet::new(), + } + } + + pub fn from_votes(mut votes: Vec<(DkgVote, NodeId)>) -> Result { + votes.sort(); + let mut knowledge = Knowledge::new(); + for (vote, voter_id) in votes { + knowledge.handle_vote(vote, voter_id)?; + } + + Ok(knowledge) + } + + pub fn got_all_acks(&self, num_participants: usize) -> bool { + self.part_acks.len() == num_participants + && self + .part_acks + .iter() + .all(|(_, a)| a.len() == num_participants) + } + + fn handle_vote(&mut self, vote: DkgVote, id: NodeId) -> Result<(), KnowledgeFault> { + match vote { + DkgVote::SinglePart(part) => { + self.parts.insert((id, part)); + } + DkgVote::SingleAck(acked_parts) => { + let parts: BTreeSet<_> = acked_parts.keys().cloned().collect(); + if self.parts.len() < parts.len() { + return Err(KnowledgeFault::MissingParts); + } else if self.parts != parts { + debug!( + "IncompatibleParts: ours: {:?}, theirs: {:?}", + self.parts, parts + ); + return Err(KnowledgeFault::IncompatibleParts); + } + for (id_part, ack) in acked_parts { + let acks = self.part_acks.entry(id_part).or_default(); + acks.insert((id, ack)); + } + } + DkgVote::AllAcks(all_acks) => { + if !self.got_all_acks(self.parts.len()) { + return Err(KnowledgeFault::MissingAcks); + } else if all_acks != self.part_acks { + debug!( + "IncompatibleAcks: ours: {:?}, theirs: {:?}", + self.part_acks, all_acks + ); + return Err(KnowledgeFault::IncompatibleAcks); + } + self.agreed_with_all_acks.insert(id); + } + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 78a2903..2878d32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,16 +5,13 @@ // under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -// -// The following code is based on hbbft : https://github.com/poanetwork/hbbft -// -// hbbft is copyright 2018, POA Networks, Ltd. -// Licensed under the Apache License, Version 2.0 or the MIT license , at your option. All files in the project -// carrying such notice may not be copied, modified, or distributed except -// according to those terms. -mod sdkg; +pub(crate) mod error; +pub(crate) mod knowledge; +pub mod sdkg; +mod state; +pub(crate) mod vote; -pub use sdkg::{to_pub_keys, AckOutcome, PartOutcome, SyncKeyGen}; +pub use error::Error; +pub use state::{DkgState, VoteResponse}; +pub use vote::DkgSignedVote; diff --git a/src/sdkg.rs b/src/sdkg.rs index 411510c..bea1479 100644 --- a/src/sdkg.rs +++ b/src/sdkg.rs @@ -77,7 +77,6 @@ use bls::{ serde_impl::FieldWrap, Ciphertext, Fr, G1Affine, PublicKey, PublicKeySet, SecretKey, SecretKeyShare, }; -use failure::Fail; use serde::{Deserialize, Serialize}; use std::{ borrow::Borrow, @@ -85,15 +84,15 @@ use std::{ fmt::{self, Debug, Formatter}, hash::Hash, ops::{AddAssign, Mul}, - sync::Arc, }; +use thiserror::Error; /// A peer node's unique identifier. pub trait NodeIdT: Eq + Ord + Clone + Debug + Hash + Send + Sync {} impl NodeIdT for N where N: Eq + Ord + Clone + Debug + Hash + Send + Sync {} -/// A map assigning to each node ID a public key, wrapped in an `Arc`. -pub type PubKeyMap = Arc>; +/// A map assigning to each node ID a public key +pub type PubKeyMap = BTreeMap; /// Returns a `PubKeyMap` corresponding to the given secret keys. /// @@ -104,27 +103,27 @@ where I: IntoIterator, { let to_pub = |(id, sk): I::Item| (id.borrow().clone(), sk.public_key()); - Arc::new(sec_keys.into_iter().map(to_pub).collect()) + sec_keys.into_iter().map(to_pub).collect() } /// A local error while handling an `Ack` or `Part` message, that was not caused by that message /// being invalid. -#[derive(Clone, PartialEq, Debug, Fail)] +#[derive(Clone, PartialEq, Debug, Error)] pub enum Error { /// Error creating `SyncKeyGen`. - #[fail(display = "Error creating SyncKeyGen: {}", _0)] + #[error("Error creating SyncKeyGen: {0}")] Creation(CryptoError), /// Error generating keys. - #[fail(display = "Error generating keys: {}", _0)] + #[error("Error generating keys: {0}")] Generation(CryptoError), /// Unknown sender. - #[fail(display = "Unknown sender")] + #[error("Unknown sender")] UnknownSender, /// Failed to serialize message. - #[fail(display = "Serialization error: {}", _0)] + #[error("Serialization error: {0}")] Serialize(String), /// Failed to encrypt message parts for a peer. - #[fail(display = "Encryption error: {}", _0)] + #[error("Encryption error: {0}")] Encrypt(String), } @@ -140,7 +139,7 @@ impl From for Error { /// The message contains a commitment to a bivariate polynomial, and for each node, an encrypted /// row of values. If this message receives enough `Ack`s, it will be used as summand to produce /// the the key set in the end. -#[derive(Deserialize, Serialize, Clone, Hash, Eq, PartialEq)] +#[derive(Deserialize, Serialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct Part(BivarCommitment, Vec); impl Debug for Part { @@ -157,7 +156,7 @@ impl Debug for Part { /// /// The message is only produced after we verified our row against the commitment in the `Part`. /// For each node, it contains one encrypted value of that row. -#[derive(Deserialize, Serialize, Clone, Hash, Eq, PartialEq)] +#[derive(Deserialize, Serialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct Ack(u64, Vec); impl Debug for Ack { @@ -462,42 +461,42 @@ impl SyncKeyGen { } /// An error in an `Ack` message sent by a faulty node. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Fail)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)] pub enum AckFault { /// The number of values differs from the number of nodes. - #[fail(display = "The number of values differs from the number of nodes")] + #[error("The number of values differs from the number of nodes")] ValueCount, /// No corresponding Part received. - #[fail(display = "No corresponding Part received")] + #[error("No corresponding Part received")] MissingPart, /// Value decryption failed. - #[fail(display = "Value decryption failed")] + #[error("Value decryption failed")] DecryptValue, /// Value deserialization failed. - #[fail(display = "Value deserialization failed")] + #[error("Value deserialization failed")] DeserializeValue, /// Value doesn't match the commitment. - #[fail(display = "Value doesn't match the commitment")] + #[error("Value doesn't match the commitment")] ValueCommitment, } /// An error in a `Part` message sent by a faulty node. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Fail)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Error)] pub enum PartFault { /// The number of rows differs from the number of nodes. - #[fail(display = "The number of rows differs from the number of nodes")] + #[error("The number of rows differs from the number of nodes")] RowCount, /// Received multiple different Part messages from the same sender. - #[fail(display = "Received multiple different Part messages from the same sender")] + #[error("Received multiple different Part messages from the same sender")] MultipleParts, /// Could not decrypt our row in the Part message. - #[fail(display = "Could not decrypt our row in the Part message")] + #[error("Could not decrypt our row in the Part message")] DecryptRow, /// Could not deserialize our row in the Part message. - #[fail(display = "Could not deserialize our row in the Part message")] + #[error("Could not deserialize our row in the Part message")] DeserializeRow, /// Row does not match the commitment. - #[fail(display = "Row does not match the commitment")] + #[error("Row does not match the commitment")] RowCommitment, } @@ -506,7 +505,6 @@ mod tests { use super::{AckOutcome, PartOutcome, SyncKeyGen}; use bls::{PublicKey, SecretKey, SignatureShare}; use std::collections::BTreeMap; - use std::sync::Arc; #[test] fn test_dkg() { @@ -531,10 +529,9 @@ mod tests { let mut parts = Vec::new(); for (id, sk) in sec_keys.into_iter().enumerate() { let (sync_key_gen, opt_part) = - SyncKeyGen::new(id, sk, Arc::new(pub_keys.clone()), threshold, &mut rng) - .unwrap_or_else(|_| { - panic!("Failed to create `SyncKeyGen` instance for node #{}", id) - }); + SyncKeyGen::new(id, sk, pub_keys.clone(), threshold, &mut rng).unwrap_or_else( + |_| panic!("Failed to create `SyncKeyGen` instance for node #{}", id), + ); nodes.insert(id, sync_key_gen); parts.push((id, opt_part.unwrap())); // Would be `None` for observer nodes. } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..21a0e32 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,211 @@ +// Copyright 2022 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use bls::{PublicKey, PublicKeySet, SecretKey, SecretKeyShare}; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::error::{Error, Result}; +use crate::knowledge::{Knowledge, KnowledgeFault}; +use crate::sdkg::{AckOutcome, Part, PartOutcome, SyncKeyGen}; +use crate::vote::{DkgSignedVote, DkgVote, IdAck, IdPart, NodeId}; + +/// State of the Dkg session, contains the sync keygen and currently known Parts and Acks +/// Can handle votes coming from other participants +pub struct DkgState { + id: NodeId, + secret_key: SecretKey, + pub_keys: BTreeMap, + keygen: SyncKeyGen, + our_part: Part, + all_votes: BTreeSet, + rng: R, +} + +pub enum VoteResponse { + WaitingForMoreVotes, + BroadcastVote(Box), + RequestAntiEntropy, + AntiEntropy(BTreeSet), + DkgComplete(PublicKeySet, SecretKeyShare), +} + +enum DkgCurrentState { + IncompatibleVotes, + NeedAntiEntropy, + Termination(BTreeMap>), + WaitingForTotalAgreement, + GotAllAcks(BTreeMap>), + WaitingForMoreAcks, + GotAllParts(BTreeSet), + WaitingForMoreParts, +} + +impl DkgState { + pub fn new( + our_id: NodeId, + secret_key: SecretKey, + pub_keys: BTreeMap, + threshold: usize, + rng: &mut R, + ) -> Result { + let (sync_key_gen, opt_part) = + SyncKeyGen::new(our_id, secret_key.clone(), pub_keys.clone(), threshold, rng)?; + Ok(DkgState { + id: our_id, + secret_key, + pub_keys, + keygen: sync_key_gen, + all_votes: BTreeSet::new(), + our_part: opt_part.ok_or(Error::NotInPubKeySet)?, + rng: rng.clone(), + }) + } + + /// The 1st vote with our Part + pub fn first_vote(&mut self) -> Result { + let vote = DkgVote::SinglePart(self.our_part.clone()); + self.cast_vote(vote) + } + + fn get_validated_vote(&self, vote: &DkgSignedVote) -> Result { + let sender_id = vote.voter; + let sender_pub_key = self.pub_keys.get(&sender_id).ok_or(Error::UnknownSender)?; + let vote = vote.get_validated_vote(sender_pub_key)?; + Ok(vote) + } + + fn all_checked_votes(&self) -> Result> { + self.all_votes + .iter() + .map(|v| Ok((self.get_validated_vote(v)?, v.voter))) + .collect() + } + + fn current_dkg_state(&self, votes: Vec<(DkgVote, NodeId)>) -> DkgCurrentState { + let knowledge = match Knowledge::from_votes(votes) { + Err(KnowledgeFault::IncompatibleAcks) | Err(KnowledgeFault::IncompatibleParts) => { + return DkgCurrentState::IncompatibleVotes; + } + Err(KnowledgeFault::MissingParts) | Err(KnowledgeFault::MissingAcks) => { + return DkgCurrentState::NeedAntiEntropy; + } + Ok(k) => k, + }; + + let num_participants = self.pub_keys.len(); + if knowledge.agreed_with_all_acks.len() == num_participants { + DkgCurrentState::Termination(knowledge.part_acks) + } else if !knowledge.agreed_with_all_acks.is_empty() { + DkgCurrentState::WaitingForTotalAgreement + } else if knowledge.got_all_acks(num_participants) { + DkgCurrentState::GotAllAcks(knowledge.part_acks) + } else if !knowledge.part_acks.is_empty() { + DkgCurrentState::WaitingForMoreAcks + } else if knowledge.parts.len() == num_participants { + DkgCurrentState::GotAllParts(knowledge.parts) + } else { + DkgCurrentState::WaitingForMoreParts + } + } + + /// Sign, log and return the vote + fn cast_vote(&mut self, vote: DkgVote) -> Result { + let sig = self.secret_key.sign(&bincode::serialize(&vote)?); + let signed_vote = DkgSignedVote::new(vote, self.id, sig); + self.all_votes.insert(signed_vote.clone()); + Ok(signed_vote) + } + + /// Handles all the Acks + fn handle_all_acks(&mut self, all_acks: BTreeMap>) -> Result<()> { + for ((part_id, _part), acks) in all_acks { + for (sender_id, ack) in acks { + let outcome = self.keygen.handle_ack(&sender_id, ack.clone())?; + if let AckOutcome::Invalid(fault) = outcome { + return Err(Error::FaultyVote(format!( + "Ack fault: {fault:?} by {sender_id:?} for part by {part_id:?}" + ))); + } + } + } + Ok(()) + } + + /// Handles the Parts to create the Acks + fn parts_into_acks(&mut self, parts: BTreeSet) -> Result { + let mut acks = BTreeMap::new(); + for (sender_id, part) in parts { + match self + .keygen + .handle_part(&sender_id, part.clone(), &mut self.rng)? + { + PartOutcome::Valid(Some(ack)) => { + acks.insert((sender_id, part), ack); + } + PartOutcome::Invalid(fault) => { + return Err(Error::FaultyVote(format!( + "Part fault: {fault:?} by {sender_id:?}" + ))); + } + PartOutcome::Valid(None) => { + // code smell: we don't have observer nodes and we can't end up here if we've + // handled parts and given our acks already, this should not happen unless our + // votes were corrupted + return Err(Error::FaultyVote("unexpected part outcome, node is faulty or keygen already handled this part".to_string())); + } + } + } + Ok(DkgVote::SingleAck(acks)) + } + + /// Returns all the votes that we received as an anti entropy update + pub fn handle_ae(&self) -> VoteResponse { + VoteResponse::AntiEntropy(self.all_votes.clone()) + } + + /// Handle a DKG vote, save the information if we learned any, broadcast: + /// - SingleAck when got all parts + /// - AllAcks when got all acks + /// Consider we reached completion when we received everyone's signatures over the AllAcks + pub fn handle_signed_vote(&mut self, msg: DkgSignedVote) -> Result { + // immediately bail if signature check fails + self.get_validated_vote(&msg)?; + + // update knowledge with vote + self.all_votes.insert(msg); + let votes = self.all_checked_votes()?; + let dkg_state = self.current_dkg_state(votes); + + // act accordingly + match dkg_state { + DkgCurrentState::NeedAntiEntropy => Ok(VoteResponse::RequestAntiEntropy), + DkgCurrentState::Termination(acks) => { + self.handle_all_acks(acks)?; + if let (pubs, Some(sec)) = self.keygen.generate()? { + Ok(VoteResponse::DkgComplete(pubs, sec)) + } else { + Err(Error::FailedToGenerateSecretKeyShare) + } + } + DkgCurrentState::GotAllAcks(acks) => { + let vote = DkgVote::AllAcks(acks); + Ok(VoteResponse::BroadcastVote(Box::new(self.cast_vote(vote)?))) + } + DkgCurrentState::GotAllParts(parts) => { + let vote = self.parts_into_acks(parts)?; + Ok(VoteResponse::BroadcastVote(Box::new(self.cast_vote(vote)?))) + } + DkgCurrentState::WaitingForMoreParts + | DkgCurrentState::WaitingForMoreAcks + | DkgCurrentState::WaitingForTotalAgreement => Ok(VoteResponse::WaitingForMoreVotes), + DkgCurrentState::IncompatibleVotes => { + Err(Error::FaultyVote("got incompatible votes".to_string())) + } + } + } +} diff --git a/src/vote.rs b/src/vote.rs new file mode 100644 index 0000000..dab3db5 --- /dev/null +++ b/src/vote.rs @@ -0,0 +1,64 @@ +// Copyright 2022 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use bls::{PublicKey, Signature}; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::error::{Error, Result}; +use crate::sdkg::{Ack, Part}; + +pub(crate) type NodeId = u8; +pub(crate) type IdPart = (NodeId, Part); +pub(crate) type IdAck = (NodeId, Ack); + +// The order of entries in this enum is IMPORTANT +// It's the order in which they should be handled +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Serialize, Deserialize)] +pub enum DkgVote { + /// Participant's own Part + SinglePart(Part), + /// Participant's own Ack over everybody's Parts + SingleAck(BTreeMap), + /// All participants' Acks over every Parts, should be identical for all participants + AllAcks(BTreeMap>), +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Serialize, Deserialize)] +pub struct DkgSignedVote { + /// This field is private to ensure votes are always signature-checked + vote: DkgVote, + /// The id of the voter + pub voter: NodeId, + /// The bls signature of the voter + pub sig: Signature, +} + +fn verify_sig(msg: &M, sig: &Signature, public_key: &PublicKey) -> Result<()> { + let msg_bytes = bincode::serialize(msg)?; + if public_key.verify(sig, msg_bytes) { + Ok(()) + } else { + Err(Error::InvalidSignature) + } +} + +impl DkgSignedVote { + /// Creates a new DkgSignedVote from a DkgVote + pub fn new(vote: DkgVote, voter: NodeId, sig: Signature) -> Self { + DkgSignedVote { vote, voter, sig } + } + + /// Gets a DkgVote out of a DkgSignedVote and checks the signature as well as the content + /// This method is the only way to obtain the underlying DkgVote, + /// this helps ensure signatures are always checked before we can access votes + pub fn get_validated_vote(&self, public_key: &PublicKey) -> Result { + verify_sig(&self.vote, &self.sig, public_key)?; + Ok(self.vote.clone()) + } +}