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
refactor!: don't send public key with signature #4518
base: main
Are you sure you want to change the base?
Conversation
cee8760
to
df420c1
Compare
df420c1
to
120c0df
Compare
120c0df
to
f66a58f
Compare
dc4d7d7
to
3701fc5
Compare
bdb903e
to
fb852e9
Compare
d1c63f2
to
9edd012
Compare
if additional_leader_signatures.next().is_some() { | ||
return Err(SignatureVerificationError::DuplicateSignatures { | ||
signatory: leader_index, | ||
}); | ||
} |
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 have concern about this check, can't malicious peer insert wrong index and valid signature (for random key pair)?
So that it looks like leader submitted multiple signatures.
This way malicous peer can't prevent safety of blockchain, but can violate liveliness.
Maybe we can require that at least one signature should be from leader?
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.
malicious peer can send all sorts of invalid blocks. What you're saying is true, but is no less dangerous than sending other types of invalid blocks. We just reject a block sent from this peer, there is no harm in that IMO
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 what @Erigara is saying is that the block validation will globally and repeatedly fail unless it has a mechanism to identify and ignore (or expel from the validator set) the peer that spoof index zero with its signature.
The problem here would be trusting the index without checking it against the signature
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 what @Erigara is saying is that the block validation will globally and repeatedly fail unless it has a mechanism to identify and ignore (or expel from the validator set) the peer that spoof index zero with its signature.
spoofing 0 index is just one way in which malicious peer can affect consensus. Worst case, malicious peer can withhold sending any messages to other peers. It will only fail consensus repeatedly until the malicious peer becomes observing peer. 0 index spoofing is no special case and I don't think we should be more forgiving in this case
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.
Ok, at least this is not a problem specific to this PR. It used to trust public keys instead of indices
211e87a
to
ad1aa08
Compare
ad1aa08
to
1479b74
Compare
(BlockMessage::BlockSigned(BlockSigned { signatures }), Role::ProxyTail) => { | ||
info!( | ||
peer_id=%self.peer_id, | ||
role=%self.role(), | ||
"Received block signatures" | ||
); | ||
|
||
let roles: &[Role] = if current_view_change_index >= 1 { | ||
let roles: &[Role] = if view_change_index >= 1 { | ||
&[Role::ValidatingPeer, Role::ObservingPeer] | ||
} else { | ||
&[Role::ValidatingPeer] | ||
}; | ||
let valid_signatures = | ||
current_topology.filter_signatures_by_roles(roles, &signatures); | ||
|
||
if let Some(voted_block) = voting_block.as_mut() { | ||
let voting_block_hash = voted_block.block.as_ref().hash_of_payload(); | ||
|
||
if hash == voting_block_hash { | ||
add_signatures::<true>(voted_block, valid_signatures); | ||
} else { | ||
debug!(%voting_block_hash, "Received signatures are not for the current block"); | ||
} | ||
let valid_signatures = self | ||
.topology | ||
.filter_signatures_by_roles(roles, &signatures) | ||
.cloned(); | ||
|
||
if let Some(mut voted_block) = voting_block.take() { | ||
add_signatures::<true>(&mut voted_block, valid_signatures, &self.topology); | ||
*voting_block = self.try_commit_block(voted_block, is_genesis_peer); | ||
} else { | ||
// NOTE: Due to the nature of distributed systems, signatures can sometimes be received before | ||
// the block (sent by the leader). Collect the signatures and wait for the block to be received | ||
voting_signatures.extend(valid_signatures); | ||
} | ||
} |
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.
By looking at this code and try_commit
it look like we don't check that signature indeed belong to the peer.
So any peer (including malicious leader itself) can send BlockSigned
message with random block matching signatures for enough validating/observing peers and block will be committed.
Another problem is that on this path signatures are not checked for duplication.
So as before peer can submit BlockSigned
message with the same index and signature and block will be committed.
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.
ValidBlock
is guaranteed to have all valid signatures except for theProxyTail
signaturevalid_bloc.commit
will check thatProxyTail
signature is validtry_commit
is only invoked by theProxyTail
. You can see there is anassert
statement inside
.block | ||
.commit(&self.topology) | ||
.unpack(|e| self.send_event(e)) | ||
.expect("INTERNAL BUG: Proxy tail failed to commit block"); | ||
|
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.
Maybe instead of unwrapping we can try to commit the block in the if
statement?
I think it would be more reliable in long term if we decide that to commit block it's not enough to just receive enough signatures.
And proxy tail can add it's signature on BlockCreated
message.
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.
this function is only callable by the ProxyTail
and ValidBlock
is guaranteed to have all signatures valid. Consequently, this cannot and must not fail unless the ProxyTail
is itself faulty
if let Err(err) = Self::verify_proxy_tail_signature(self.as_ref(), topology) { | ||
return WithEvents::new(Err((self, err.into()))); | ||
} |
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 probably should check that whole set of sigantures is valid.
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.
ValidBlock
is guaranteed to have all signatures valid except ProxyTail
e366a7d
to
434750f
Compare
Signed-off-by: Marin Veršić <marin.versic101@gmail.com>
Signed-off-by: Marin Veršić <marin.versic101@gmail.com>
ee215e4
to
40f7083
Compare
Signed-off-by: Marin Veršić <marin.versic101@gmail.com>
40f7083
to
e013a1d
Compare
Description
Linked issue
Closes #4393 #4410
Benefits
Checklist
CONTRIBUTING.md