-
Notifications
You must be signed in to change notification settings - Fork 186
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
Signing policy + optional Signature, From and Seqno #359
Conversation
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.
Thank you! This looks great at first glance, I'll take a closer look tomorrow morning.
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 need to handle the unexpected signature in the score tracer.
Also, it might make sense to enforce empty from/seqno when we operate in anoymous mode to defend against and adversary stuffing garbage in those fields.
Some concerns:
|
Hrm, the test passes on travis and wfm; maybe there is some non-determinism. |
cc @raulk |
The test that fails locally:
connect(t, h[0], h[1])
connect(t, h[0], h[2])
// verify that the direct peers connected
time.Sleep(2 * time.Second)
if len(h[1].Network().ConnsToPeer(h[2].ID())) == 0 {
t.Fatal("expected a connection between direct peers")
} Looks like it's a timing thing that misses, and unrelated to this PR. Edit: increasing the two sleep statements before expectations to 10s worked. Flaky test. |
I think this is where things go wrong with the flaky test: Line 1529 in aabbdb1
New go routines are started to make connections, and the connections are not awaited (no waitgroup). At the same time, maybe that is desirable, to not halt the heartbeat loop. Waiting for it in a test is not ideal though. And I wonder what happens next heartbeat, does it just repeatedly try to connect? Is that what the ticking is for?\ Edit: if 1 tick is 1 heartbeat is 1 second, then 2 ticks to try 2nd connect attempt will be just enough or not, depending on go routine order. And the first attempt |
Yeah, we can't block the event loop. It retries every few ticks, with an initial spawn. |
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, this looks good to me.
So, regardless of the very interesting issues you raise, codewise this is ready to be merged. |
I am going to merge it but not yet tag a release. |
With respect to the seqno...not only can it be used to fingerprint nodes, but the fact that it is incremented with each new gossip message authored allows attackers to approximate how many validators a node is running (in the case of eth2). |
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.
Nice, just one tiny comment.
} | ||
if t.p.signID != "" { | ||
m.From = []byte(t.p.signID) | ||
m.Seqno = t.p.nextSeqno() |
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.
Note: I don't think whether we set or not the Seqno
is correlated with whether we send or not the source peer ID, rather with whether we have a custom MessageIdFn
or not.
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.
Yes, we can introduce another option for it, but the thought here is that seq-no makes little sense without a message author (since everyone can claim the seq-no for any message data), so it's just left out.
There's some polishing that can be done. I appreciate the quick PR merge, but would have welcomed more feedback/discussion. Reviewing the update path for eth2 clients would also help. Implementations all behave a little different, and this is the chance to plan signing functionality (non-eth2, but relevant) for those that don't have it yet, make verification strict yes/no, and align everything. I am tracking behavioral differences in a table in the eth2-specs issue here: ethereum/consensus-specs#1981 (comment)
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.
Sure, that's why I haven't cut a release yet -- we can iterate on it.
This PR proposes new non-breaking PubSub options, to force stricter validation (avoid hypothethical network split), and avoid privacy problems in Eth2.
Why
Privacy
The current gossip message ID is purely based on a hash of the contents, but it is still wrapped in a protobuf that carries
From
,Seqno
andSignature
.The
From
andSeqno
affect privacy: we don't need, or want, the original source of the message to be known. Currently, I believe that if messages are not re-published,but propagated, that at least in the Go implementation these details remain in the gossip message.
While
From
is problematic (and previously known to be, just not fixed by anyone),Seqno
alone is also problematic, since (in Go at least) it is initialized as nanosecond time of the node, and then only increments by 1. Because of the slow non-random increase on top of a big number, it's effectively a unique identifier of the origin, embedded in every message.This could be used to quickly correlate messages, and narrow down which validators (based on message contents) run on which nodes.
Network split
The "Signature" is not really used, and empty. However, the Go implementation seems to validate it anyway, if it is non-empty.
Now other gossip implementations don't use it at all, or have a stalled PR open that implements similar behavior.
In our case, the signature is dangerous, because it can make different nodes mislike eachother:
Changes
Loosely based on discussion with @raulk:
MessageSignaturePolicy
enum:WithStrictSignatureVerification
andWithMessageSigning
to use the enum. This refactors out the logic away from the function, and into the constructor (but minimal). This avoids an unnecessary peerstore private-key lookup (getting the host private key when not using it as signing key)WithMessageSignaturePolicy
to set the singing policy. I have doubts here, alternatively we could not deprecateWithMessageSigning
, and eventually just say that the verification bool is always on.not signing && verification
means that signatures must be nil to be valid.pushMsg
now checks if the signature is nil, given the right circumstances (and added a trace for it)WithNoAuthor
option, to not sign any messages, and omit any origin data (seq no and signer identity)pb.Message
Key
attribute, but might need to be omitted or handled as well?signID
is nil, the signing option is disabled: you can't be not signing while also requiring signatures. (matches previous "non sensical option" check in constructor). Instead of returning an error I am disabling the signing now. But maybe it should just error instead?Message.From
should be set to the signer, not the current host (since they may be different, and potentially it is used for signature checking via key extraction, unlessKey
is set?).Any feedback welcome, I can make changes, or change the approach.