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

Replace redundant BLS in QBFT with RSA #372

Merged
merged 40 commits into from
Apr 9, 2024

Conversation

MatheusFranco99
Copy link
Contributor

@MatheusFranco99 MatheusFranco99 commented Mar 27, 2024

Overview

This PR adds an RSA check in the Validator.ProcessMessage function and drops redundant BLS signature checks in QBFT.

Changes

  • QBFT: Drop redundant checks for Proposal, Prepare, Commit and Round-Change messages
  • QBFT: Keep checks for message justification and decided messages
  • QBFT: Add a check to ensure that the signers belong to the committee
  • Operator: Add SSV PublicKey
  • Validator: Change the ProcessMessage function signature to receive SignedSSVMessage instead of SSVMessage and add an RSA signature check

Improvement

New total duty time per number of consensus rounds.

1 Round 2 Rounds 3 Rounds
67% 70% 71%

New consensus n-th round time.

1st Round 2nd Round 3rd Round
21% 73% 73%

qbft/commit.go Outdated
Comment on lines 116 to 118
if !signedCommit.CheckSignersInCommittee(operators) {
return errors.New("signers not in committee")
}
Copy link
Contributor

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?

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 think it does. But I'm leaving it here because spec doesn't know and it's a light check

Copy link
Contributor

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
Comment on lines 114 to 116
if !signedPrepare.CheckSignersInCommittee(operators) {
return errors.New("signers not in committee")
}
Copy link
Contributor

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

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 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.

Copy link
Contributor

@GalRogozinski GalRogozinski left a 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

@MatheusFranco99 MatheusFranco99 changed the title Reduce signatures - drop redundant BLS in QBFT (no fork) Reduce signatures - drop redundant BLS in QBFT Mar 27, 2024
@@ -213,6 +214,7 @@ func validRoundChangeForData(
height Height,
round Round,
fullData []byte,
verifySignature bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

switch to dontVerifySignature?

Copy link
Contributor

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?

Copy link
Contributor Author

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 {
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree

Copy link
Contributor

@moshe-blox moshe-blox left a 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(
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor Author

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(
Copy link
Contributor

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?

Copy link
Contributor Author

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

Comment on lines +231 to +232
if err := signedMsg.Validate(); err != nil {
return errors.Wrap(err, "roundChange invalid")
Copy link
Contributor

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?

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 think so

Copy link
Contributor

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

Copy link
Contributor Author

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()

Copy link
Contributor

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

Copy link
Contributor Author

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 {
Copy link
Contributor

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

@olegshmuelov
Copy link
Contributor

since #342 was merged already

can we fix small issue in the current pr?

@GalRogozinski
Copy link
Contributor

since #342 was merged already

can we fix small issue in the current pr?

This PR doesn't change runner at all...
Maybe in a subsequent PR when we change runner

@olegshmuelov
Copy link
Contributor

since #342 was merged already
can we fix small issue in the current pr?

This PR doesn't change runner at all... Maybe in a subsequent PR when we change runner

created an issue
#376

@@ -16,10 +17,14 @@ type testingOperatorSigner struct {

var (
testingOperatorSignerInstance *testingOperatorSigner
opMu sync.Mutex
Copy link
Contributor

Choose a reason for hiding this comment

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

why?

Copy link
Contributor Author

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

Copy link
Contributor

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?

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 the singleton

qbft/state.go Outdated
}

// GetSigner returns a Signer instance
func (c *Config) GetSigner() types.SSVSigner {
func (c *Config) GetSigner() types.SSVShareSigner {
Copy link
Contributor

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

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, better. Thanks

ssv/attester.go Outdated
Comment on lines 233 to 236
func (r *AttesterRunner) GetOperatorSigner() types.SSVOperatorSigner {
return r.operatorSigner
}

Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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

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 agree the word SSV here is redundant. I'll remove it, thx

return nil
}

func validRoundChangeForDataWithVerification(
Copy link
Contributor

@y0sher y0sher Apr 7, 2024

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

Copy link
Contributor Author

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
Comment on lines 40 to 43
// AddSSVOperatorKey saves an SSV operator key
AddSSVOperatorKey(sk *rsa.PrivateKey) error
// RemoveSSVOperatorKey removes an SSV operator key
RemoveSSVOperatorKey(pubKey string)
Copy link
Contributor

@y0sher y0sher Apr 7, 2024

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.

Copy link
Contributor

@y0sher y0sher left a 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
Comment on lines 54 to 68
// 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")
}
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree. Thanks!

@@ -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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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. 🤔

Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

@MatheusFranco99 MatheusFranco99 Apr 9, 2024

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

Copy link
Contributor

@GalRogozinski GalRogozinski left a 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

Comment on lines 55 to 60
type KeyManager interface {
BeaconSigner
SSVSigner
ShareSigner
// AddShare saves a share key
AddShare(shareKey *bls.SecretKey) error
// RemoveShare removes a share key
Copy link
Contributor

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

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, other PR

@@ -16,10 +17,14 @@ type testingOperatorSigner struct {

var (
testingOperatorSignerInstance *testingOperatorSigner
opMu sync.Mutex
Copy link
Contributor

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 {
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor Author

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 {
Copy link
Contributor

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

@y0sher
Copy link
Contributor

y0sher commented Apr 9, 2024

Q: are we signing rsa in consensus ? If we are verifying the we should probably also sign.

@GalRogozinski
Copy link
Contributor

GalRogozinski commented Apr 9, 2024

Q: are we signing rsa in consensus ? If we are verifying the we should probably also sign.

In spec we sign in validator.go
In impl from my POV you should sign where you pass all spec tests

@GalRogozinski GalRogozinski merged commit 83d5ab4 into main Apr 9, 2024
2 checks passed
@GalRogozinski GalRogozinski deleted the reduce_bls_signatures_qbft_no_fork branch April 9, 2024 14:49
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.

5 participants