-
Notifications
You must be signed in to change notification settings - Fork 24
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
Replace redundant BLS in QBFT with RSA #372
Conversation
qbft/commit.go
Outdated
if !signedCommit.CheckSignersInCommittee(operators) { | ||
return errors.New("signers not in committee") | ||
} |
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 wondering if MsgValidation also takes care of it?
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 it does. But I'm leaving it here because spec doesn't know and it's a light check
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 we should NEVER assume something happens in msg validation hence we don't need to take care in consensus.
qbft/prepare.go
Outdated
if !signedPrepare.CheckSignersInCommittee(operators) { | ||
return errors.New("signers not in committee") | ||
} |
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.
just from sw engineering point of view
shouldn't we have some baseValidation
that keeps on calling this
so we never forget to add this?
because it repeats itself
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 we should totally do that. Not only for this check but for many others such as height, msg.Validate(), etc. Since it would require some bigger refactoring, let's do it in a future PR? Just so we can dispatch this to implementation as soon as possible and as simple as possible.
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.
Approved but you can still fix my comment
qbft/round_change.go
Outdated
@@ -213,6 +214,7 @@ func validRoundChangeForData( | |||
height Height, | |||
round Round, | |||
fullData []byte, | |||
verifySignature bool, |
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.
switch to dontVerifySignature?
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.
on another thought, maybe we should go for the validateX() and validateXNoVerify() pattern?
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.
Agree
qbft/commit.go
Outdated
@@ -127,7 +135,7 @@ func validateCommit( | |||
proposedMsg *SignedMessage, | |||
operators []*types.Operator, | |||
) error { | |||
if err := baseCommitValidation(config, signedCommit, height, operators); err != nil { | |||
if err := baseCommitValidation(config, signedCommit, height, operators, false); err != nil { |
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.
if we go for the NoVerify suffix pattern, validateCommit could be validateCommitNoVerify to be explicit
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.
Agree
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.
Let's merge this after #345 is on main
qbft/commit.go
Outdated
@@ -94,7 +95,7 @@ func CreateCommit(state *State, config IConfig, root [32]byte) (*SignedMessage, | |||
return signedMsg, nil | |||
} | |||
|
|||
func baseCommitValidation( | |||
func baseCommitValidationNoVerification( |
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.
the config is not used here anymore, should we delete it?
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.
Indeed. I'll remove it. Thanks
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 just wonder if other readers know that "verification" we mean signatures...
I barely know this 😅
Maybe "IgnoreSignature" is better?
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 decided to finally put an end to my questioning of the usages between the 2 terms and googled this:
https://www.connect2id.com/blog/validate-vs-verify-in-the-context-of-cryptography
TLDR: "verify" is the correct word in term of cryptography but "the Internet security community sometimes uses these two terms inconsistently"...
So I think "IgnoreSignature" is better because it won't confuse anyone
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. "IgnoreSignature" is more clear. I'll update it. Thanks!
qbft/prepare.go
Outdated
@@ -79,7 +80,7 @@ func getRoundChangeJustification(state *State, config IConfig, prepareMsgContain | |||
|
|||
// validSignedPrepareForHeightRoundAndRoot known in dafny spec as validSignedPrepareForHeightRoundAndDigest | |||
// https://entethalliance.github.io/client-spec/qbft_spec.html#dfn-qbftspecification | |||
func validSignedPrepareForHeightRoundAndRoot( | |||
func validSignedPrepareForHeightRoundAndRootNoVerification( |
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.
the config is not used here anymore, should we delete it?
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.
Indeed. I'll remove it. Thanks
if err := signedMsg.Validate(); err != nil { | ||
return errors.Wrap(err, "roundChange invalid") |
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.
just to understand, it was a bug that we validated only the inner 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.
I think so
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.
if so, we have to add a test for message signers is empty
, non unique signer
& signer ID 0 not allowed
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.
Yup, adding these tests.
Btw, the previous version couldn't produce any errors because, even though it should be signedMsg.Validate()
and not signedMsg.Message.Validate()
, the other checks were also verifying the conditions. But, of course, still better to call signedMsg.Validate()
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.
It wasn't a bug that we only called the inner check..
It is actually a duplicate call as a shrewed dev once noticed in #326
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.
It is not a bug, but all other base validation functions for the other message types call validate on the upper message, and this is the only inconsistent one. Even though, all of these calls are indeed duplicated 😅
qbft/commit.go
Outdated
@@ -127,7 +145,7 @@ func validateCommit( | |||
proposedMsg *SignedMessage, | |||
operators []*types.Operator, | |||
) error { | |||
if err := baseCommitValidation(config, signedCommit, height, operators); err != nil { | |||
if err := baseCommitValidationNoVerification(signedCommit, height, operators); err != nil { |
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.
one more cleanup can be done here
to not pass the config
to validateCommit
@@ -16,10 +17,14 @@ type testingOperatorSigner struct { | |||
|
|||
var ( | |||
testingOperatorSignerInstance *testingOperatorSigner | |||
opMu sync.Mutex |
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.
why?
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.
The lack of it randomly produces errors around tests
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.
do we have many different instances with different keys?
if not maybe singleton is better?
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 is the singleton
qbft/state.go
Outdated
} | ||
|
||
// GetSigner returns a Signer instance | ||
func (c *Config) GetSigner() types.SSVSigner { | ||
func (c *Config) GetSigner() types.SSVShareSigner { |
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.
nit:
you can also rename the function
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, better. Thanks
ssv/attester.go
Outdated
func (r *AttesterRunner) GetOperatorSigner() types.SSVOperatorSigner { | ||
return r.operatorSigner | ||
} | ||
|
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 runner use the signer?
in the end we use the network to sign it
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.
Indeed. Removing it. Thanks!
qbft/state.go
Outdated
@@ -31,7 +31,7 @@ type IConfig interface { | |||
} | |||
|
|||
type Config struct { | |||
Signer types.SSVShareSigner | |||
SSVShareSigner types.SSVShareSigner |
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 should probably be just ShareSigner
like below, and the same for method names
tbh im not sure that we even need the word 'SSV' in the structs, but it's not too bad, lmk what you think @GalRogozinski @MatheusFranco99
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 agree the word SSV
here is redundant. I'll remove it, thx
qbft/round_change.go
Outdated
return nil | ||
} | ||
|
||
func validRoundChangeForDataWithVerification( |
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 don't want to nit pick on the naming, but WithVerification
and IgnoreSignature
don't sound related to each other when and fact the functionality should be identical with one difference.
I would go with either WithVerification
and NoVerification
or IgnoreSignature
and WithSignature
/VerifySignature
but leaving it like now is a bit confusing 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.
Definitely. Forgot about that. Thanks!
types/signer.go
Outdated
// AddSSVOperatorKey saves an SSV operator key | ||
AddSSVOperatorKey(sk *rsa.PrivateKey) error | ||
// RemoveSSVOperatorKey removes an SSV operator key | ||
RemoveSSVOperatorKey(pubKey string) |
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.
These (Add/Remove) methods are redundant, don't make us satisfy this interface (😆); There is always just a single operator key per instance, so there's never a need to add and remove them.
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.
some small stuff.
ssv/validator.go
Outdated
// Validate message | ||
if err := signedSSVMessage.Validate(); err != nil { | ||
return errors.Wrap(err, "invalid SignedSSVMessage") | ||
} | ||
|
||
// Decode the nested SSVMessage | ||
msg := &types.SSVMessage{} | ||
if err := msg.Decode(signedSSVMessage.Data); err != nil { | ||
return errors.Wrap(err, "could not decode data into an SSVMessage") | ||
} | ||
|
||
// Verify SignedSSVMessage's signature | ||
if err := v.SignatureVerifier.Verify(signedSSVMessage, v.Share.Committee); err != nil { | ||
return errors.Wrap(err, "SignedSSVMessage has an invalid 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.
we should probably decode after verifying
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.
Agree. Thanks!
types/messages.go
Outdated
@@ -191,7 +191,7 @@ func (msg *SignedSSVMessage) Decode(data []byte) error { | |||
// - Data length should not be 0 | |||
func (msg *SignedSSVMessage) Validate() error { | |||
if msg.OperatorID == 0 { | |||
return errors.New("OperatorID in SignedSSVMessage is 0") | |||
return errors.New("signer ID 0 not allowed") | |||
} | |||
if len(msg.Signature) == 0 { |
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.
is there a reason to not require the expected length of RSA signatures, or is that not the right place to do so, or not the right assumption for the spec to make?
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.
Actually, in the beginning, I wanted to let it open. But I've also added the custom decoding function to be used by implementation that sets it to 256 bytes
signatureSize = 256
signature := encoded[signatureOffset : signatureOffset+signatureSize]
I have no opinion right now on whether we should check it here or leave the current check. 🤔
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.
@GalRogozinski any reason why we shouldn't check that len(sig) == signatureSize
in spec?
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.
When you decode the data from pubsub you create a byte array of the expected length
maybe change SignedMessage
to have Signature [256]byte
btw @MatheusFranco99 I remember we said the encoded length of the sig we use is actually greater correct?
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, I'll change it to 256 bytes
This was about an encoded key. For the signature, it's indeed 256
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.
just final nits and that's it
type KeyManager interface { | ||
BeaconSigner | ||
SSVSigner | ||
ShareSigner | ||
// AddShare saves a share key | ||
AddShare(shareKey *bls.SecretKey) error | ||
// RemoveShare removes a share key |
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.
heads up for KeyManager...
I think it should be removed either in this PR or the next one (imo do it the next one so we can close this one quickly)
there can still be a KeyManager struct in tests or impl that simply implements them both
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, other PR
@@ -16,10 +17,14 @@ type testingOperatorSigner struct { | |||
|
|||
var ( | |||
testingOperatorSignerInstance *testingOperatorSigner | |||
opMu sync.Mutex |
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.
do we have many different instances with different keys?
if not maybe singleton is better?
} | ||
|
||
func MsgValidation(runner ssv.Runner, msgValidator MessageValidator) MsgValidatorFunc { | ||
func MsgValidation(runner ssv.Runner) MsgValidatorFunc { |
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.
do you see value to keep this file here for now?
if no, you may delete it
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.
doesn't have to be in this PR and not a blocker
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.
Future PR
) | ||
|
||
// InconsistentNetworkSigner tests a SignedSSVMessage with inconsistent signer regarding its nested SignedPartialSignatureMessage | ||
func InconsistentNetworkSigner() tests.SpecTest { |
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.
change network to operator on all tests I think
Q: are we signing rsa in consensus ? If we are verifying the we should probably also sign. |
In spec we sign in validator.go |
Overview
This PR adds an RSA check in the
Validator.ProcessMessage
function and drops redundant BLS signature checks in QBFT.Changes
ProcessMessage
function signature to receiveSignedSSVMessage
instead ofSSVMessage
and add an RSA signature checkImprovement
New total duty time per number of consensus rounds.
New consensus n-th round time.