Split out Threshold Decryption from Honey Badger.

This commit is contained in:
Andreas Fackler 2018-08-03 11:18:06 +02:00 committed by Nick Sanders
parent 22ccb740f1
commit 46a8728792
8 changed files with 285 additions and 230 deletions

View File

@ -16,6 +16,8 @@ pub enum FaultKind {
InvalidCiphertext,
/// `HoneyBadger` was unable to decrypt a share received from a proposer.
ShareDecryptionFailed,
/// `ThresholdDecryption` received multiple shares from the same sender.
MultipleDecryptionShares,
/// `Broadcast` received a `Value` from a node other than the proposer.
ReceivedValueFromNonProposer,
/// `Broadcast` recevied an Echo message containing an invalid proof.

View File

@ -48,9 +48,8 @@ where
common_subsets: BTreeMap::new(),
max_future_epochs: self.max_future_epochs as u64,
incoming_queue: BTreeMap::new(),
received_shares: BTreeMap::new(),
threshold_decryption: BTreeMap::new(),
decrypted_contributions: BTreeMap::new(),
ciphertexts: BTreeMap::new(),
_phantom: PhantomData,
}
}

View File

@ -4,20 +4,24 @@ use bincode;
use failure::{Backtrace, Context, Fail};
use common_subset;
use threshold_decryption;
/// Honey badger error variants.
#[derive(Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "ProposeBincode error: {}", _0)]
ProposeBincode(bincode::ErrorKind),
#[fail(display = "ProposeCommonSubset0 error: {}", _0)]
ProposeCommonSubset0(common_subset::Error),
#[fail(display = "ProposeCommonSubset1 error: {}", _0)]
ProposeCommonSubset1(common_subset::Error),
#[fail(display = "HandleCommonMessageCommonSubset0 error: {}", _0)]
HandleCommonMessageCommonSubset0(common_subset::Error),
#[fail(display = "HandleCommonMessageCommonSubset1 error: {}", _0)]
HandleCommonMessageCommonSubset1(common_subset::Error),
#[fail(display = "Failed to instantiate Common Subset: {}", _0)]
CreateCommonSubset(common_subset::Error),
#[fail(
display = "Failed to input contribution to Common Subset: {}",
_0
)]
InputCommonSubset(common_subset::Error),
#[fail(display = "Failed to handle Common Subset message: {}", _0)]
HandleCommonSubsetMessage(common_subset::Error),
#[fail(display = "Threshold decryption error: {}", _0)]
ThresholdDecryption(threshold_decryption::Error),
#[fail(display = "Unknown sender")]
UnknownSender,
}

View File

@ -1,19 +1,19 @@
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use std::marker::PhantomData;
use std::mem;
use std::sync::Arc;
use bincode;
use crypto::{Ciphertext, DecryptionShare};
use crypto::Ciphertext;
use itertools::Itertools;
use rand::Rand;
use serde::{Deserialize, Serialize};
use super::{Batch, Error, ErrorKind, HoneyBadgerBuilder, Message, MessageContent, Result};
use common_subset::{self, CommonSubset};
use fault_log::{Fault, FaultKind, FaultLog};
use messaging::{self, DistAlgorithm, NetworkInfo, Target};
use fault_log::FaultKind;
use messaging::{self, DistAlgorithm, NetworkInfo};
use threshold_decryption::{self as td, ThresholdDecryption};
use traits::{Contribution, NodeUidT};
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
@ -32,14 +32,10 @@ pub struct HoneyBadger<C, N: Rand> {
pub(super) max_future_epochs: u64,
/// Messages for future epochs that couldn't be handled yet.
pub(super) incoming_queue: BTreeMap<u64, Vec<(N, MessageContent<N>)>>,
/// Received decryption shares for an epoch. Each decryption share has a sender and a
/// proposer. The outer `BTreeMap` has epochs as its key. The next `BTreeMap` has proposers as
/// its key. The inner `BTreeMap` has the sender as its key.
pub(super) received_shares: BTreeMap<u64, BTreeMap<N, BTreeMap<N, DecryptionShare>>>,
/// The threshold decryption algorithm, by epoch and proposer.
pub(super) threshold_decryption: BTreeMap<u64, BTreeMap<N, ThresholdDecryption<N>>>,
/// Decoded accepted proposals.
pub(super) decrypted_contributions: BTreeMap<N, Vec<u8>>,
/// Ciphertexts output by Common Subset in an epoch.
pub(super) ciphertexts: BTreeMap<u64, BTreeMap<N, Ciphertext>>,
pub(super) decrypted_contributions: BTreeMap<u64, BTreeMap<N, Vec<u8>>>,
pub(super) _phantom: PhantomData<C>,
}
@ -108,7 +104,7 @@ where
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(
CommonSubset::new(self.netinfo.clone(), epoch)
.map_err(ErrorKind::ProposeCommonSubset0)?,
.map_err(ErrorKind::CreateCommonSubset)?,
),
};
let ser_prop =
@ -116,7 +112,7 @@ where
let ciphertext = self.netinfo.public_key_set().public_key().encrypt(ser_prop);
self.has_input = true;
cs.input(bincode::serialize(&ciphertext).unwrap())
.map_err(ErrorKind::ProposeCommonSubset1)?
.map_err(ErrorKind::InputCommonSubset)?
};
self.process_output(cs_step, epoch)
}
@ -167,19 +163,15 @@ where
// Epoch has already terminated. Message is obsolete.
return Ok(Step::default());
} else {
entry.insert(
CommonSubset::new(self.netinfo.clone(), epoch)
.map_err(ErrorKind::HandleCommonMessageCommonSubset0)?,
)
let cs_result = CommonSubset::new(self.netinfo.clone(), epoch);
entry.insert(cs_result.map_err(ErrorKind::CreateCommonSubset)?)
}
}
};
cs.handle_message(sender_id, message)
.map_err(ErrorKind::HandleCommonMessageCommonSubset1)?
.map_err(ErrorKind::HandleCommonSubsetMessage)?
};
let step = self.process_output(cs_step, epoch)?;
self.remove_terminated();
Ok(step)
self.process_output(cs_step, epoch)
}
/// Handles decryption shares sent by `HoneyBadger` instances.
@ -188,84 +180,77 @@ where
sender_id: &N,
epoch: u64,
proposer_id: N,
share: DecryptionShare,
share: td::Message,
) -> Result<Step<C, N>> {
if let Some(ciphertext) = self
.ciphertexts
.get(&epoch)
.and_then(|cts| cts.get(&proposer_id))
{
if !self.verify_decryption_share(sender_id, &share, ciphertext) {
let fault_kind = FaultKind::UnverifiedDecryptionShareSender;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
}
}
// Insert the share.
self.received_shares
let netinfo = self.netinfo.clone();
let td_step = self
.threshold_decryption
.entry(epoch)
.or_insert_with(BTreeMap::new)
.entry(proposer_id)
.or_insert_with(BTreeMap::new)
.insert(sender_id.clone(), share);
.entry(proposer_id.clone())
.or_insert_with(|| ThresholdDecryption::new(netinfo))
.handle_message(sender_id, share)
.map_err(ErrorKind::ThresholdDecryption)?;
let mut step = self.process_threshold_decryption(epoch, proposer_id, td_step)?;
if epoch == self.epoch {
self.try_output_batches()
} else {
Ok(Step::default())
step.extend(self.try_output_batches()?);
}
Ok(step)
}
/// Verifies a given decryption share using the sender's public key and the proposer's
/// ciphertext. Returns `true` if verification has been successful and `false` if verification
/// has failed.
fn verify_decryption_share(
&self,
sender_id: &N,
share: &DecryptionShare,
ciphertext: &Ciphertext,
) -> bool {
if let Some(pk) = self.netinfo.public_key_share(sender_id) {
pk.verify_decryption_share(&share, ciphertext)
} else {
false
/// Processes a Threshold Decryption step.
fn process_threshold_decryption(
&mut self,
epoch: u64,
proposer_id: N,
td_step: td::Step<N>,
) -> Result<Step<C, N>> {
let mut step = Step::default();
let opt_output = step.extend_with(td_step, |share| {
MessageContent::DecryptionShare {
proposer_id: proposer_id.clone(),
share,
}.with_epoch(epoch)
});
if let Some(output) = opt_output.into_iter().next() {
self.decrypted_contributions
.entry(epoch)
.or_insert_with(BTreeMap::new)
.insert(proposer_id, output);
}
Ok(step)
}
/// When contributions of transactions have been decrypted for all valid proposers in this
/// epoch, moves those contributions into a batch, outputs the batch and updates the epoch.
fn try_output_batch(&mut self) -> Result<Option<Step<C, N>>> {
// Return if we don't have ciphertexts yet.
let proposer_ids = match self.ciphertexts.get(&self.epoch) {
let proposer_ids = match self.threshold_decryption.get(&self.epoch) {
Some(cts) => cts.keys().cloned().collect_vec(),
None => return Ok(None),
None => return Ok(None), // Decryption hasn't even started yet.
};
// Try to decrypt all contributions. If some are still missing, return.
if !proposer_ids
.into_iter()
.all(|id| self.try_decrypt_proposer_contribution(id))
{
return Ok(None);
}
let mut step = Step::default();
// Deserialize the output.
let contributions: BTreeMap<N, C> =
mem::replace(&mut self.decrypted_contributions, BTreeMap::new())
let contributions: BTreeMap<N, C> = {
let decrypted = match self.decrypted_contributions.get(&self.epoch) {
Some(dc) if dc.keys().eq(proposer_ids.iter()) => dc,
_ => return Ok(None), // Not enough decrypted contributions yet.
};
decrypted
.into_iter()
.flat_map(|(proposer_id, ser_contrib)| {
// If deserialization fails, the proposer of that item is faulty. Ignore it.
if let Ok(contrib) = bincode::deserialize::<C>(&ser_contrib) {
Some((proposer_id, contrib))
if let Ok(contrib) = bincode::deserialize::<C>(ser_contrib) {
Some((proposer_id.clone(), contrib))
} else {
let fault_kind = FaultKind::BatchDeserializationFailed;
step.fault_log.append(proposer_id, fault_kind);
step.fault_log.append(proposer_id.clone(), fault_kind);
None
}
})
.collect();
.collect()
};
let batch = Batch {
epoch: self.epoch,
contributions,
@ -285,8 +270,9 @@ where
/// Increments the epoch number and clears any state that is local to the finished epoch.
fn update_epoch(&mut self) -> Result<Step<C, N>> {
// Clear the state of the old epoch.
self.ciphertexts.remove(&self.epoch);
self.received_shares.remove(&self.epoch);
self.threshold_decryption.remove(&self.epoch);
self.decrypted_contributions.remove(&self.epoch);
self.common_subsets.remove(&self.epoch);
self.epoch += 1;
self.has_input = false;
let max_epoch = self.epoch + self.max_future_epochs;
@ -311,51 +297,14 @@ where
Ok(step)
}
/// Tries to decrypt the contribution from a given proposer.
fn try_decrypt_proposer_contribution(&mut self, proposer_id: N) -> bool {
if self.decrypted_contributions.contains_key(&proposer_id) {
return true; // Already decrypted.
}
let shares = if let Some(shares) = self
.received_shares
.get(&self.epoch)
.and_then(|sh| sh.get(&proposer_id))
{
shares
} else {
return false; // No shares yet.
};
if shares.len() <= self.netinfo.num_faulty() {
return false; // Not enough shares yet.
}
if let Some(ciphertext) = self
.ciphertexts
.get(&self.epoch)
.and_then(|cts| cts.get(&proposer_id))
{
match {
let to_idx = |(id, share)| (self.netinfo.node_index(id).unwrap(), share);
let share_itr = shares.into_iter().map(to_idx);
self.netinfo.public_key_set().decrypt(share_itr, ciphertext)
} {
Ok(contrib) => {
self.decrypted_contributions.insert(proposer_id, contrib);
}
Err(err) => error!("{:?} Decryption failed: {:?}.", self.our_id(), err),
}
}
true
}
fn send_decryption_shares(
&mut self,
cs_output: BTreeMap<N, Vec<u8>>,
epoch: u64,
) -> Result<Step<C, N>> {
let mut step = Step::default();
let mut ciphertexts = BTreeMap::new();
for (proposer_id, v) in cs_output {
// TODO: Input into ThresholdDecryption. Check errors!
let ciphertext: Ciphertext = match bincode::deserialize(&v) {
Ok(ciphertext) => ciphertext,
Err(err) => {
@ -368,98 +317,32 @@ where
continue;
}
};
if !ciphertext.verify() {
warn!("Invalid ciphertext from {:?}", proposer_id);
let fault_kind = FaultKind::ShareDecryptionFailed;
step.fault_log.append(proposer_id.clone(), fault_kind);
continue;
}
let (incorrect_senders, faults) =
self.verify_pending_decryption_shares(&proposer_id, &ciphertext, epoch);
self.remove_incorrect_decryption_shares(&proposer_id, incorrect_senders, epoch);
step.fault_log.extend(faults);
if self.netinfo.is_validator() {
step.extend(self.send_decryption_share(&proposer_id, &ciphertext, epoch)?);
}
ciphertexts.insert(proposer_id, ciphertext);
let netinfo = self.netinfo.clone();
let td_step = match self
.threshold_decryption
.entry(epoch)
.or_insert_with(BTreeMap::new)
.entry(proposer_id.clone())
.or_insert_with(|| ThresholdDecryption::new(netinfo))
.input(ciphertext)
{
Ok(td_step) => td_step,
Err(td::Error::InvalidCiphertext(_)) => {
warn!("Invalid ciphertext from {:?}", proposer_id);
let fault_kind = FaultKind::ShareDecryptionFailed;
step.fault_log.append(proposer_id.clone(), fault_kind);
continue;
}
Err(err) => return Err(ErrorKind::ThresholdDecryption(err).into()),
};
step.extend(self.process_threshold_decryption(epoch, proposer_id, td_step)?);
}
self.ciphertexts.insert(epoch, ciphertexts);
if epoch == self.epoch {
step.extend(self.try_output_batches()?);
}
Ok(step)
}
/// Sends decryption shares without verifying the ciphertext.
fn send_decryption_share(
&mut self,
proposer_id: &N,
ciphertext: &Ciphertext,
epoch: u64,
) -> Result<Step<C, N>> {
let share = self
.netinfo
.secret_key_share()
.decrypt_share_no_verify(&ciphertext);
// Send the share to remote nodes.
let our_id = self.netinfo.our_uid().clone();
// Insert the share.
self.received_shares
.entry(epoch)
.or_insert_with(BTreeMap::new)
.entry(proposer_id.clone())
.or_insert_with(BTreeMap::new)
.insert(our_id, share.clone());
let content = MessageContent::DecryptionShare {
proposer_id: proposer_id.clone(),
share,
};
Ok(Target::All.message(content.with_epoch(epoch)).into())
}
/// Verifies the shares of the current epoch that are pending verification. Returned are the
/// senders with incorrect pending shares.
fn verify_pending_decryption_shares(
&self,
proposer_id: &N,
ciphertext: &Ciphertext,
epoch: u64,
) -> (BTreeSet<N>, FaultLog<N>) {
let mut incorrect_senders = BTreeSet::new();
let mut fault_log = FaultLog::new();
if let Some(sender_shares) = self
.received_shares
.get(&epoch)
.and_then(|e| e.get(proposer_id))
{
for (sender_id, share) in sender_shares {
if !self.verify_decryption_share(sender_id, share, ciphertext) {
let fault_kind = FaultKind::UnverifiedDecryptionShareSender;
fault_log.append(sender_id.clone(), fault_kind);
incorrect_senders.insert(sender_id.clone());
}
}
}
(incorrect_senders, fault_log)
}
fn remove_incorrect_decryption_shares(
&mut self,
proposer_id: &N,
incorrect_senders: BTreeSet<N>,
epoch: u64,
) {
if let Some(sender_shares) = self
.received_shares
.get_mut(&epoch)
.and_then(|e| e.get_mut(proposer_id))
{
for sender_id in incorrect_senders {
sender_shares.remove(&sender_id);
}
}
}
/// Checks whether the current epoch has output, and if it does, sends out our decryption
/// shares. The `epoch` argument allows to differentiate between calls which produce output in
/// all conditions, `epoch == None`, and calls which only produce output in a given epoch,
@ -482,23 +365,4 @@ where
}
Ok(step)
}
/// Removes all `CommonSubset` instances from _past_ epochs that have terminated.
fn remove_terminated(&mut self) {
let terminated_epochs: Vec<u64> = self
.common_subsets
.iter()
.take_while(|&(epoch, _)| *epoch < self.epoch)
.filter(|&(_, cs)| cs.terminated())
.map(|(epoch, _)| *epoch)
.collect();
for epoch in terminated_epochs {
debug!(
"{:?} Epoch {} has terminated.",
self.netinfo.our_uid(),
epoch
);
self.common_subsets.remove(&epoch);
}
}
}

View File

@ -1,7 +1,7 @@
use crypto::DecryptionShare;
use rand::Rand;
use common_subset;
use threshold_decryption;
/// The content of a `HoneyBadger` message. It should be further annotated with an epoch.
#[derive(Clone, Debug, Deserialize, Rand, Serialize)]
@ -11,7 +11,7 @@ pub enum MessageContent<N: Rand> {
/// A decrypted share of the output of `proposer_id`.
DecryptionShare {
proposer_id: N,
share: DecryptionShare,
share: threshold_decryption::Message,
},
}

View File

@ -134,6 +134,7 @@ pub mod honey_badger;
pub mod messaging;
pub mod queueing_honey_badger;
pub mod sync_key_gen;
pub mod threshold_decryption;
pub mod transaction_queue;
/// Common supertraits.

184
src/threshold_decryption.rs Normal file
View File

@ -0,0 +1,184 @@
//! # Collaborative Threshold Decryption
//!
//! Each node inputs the same encrypted data, and after at least _f + 1_ correct validators have
//! don seo, each node outputs the decrypted data.
//!
//! ## How it works
//!
//! The algorithm uses a threshold encryption scheme: A message encrypted to the network's public
//! key can be collaboratively decrypted by combining at least _f + 1_ decryption shares. Each
//! validator holds a secret key share, and uses it to produce and multicast a decryption share.
//! The algorithm outputs as soon as _f + 1_ of them have been received.
use std::collections::BTreeMap;
use std::sync::Arc;
use crypto::error as cerror;
use crypto::{Ciphertext, DecryptionShare};
use fault_log::{Fault, FaultKind, FaultLog};
use messaging::{self, DistAlgorithm, NetworkInfo, Target};
use traits::NodeUidT;
/// A threshold decryption error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum Error {
#[fail(display = "Redundant input provided: {:?}", _0)]
MultipleInputs(Box<Ciphertext>),
#[fail(display = "Invalid ciphertext: {:?}", _0)]
InvalidCiphertext(Box<Ciphertext>),
#[fail(display = "Unknown sender")]
UnknownSender,
#[fail(display = "Decryption failed: {:?}", _0)]
Decryption(cerror::Error),
}
/// A threshold decryption result.
pub type Result<T> = ::std::result::Result<T, Error>;
/// A Threshold Decryption message.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Rand)]
pub struct Message(pub DecryptionShare);
/// A Threshold Decryption algorithm instance. If every node inputs the same data, encrypted to the
/// network's public key, every node will output the decrypted data.
#[derive(Debug)]
pub struct ThresholdDecryption<N> {
netinfo: Arc<NetworkInfo<N>>,
/// The encrypted data.
ciphertext: Option<Ciphertext>,
/// All received threshold decryption shares.
shares: BTreeMap<N, DecryptionShare>,
/// Whether we have already returned the output.
terminated: bool,
}
pub type Step<N> = messaging::Step<ThresholdDecryption<N>>;
impl<N: NodeUidT> DistAlgorithm for ThresholdDecryption<N> {
type NodeUid = N;
type Input = Ciphertext;
type Output = Vec<u8>;
type Message = Message;
type Error = Error;
fn input(&mut self, input: Ciphertext) -> Result<Step<N>> {
self.set_ciphertext(input)
}
fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
self.handle_message(sender_id, message)
}
fn terminated(&self) -> bool {
self.terminated
}
fn our_id(&self) -> &N {
self.netinfo.our_uid()
}
}
impl<N: NodeUidT> ThresholdDecryption<N> {
/// Creates a new Threshold Decryption instance.
pub fn new(netinfo: Arc<NetworkInfo<N>>) -> Self {
ThresholdDecryption {
netinfo,
ciphertext: None,
shares: BTreeMap::new(),
terminated: false,
}
}
/// Sets the ciphertext, sends the decryption share, and tries to decrypt it.
/// This must be called exactly once, with the same ciphertext in all participating nodes.
pub fn set_ciphertext(&mut self, ct: Ciphertext) -> Result<Step<N>> {
if self.ciphertext.is_some() {
return Err(Error::MultipleInputs(Box::new(ct)));
}
let share = match self.netinfo.secret_key_share().decrypt_share(&ct) {
None => return Err(Error::InvalidCiphertext(Box::new(ct))),
Some(share) => share,
};
self.ciphertext = Some(ct);
let our_id = self.our_id().clone();
let mut step = Step::default();
step.fault_log.extend(self.remove_invalid_shares());
if self.netinfo.is_validator() {
let msg = Target::All.message(Message(share.clone()));
step.messages.push_back(msg);
self.shares.insert(our_id, share);
}
step.extend(self.try_output()?);
Ok(step)
}
fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
if self.terminated {
return Ok(Step::default()); // Don't waste time on redundant shares.
}
let Message(share) = message;
if !self.is_share_valid(sender_id, &share) {
let fault_kind = FaultKind::UnverifiedDecryptionShareSender;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
}
if self.shares.insert(sender_id.clone(), share).is_some() {
return Ok(Fault::new(sender_id.clone(), FaultKind::MultipleDecryptionShares).into());
}
self.try_output()
}
/// Removes all shares that are invalid, and returns faults for their senders.
fn remove_invalid_shares(&mut self) -> FaultLog<N> {
let faulty_senders: Vec<N> = self
.shares
.iter()
.filter(|(id, share)| !self.is_share_valid(id, share))
.map(|(id, _)| id.clone())
.collect();
let mut fault_log = FaultLog::default();
for id in faulty_senders {
self.shares.remove(&id);
fault_log.append(id, FaultKind::UnverifiedDecryptionShareSender);
}
fault_log
}
/// Returns `true` if the share is valid, or if we don't have the ciphertext yet.
fn is_share_valid(&self, id: &N, share: &DecryptionShare) -> bool {
let ct = match self.ciphertext {
None => return true, // No ciphertext yet. Verification postponed.
Some(ref ct) => ct,
};
match self.netinfo.public_key_share(id) {
None => false, // Unknown sender.
Some(pk) => pk.verify_decryption_share(share, ct),
}
}
/// Outputs the decrypted message, if we have the ciphertext and enough shares.
fn try_output(&mut self) -> Result<Step<N>> {
if self.terminated || self.shares.len() <= self.netinfo.num_faulty() {
return Ok(Step::default()); // Not enough shares yet, or already terminated.
}
let ct = match self.ciphertext {
None => return Ok(Step::default()), // Still waiting for the ciphertext.
Some(ref ct) => ct,
};
self.terminated = true;
let plaintext = {
let to_idx = |(id, share)| {
let idx = self
.netinfo
.node_index(id)
.expect("we put only validators' shares in the map; qed");
(idx, share)
};
let share_itr = self.shares.iter().map(to_idx);
self.netinfo
.public_key_set()
.decrypt(share_itr, ct)
.map_err(Error::Decryption)?
};
Ok(Step::default().with_output(plaintext))
}
}

View File

@ -25,6 +25,7 @@ use rand::Rng;
use hbbft::honey_badger::{self, Batch, HoneyBadger, MessageContent};
use hbbft::messaging::{NetworkInfo, Target, TargetedMessage};
use hbbft::threshold_decryption;
use hbbft::transaction_queue::TransactionQueue;
use network::{
@ -111,7 +112,7 @@ impl Adversary<UsizeHoneyBadger> for FaultyShareAdversary {
Target::All.message(
MessageContent::DecryptionShare {
proposer_id: NodeUid(proposer_id),
share: share.clone(),
share: threshold_decryption::Message(share.clone()),
}.with_epoch(*epoch),
),
))