Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encryption of proposals in HoneyBadger #68

Merged
merged 9 commits into from
Jun 22, 2018
Merged

Conversation

vkomenda
Copy link
Contributor

Close #41

@vkomenda vkomenda requested a review from afck June 20, 2018 17:23
self.handle_common_subset_message(sender_id, epoch, cs_msg)
}
MessageContent::DecryptionShare { proposer_id, share } => {
self.handle_decryption_share_message(sender_id, epoch, &proposer_id, &share)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could pass the proposer ID and share by value to avoid the cloning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then clippy starts complaining on "needless" pass-by-value.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But not if you remove the .clone() in handle_decryption_share_message.

.collect(),
)].into_iter()
.collect()
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another point where we'll need to think about spam protection (#43) in the future, i.e. guard against a node sending us tons of fake decryption shares for later epochs. But I don't have an idea how to do that yet.

.decrypted_selections
.iter()
.flat_map(|(proposer_id, ser_batch)| {
// If serialization fails, the proposer of that batch is faulty. Ignore it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserialization?

let mut proposer_ids: Option<BTreeSet<NodeUid>> = None;
if let Some(shares) = self.received_shares.get(&self.epoch) {
proposer_ids = Some(shares.keys().cloned().collect());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With else { return Ok(false); }, the proposer_ids wouldn't have to be an option and could be immutable.

}

/// Tries to decrypt the selection of transactions from a given proposer. Outputs `true` if and
/// only of decryption finished without errors.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ofif

self.try_output_batch()?;
}

Ok(())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to pk.verify_decryption_share(&share, &ciphertext) with the public key share pk of the sender as soon as we have both the ciphertext and the share. Then we drop all invalid shares, so we can be sure that any set of f + 1 share will successfully yield the proposer's input.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a separate issue to test my proposed implementation: #75. I suggest adding that test after we finish working on this PR.

.collect(),
)].into_iter()
.collect()
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be simplified:

self.received_shares
    .entry(epoch)
    .or_insert_with(BTreeSet::new)
    .entry(proposer_id.clone())
    .or_insert_with(BTreeSet::new)
    .insert(sender_id.clone(), share.clone());

// If serialization fails, the proposer of that batch is faulty. Ignore it.
bincode::deserialize::<Vec<Tx>>(&ser_batch)
.ok()
.map(|proposed| (proposer_id.clone(), proposed))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to keep in mind for #51: We could add another field (faulty_nodes?) to Batch, to report the nodes whose input didn't deserialize.

if !proposer_ids.is_empty()
&& proposer_ids
.iter()
.all(|proposer_id| self.try_decrypt_proposer_selection(proposer_id))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The criteria here should probably be:

  • CommonSubset has already output in this epoch.
  • For every proposer from CommonSubset's output, we have received f + 1 valid decryption shares.

If there are at most f faulty nodes, it should be guaranteed that that eventually happens and that decryption succeeds.

Calling try_decrypt_proposer_selection every time looks like it might be expensive: wouldn't that repeatedly decrypt and overwrite the same thing? (Or should that method just return true immediately, if there's already an entry in decrypted_selections?)

let all_ciphertext_proposers: BTreeSet<_> = ciphertexts.keys().collect();
let all_decrypted_selection_proposers: BTreeSet<_> =
self.decrypted_selections.keys().collect();
all_ciphertext_proposers.is_subset(&all_decrypted_selection_proposers)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a proper subset or would it just be equal?

Copy link
Collaborator

@afck afck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!
Just one possible simplification (but maybe I'm just confused by all those nested maps).

@@ -60,6 +60,9 @@ pub struct HoneyBadger<Tx, NodeUid> {
decrypted_selections: BTreeMap<NodeUid, Vec<u8>>,
/// Ciphertexts output by Common Subset in an epoch.
ciphertexts: BTreeMap<u64, BTreeMap<NodeUid, Ciphertext<Bls12>>>,
/// Those shares in `received_shares` that could not be verified on reception due to the absense
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is keeping track about those even necessary? When we receive the ciphertext, doesn't that mean that none of the existing shares have been verified yet? I.e. won't this set always either be equal to the senders whose shares we've already received (if we don't have the ciphertext yet), or empty (if we have it)?

(Also typo: absence)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed the case. Thanks for spotting this.

@@ -104,7 +104,10 @@ where
}

fn next_message(&mut self) -> Option<TargetedMessage<Self::Message, NodeUid>> {
self.messages.pop_front()
let convert = |msg: TargetedMessage<InternalMessage<NodeUid>, NodeUid>| {
msg.map(|int_msg| int_msg.into_external())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That probably doesn't need a closure: map(InternalMessage::into_external).

InternalMessageContent::CommonSubset(msg) => MessageContent::CommonSubset(msg),
InternalMessageContent::DecryptionShare { proposer_id, share } => {
// FIXME: Handle errors.
let share_copy = Rc::try_unwrap(share).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that will usually fail: Most of the time, the local reference will live longer than the message.

I hadn't realized that Rc doesn't automatically implement the serde traits. Maybe instead of the internal-external distinction the message should just always have an Rc, but we should implement Serialize and Deserialize manually? (Or rather with some attribute #[serde(with = "a_new_rc_serde_module")].)

But maybe this is all premature optimization; I'm happy to postpone that to a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Rc::try_unwrap() does fail here. I'll try to experiment with custom Serialize and Deserialize. There seems to be no shortcut to that and I think you are right to say it may deserve a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #77

@vkomenda vkomenda force-pushed the vk-encrypt-proposed branch from 0feb6fa to 4c2e92e Compare June 22, 2018 09:17
@vkomenda vkomenda merged commit e0a85a5 into master Jun 22, 2018
@afck afck deleted the vk-encrypt-proposed branch June 26, 2018 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants