Skip to main content

fedimint_hbbft/
threshold_decrypt.rs

1//! # Collaborative Threshold Decryption
2//!
3//! Each node inputs the same encrypted data, and after at least _f + 1_ correct validators have
4//! done so, each node outputs the decrypted data.
5//!
6//! ## How it works
7//!
8//! The algorithm uses a threshold encryption scheme: A message encrypted to the network's public
9//! key can be collaboratively decrypted by combining at least _f + 1_ decryption shares. Each
10//! validator holds a secret key share, and uses it to produce and multicast a decryption share once
11//! a ciphertext is provided. The algorithm outputs as soon as it receives a ciphertext and _f + 1_
12//! threshold shares.
13
14use std::collections::BTreeMap;
15use std::sync::Arc;
16
17use crate::crypto::{self, Ciphertext, DecryptionShare};
18use rand::Rng;
19use rand_derive::Rand;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22
23use crate::fault_log::{self, Fault};
24use crate::{ConsensusProtocol, NetworkInfo, NodeIdT, Target};
25
26/// A threshold decryption error.
27#[derive(Clone, Eq, PartialEq, Debug, Error)]
28pub enum Error {
29    /// Redundant input provided.
30    #[error("Redundant input provided: {0:?}")]
31    MultipleInputs(Box<Ciphertext>),
32    /// Invalid ciphertext.
33    #[error("Invalid ciphertext: {0:?}")]
34    InvalidCiphertext(Box<Ciphertext>),
35    /// Unknown sender.
36    #[error("Unknown sender")]
37    UnknownSender,
38    /// Decryption failed.
39    #[error("Decryption failed: {0:?}")]
40    Decryption(crypto::error::Error),
41    /// Tried to decrypt before setting a cipherext.
42    #[error("Tried to decrypt before setting ciphertext")]
43    CiphertextIsNone,
44}
45
46/// A threshold decryption result.
47pub type Result<T> = ::std::result::Result<T, Error>;
48
49/// A threshold decryption message fault
50#[derive(Clone, Debug, Error, PartialEq, Eq)]
51pub enum FaultKind {
52    /// `ThresholdDecrypt` received multiple shares from the same sender.
53    #[error("`ThresholdDecrypt` received multiple shares from the same sender.")]
54    MultipleDecryptionShares,
55    /// `HoneyBadger` received a decryption share from an unverified sender.
56    #[error("`HoneyBadger` received a decryption share from an unverified sender.")]
57    UnverifiedDecryptionShareSender,
58}
59
60/// The type of fault log whose entries are `ThresholdDecrypt` faults.
61pub type FaultLog<N> = fault_log::FaultLog<N, FaultKind>;
62
63/// A Threshold Decryption message.
64#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Rand)]
65pub struct Message(pub DecryptionShare);
66
67/// A Threshold Decrypt algorithm instance. If every node inputs the same data, encrypted to the
68/// network's public key, every node will output the decrypted data.
69#[derive(Debug)]
70pub struct ThresholdDecrypt<N> {
71    netinfo: Arc<NetworkInfo<N>>,
72    /// The encrypted data.
73    ciphertext: Option<Ciphertext>,
74    /// All received threshold decryption shares.
75    shares: BTreeMap<N, (usize, DecryptionShare)>,
76    /// Whether we already sent our shares.
77    had_input: bool,
78    /// Whether we have already returned the output.
79    terminated: bool,
80}
81
82/// A `ThresholdDecrypt` step. It will contain at most one output.
83pub type Step<N> = crate::CpStep<ThresholdDecrypt<N>>;
84
85impl<N: NodeIdT> ConsensusProtocol for ThresholdDecrypt<N> {
86    type NodeId = N;
87    type Input = ();
88    type Output = Vec<u8>;
89    type Message = Message;
90    type Error = Error;
91    type FaultKind = FaultKind;
92
93    fn handle_input<R: Rng>(&mut self, _input: (), _rng: &mut R) -> Result<Step<N>> {
94        self.start_decryption()
95    }
96
97    fn handle_message<R: Rng>(
98        &mut self,
99        sender_id: &Self::NodeId,
100        message: Message,
101        _rng: &mut R,
102    ) -> Result<Step<N>> {
103        self.handle_message(sender_id, message)
104    }
105
106    fn terminated(&self) -> bool {
107        self.terminated
108    }
109
110    fn our_id(&self) -> &N {
111        self.netinfo.our_id()
112    }
113}
114
115impl<N: NodeIdT> ThresholdDecrypt<N> {
116    /// Creates a new Threshold Decrypt instance.
117    pub fn new(netinfo: Arc<NetworkInfo<N>>) -> Self {
118        ThresholdDecrypt {
119            netinfo,
120            ciphertext: None,
121            shares: BTreeMap::new(),
122            had_input: false,
123            terminated: false,
124        }
125    }
126
127    /// Creates a new instance of `ThresholdDecrypt`, including setting the ciphertext to
128    /// decrypt.
129    pub fn new_with_ciphertext(netinfo: Arc<NetworkInfo<N>>, ct: Ciphertext) -> Result<Self> {
130        let mut td = ThresholdDecrypt::new(netinfo);
131        td.set_ciphertext(ct)?;
132        Ok(td)
133    }
134
135    /// Sets the ciphertext, sends the decryption share, and tries to decrypt it.
136    /// This must be called exactly once, with the same ciphertext in all participating nodes.
137    /// If we have enough shares, outputs the plaintext.
138    pub fn set_ciphertext(&mut self, ct: Ciphertext) -> Result<()> {
139        if self.ciphertext.is_some() {
140            return Err(Error::MultipleInputs(Box::new(ct)));
141        }
142        if !ct.verify() {
143            return Err(Error::InvalidCiphertext(Box::new(ct)));
144        }
145        self.ciphertext = Some(ct);
146        Ok(())
147    }
148
149    /// Sends our decryption shares to peers, and if we have collected enough, returns the decrypted
150    /// message. Returns an error if the ciphertext hasn't been received yet.
151    pub fn start_decryption(&mut self) -> Result<Step<N>> {
152        if self.had_input {
153            return Ok(Step::default()); // Don't waste time on redundant shares.
154        }
155        let ct = self.ciphertext.clone().ok_or(Error::CiphertextIsNone)?;
156        let mut step = Step::default();
157        step.fault_log.extend(self.remove_invalid_shares());
158        self.had_input = true;
159        let opt_idx = self.netinfo.node_index(self.our_id());
160        let (idx, share) = match (opt_idx, self.netinfo.secret_key_share()) {
161            (Some(idx), Some(sks)) => (idx, sks.decrypt_share_no_verify(&ct)),
162            (_, _) => return Ok(step.join(self.try_output()?)), // Not a validator.
163        };
164        let our_id = self.our_id().clone();
165        let msg = Target::all().message(Message(share.clone()));
166        step.messages.push(msg);
167        self.shares.insert(our_id, (idx, share));
168        step.extend(self.try_output()?);
169        Ok(step)
170    }
171
172    /// Returns an iterator over the IDs of all nodes who sent a share.
173    pub fn sender_ids(&self) -> impl Iterator<Item = &N> {
174        self.shares.keys()
175    }
176
177    /// Handles a message with a decryption share received from `sender_id`.
178    ///
179    /// This must be called with every message we receive from another node.
180    ///
181    /// If we have collected enough, returns the decrypted message.
182    pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
183        if self.terminated {
184            return Ok(Step::default()); // Don't waste time on redundant shares.
185        }
186        // Before checking the share, ensure the sender is a known validator
187        let idx = self
188            .netinfo
189            .node_index(sender_id)
190            .ok_or(Error::UnknownSender)?;
191        let Message(share) = message;
192        if !self.is_share_valid(sender_id, &share) {
193            let fault_kind = FaultKind::UnverifiedDecryptionShareSender;
194            return Ok(Fault::new(sender_id.clone(), fault_kind).into());
195        }
196        let entry = (idx, share);
197        if self.shares.insert(sender_id.clone(), entry).is_some() {
198            return Ok(Fault::new(sender_id.clone(), FaultKind::MultipleDecryptionShares).into());
199        }
200        self.try_output()
201    }
202
203    /// Removes all shares that are invalid, and returns faults for their senders.
204    fn remove_invalid_shares(&mut self) -> FaultLog<N> {
205        let faulty_senders: Vec<N> = self
206            .shares
207            .iter()
208            .filter(|(id, (_, share))| !self.is_share_valid(id, share))
209            .map(|(id, _)| id.clone())
210            .collect();
211        let mut fault_log = FaultLog::default();
212        for id in faulty_senders {
213            self.shares.remove(&id);
214            fault_log.append(id, FaultKind::UnverifiedDecryptionShareSender);
215        }
216        fault_log
217    }
218
219    /// Returns `true` if the share is valid, or if we don't have the ciphertext yet.
220    fn is_share_valid(&self, id: &N, share: &DecryptionShare) -> bool {
221        let ct = match self.ciphertext {
222            None => return true, // No ciphertext yet. Verification postponed.
223            Some(ref ct) => ct,
224        };
225        match self.netinfo.public_key_share(id) {
226            None => false, // Unknown sender.
227            Some(pk) => pk.verify_decryption_share(share, ct),
228        }
229    }
230
231    /// Outputs the decrypted message, if we have the ciphertext and enough shares.
232    fn try_output(&mut self) -> Result<Step<N>> {
233        if self.terminated || self.shares.len() <= self.netinfo.num_faulty() {
234            return Ok(Step::default()); // Not enough shares yet, or already terminated.
235        }
236        let ct = match self.ciphertext {
237            None => return Ok(Step::default()), // Still waiting for the ciphertext.
238            Some(ref ct) => ct.clone(),
239        };
240        self.terminated = true;
241        let step = self.start_decryption()?; // Before terminating, make sure we sent our share.
242        let share_itr = self
243            .shares
244            .values()
245            .map(|&(ref idx, ref share)| (idx, share));
246        let plaintext = self
247            .netinfo
248            .public_key_set()
249            .decrypt(share_itr, &ct)
250            .map_err(Error::Decryption)?;
251        Ok(step.with_output(plaintext))
252    }
253}