-
Notifications
You must be signed in to change notification settings - Fork 31
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
Vote and Cert validation #3521
Vote and Cert validation #3521
Conversation
@@ -70,7 +70,7 @@ pub struct SimpleCertificate<TYPES: NodeType, VOTEABLE: Voteable, THRESHOLD: Thr | |||
/// The data this certificate is for. I.e the thing that was voted on to create this Certificate | |||
pub data: VOTEABLE, | |||
/// commitment of all the votes this cert should be signed over | |||
pub vote_commitment: Commitment<VOTEABLE>, | |||
pub data_commitment: Commitment<VOTEABLE>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may not be able to change this, but I'm not completely sure. I would lean towards keeping the old name out of caution.
This doesn't affect the bincode serialization of the struct, but I believe it will change the JSON serialization. I'm not sure what repositories use JSON (if any), though I think the sequencer at the very least has a test that might catch this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think @jbearer mentioned a few weeks ago that changing the schema will cause downstream changes. Are we still in the "try not to change public interfaces" mode? Or are we past that.
@@ -375,11 +375,11 @@ impl<TYPES: NodeType, I: NodeImplementation<TYPES>> ConsensusTaskState<TYPES, I> | |||
|
|||
debug!( | |||
"Attempting to publish proposal after forming a TC for view {}", | |||
*qc.view_number | |||
*qc.view_number() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're a hero for making these little fixes!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been trying to wrap my head around this and I think there is some room to clear things up. I keep getting confused on the various usages of vote.commit()
, and vote_commentment()
as well as where we use the "data commitment". I think maybe we can remove one or the other, and possibly the Committable
bound from a few places and that would help clear things up.
/// commitment of all the votes this cert should be signed over | ||
pub vote_commitment: Commitment<VOTEABLE>, | ||
/// Do not use this field, the commitment needs to be calculated instead | ||
vote_commitment: Commitment<VOTE>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we keep this type the same as it was before, since we don't need it anyway: Commitment<VOTE::Data>
crates/types/src/simple_vote.rs
Outdated
fn vote_commitment(&self) -> Commitment<Self> { | ||
self.commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just remove if we can just call commit() anyway?
crates/types/src/vote.rs
Outdated
@@ -90,33 +105,39 @@ type SignersMap<COMMITMENT, KEY> = HashMap< | |||
/// Accumulates votes until a certificate is formed. This implementation works for all simple vote and certificate pairs | |||
pub struct VoteAccumulator< | |||
TYPES: NodeType, | |||
VOTE: Vote<TYPES>, | |||
CERT: Certificate<TYPES, Voteable = VOTE::Commitment>, | |||
VOTE: Vote<TYPES> + Committable, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the Commitable
bound shouldn't be needed
crates/types/src/vote.rs
Outdated
{ | ||
/// Add a vote to the total accumulated votes. Returns the accumulator or the certificate if we | ||
/// have accumulated enough votes to exceed the threshold for creating a certificate. | ||
pub fn accumulate(&mut self, vote: &VOTE, membership: &TYPES::Membership) -> Either<(), CERT> { | ||
let key = vote.signing_key(); | ||
|
||
let vote_commitment = vote.date_commitment(); | ||
let vote_commitment = vote.vote_commitment(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vote.commit()
?
crates/types/src/vote.rs
Outdated
VoteAccumulator<TYPES, VOTE, CERT> | ||
impl< | ||
TYPES: NodeType, | ||
VOTE: Vote<TYPES> + Committable + Clone, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same do we need Committable
crates/types/src/simple_vote.rs
Outdated
match TYPES::SignatureKey::sign(private_key, data.commit().as_ref()) { | ||
match TYPES::SignatureKey::sign( | ||
private_key, | ||
<SimpleVote<TYPES, DATA> as Vote<TYPES>>::commit(&data, view).as_ref(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Self::commit() not work here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting a compiler error because of the name clash. I can change the Vote::commit
name if it helps with the readability. Wdyt?
For now, I've simplified it to:
<Self as Vote<TYPES>>::commit(&data, view).as_ref()
The error:
error[E0034]: multiple applicable items in scope
--> crates/types/src/simple_vote.rs:153:19
|
153 | Self::commit(&data, view).as_ref(),
| ^^^^^^ multiple `commit` found
|
note: candidate #1 is defined in an impl of the trait `Vote` for the type `SimpleVote<TYPES, DATA>`
--> crates/types/src/simple_vote.rs:164:5
|
164 | fn commit(data: &DATA, view: TYPES::Time) -> Commitment<Self> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `Committable` for the type `SimpleVote<TYPES, DATA>`
--> crates/types/src/simple_vote.rs:173:5
|
173 | fn commit(&self) -> Commitment<Self> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: use fully-qualified syntax to disambiguate
|
153 | <SimpleVote<TYPES, DATA> as Vote>::commit(&data, view).as_ref(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
153 | Committable::commit(&data, view).as_ref(),
| ~~~~~~~~~~~~~
crates/types/src/vote.rs
Outdated
/// Gets the (Data + view number) commitment of the vote | ||
fn vote_commitment(&self) -> Commitment<Self>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as other comment if Vote
must be committable then this is redundant right?
crates/types/src/vote.rs
Outdated
@@ -156,7 +177,7 @@ impl<TYPES: NodeType, VOTE: Vote<TYPES>, CERT: Certificate<TYPES, Voteable = VOT | |||
|
|||
// TODO: Get the stake from the stake table entry. | |||
*total_stake_casted += stake_table_entry.stake(); | |||
total_vote_map.insert(key, (vote.signature(), vote.date_commitment())); | |||
total_vote_map.insert(key, (vote.signature(), vote.vote_commitment())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think now that we are calculating a commitment on this call we should not be calling vote_commitment
repeatedly and just reuse the value already calculated above
crates/types/src/vote.rs
Outdated
@@ -173,8 +194,8 @@ impl<TYPES: NodeType, VOTE: Vote<TYPES>, CERT: Certificate<TYPES, Voteable = VOT | |||
); | |||
|
|||
let cert = CERT::create_signed_certificate( | |||
vote.date_commitment(), | |||
vote.date().clone(), | |||
vote.commit(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, we already calculated this value, lets not calculated it again
@@ -94,22 +116,21 @@ impl<TYPES: NodeType, VOTEABLE: Voteable + Committable, THRESHOLD: Threshold<TYP | |||
}; | |||
committable::RawCommitmentBuilder::new("Certificate") | |||
.field("data", self.data.commit()) | |||
.field("vote_commitment", self.vote_commitment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we leave this in? It would be easier, because we wouldn't have to do any version switch on the certificate
Can we close this now? |
Closes #3520
This PR:
This PR tries to improve a vote and a certificate validation. The existing problem is that both a view and a certificate include a view number. These view numbers are not signed in any way and can be easily spoofed by a malicious actor, e.g. a valid certificate can be received by a malicious leader which can then re-send the received certificate with changed view numbers. These will be accepted as valid certificates by the other nodes and can wreck havoc on the consensus.
The approach taken by this PR is following:
Changes in the
Vote
trait:The
Vote
trait is nowCommittable
on its own (not only its data). Explained below forSimpleVote
.create_signed_vote
is now a trait associated function.Changes to
SimpleVote
:vote_commitment
calculates the commitment for the whole vote (data + view number).There's a new private associated function called
commit
which calculates the commit based on the data and the view number. This method is used increate_signed_vote
and in thecommit
method.Changes to
SimpleCertificate
:The
vote_commitment
field is now private and should not be used in any way. There is anew
associated function which created a certificate.vote_commitment
method calculates and returns the vote's commitment instead of returning the stored the commitment. This means the commitment is calculated from the data and the view number.is_valid_cert
now correctly checks the received signature against the locally calculated commitment which includes the vote's data and the view number.This PR does not:
Key places to review:
The crucial parts are in:
is_valid_cert
method andvote_commitment
method of theSimpleCertificate
.