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

Identify invalid signature within batch verification (#11582) #11741

Merged

Conversation

dyng
Copy link
Contributor

@dyng dyng commented Dec 8, 2022

What type of PR is this?

Feature

What does this PR do? Why is it needed?

The signatures of newly received blocks are collected and verified as a batch, while it's perfect if everything is ok, we can't tell which signature is invalid if batch verification fails.

Which issues(s) does this PR fix?

Fixes #11582

Other notes for review

This PR is not yet complete because I need to discuss with reviewer to determine where VerifyVerbosely is supposed to be used.

@dyng dyng requested a review from a team as a code owner December 8, 2022 07:53
@dyng
Copy link
Contributor Author

dyng commented Dec 11, 2022

@terencechain could you have some time to review this PR? I'm not sure if VerifyVerbose is a right approach to original issue. If so, I'll then replace Verify to VerifyVerbosely in ExecuteStateTransition and onBlockBatch. Thanks.

@@ -216,11 +218,14 @@ func createAttestationSignatureBatch(
return nil, errors.Wrap(err, "could not get signing root of object")
}
msgs[i] = root

descs[i] = "attestation signature"
Copy link
Member

Choose a reason for hiding this comment

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

Instead of hard-coding these descriptions, maybe we can use enums

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 looks better, thanks!

@terencechain
Copy link
Member

@terencechain could you have some time to review this PR? I'm not sure if VerifyVerbose is a right approach to original issue. If so, I'll then replace Verify to VerifyVerbosely in ExecuteStateTransition and onBlockBatch. Thanks.

I dont think VerifyVerbosely should be the default but rather a feature flag --verify-sig-verbose or something similar

@@ -25,6 +32,11 @@ func (s *SignatureBatch) Join(set *SignatureBatch) *SignatureBatch {
s.Signatures = append(s.Signatures, set.Signatures...)
s.PublicKeys = append(s.PublicKeys, set.PublicKeys...)
s.Messages = append(s.Messages, set.Messages...)
if s.Descriptions != nil && set.Descriptions != nil {
Copy link
Member

Choose a reason for hiding this comment

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

Remove this nil check, since all signature sets now have a description attached to 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.

Because SignatureBatch is such a fundamental struct so I hesitated to make a big change, then ended up a compromised solution that SignatureBatch can have either full description list or nil. But I think your comment is absolutely correct, it should be far more elegant if every signature has a description attached. I'll make the change as you commented. Thanks for your advice!

if err != nil {
_, e := fmt.Fprintf(&sb, "signature '%s' is invalid."+
" signature: %v, public key: %v, message: %v, error: %v\n", desc, sig, pubKey, msg, err)
if e != nil {
Copy link
Member

Choose a reason for hiding this comment

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

don't ignore errors here, also why are we using Fprintf here ? Wrap the error and then return 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.

Here I used strings.Builder for better performance (not a big problem though). strings.Builder follows StringWriter interface that emits error but actually always returns nil as error, so I decided to ignore it. I'll consider to use string concatenation instead.

valid, err := VerifySignature(sig, msg, pubKey)
if !valid {
var desc string
if len(s.Descriptions) != len(s.Signatures) {
Copy link
Member

Choose a reason for hiding this comment

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

These checks are going to be ugly and a source of bugs, it would be much better to always have the same number of descriptions and signatures in a set.

}, nil
}

// verifies the signature from the raw data, public key and domain provided.
func verifySignature(signedData, pub, signature, domain []byte) error {
set, err := signatureBatch(signedData, pub, signature, domain)
set, err := signatureBatch(signedData, pub, signature, domain, "")
Copy link
Member

Choose a reason for hiding this comment

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

better to name it as "unknown" rather than an empty string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As @terencechain's comment, I'll use enums instead. Thanks!

}
} else {
_, e := fmt.Fprintf(&sb, "signature '%s' is invalid."+
" signature: %v, public key: %v, message: %v\n", desc, sig, pubKey, msg)
Copy link
Member

Choose a reason for hiding this comment

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

Better to represent msg as hex rather than an array of bytes when printing the error message. Same applies to signature and public keys. Also you need to marshal the public key into bytes otherwise you end up printing pointers to the struct which isn't useful

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I'll make error message more readable.

@@ -82,6 +145,10 @@ func (s *SignatureBatch) RemoveDuplicates() (int, *SignatureBatch, error) {
sigs := s.Signatures[:0]
pubs := s.PublicKeys[:0]
msgs := s.Messages[:0]
var descs []string
if s.Descriptions != nil {
Copy link
Member

Choose a reason for hiding this comment

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

same thing here, do not have a nil check here. The correct thing to do would be to make sure all signature sets always have an attached description.

@dyng dyng changed the title [WIP] Identify invalid signature within batch verification (#11582) Identify invalid signature within batch verification (#11582) Dec 13, 2022
@dyng
Copy link
Contributor Author

dyng commented Dec 13, 2022

I have done the changes, please take a review when you have time. Thanks! @nisdas @terencechain

@dyng dyng force-pushed the feature-individual-signature-verification branch from 1295f6e to fb62cdd Compare December 13, 2022 09:20
@@ -5,3 +5,31 @@ const DomainByteLength = 4

// CurveOrder for the BLS12-381 curve.
const CurveOrder = "52435875175126190479447740508185965837690552500527637822603658699938581184513"

// List of descriptions for different kinds of signatures
const (
Copy link
Member

Choose a reason for hiding this comment

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

This is the wrong place to declare these descriptions. The bls library should have no knowledge of what is an application construct( bls change, randao, etc)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for your comment! I have moved these constants to signing package, but left AggregatedSignature in bls package as it is a local knowledge belongs to bls package.

@@ -140,6 +195,7 @@ func (s *SignatureBatch) AggregateBatch() (*SignatureBatch, error) {
b.PublicKeys = []PublicKey{aggPub}
b.Signatures = [][]byte{aggSig.Marshal()}
b.Messages = [][32]byte{copiedRt}
b.Descriptions = []string{AggregatedSignature}
Copy link
Member

Choose a reason for hiding this comment

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

one worry is that you get information loss with this, since you lose the ability to determine which type of signature was invalid. Its instead just a generic AggregatedSignature Usually messages with the same root, will reference the same types of messages(attestations,sync committee,etc) . But we can maybe fix this in a follow up PR.

Copy link
Contributor Author

@dyng dyng Dec 16, 2022

Choose a reason for hiding this comment

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

I thought about the information loss, but still used a generic description here for two reason:

  1. the signatures themself are aggregated and we can hardly identify invalid individual signature.
  2. AggregateBatch() is called in validateWithBatchVerifier() but not VerifyVerbosely(), currently only VerifyVerbosely() is responsible to nail down failed signature.

But I'm not sure if I have fully understood the whole picture of block processing (hence original issue #11582), correct me if I am wrong! Thanks! Maybe as you said, we can fix this later.

@@ -119,12 +172,14 @@ func (s *SignatureBatch) AggregateBatch() (*SignatureBatch, error) {
currBatch.Signatures = append(currBatch.Signatures, s.Signatures[i])
currBatch.Messages = append(currBatch.Messages, s.Messages[i])
currBatch.PublicKeys = append(currBatch.PublicKeys, s.PublicKeys[i])
currBatch.Descriptions = append(currBatch.Descriptions, s.Descriptions[i])
Copy link
Member

Choose a reason for hiding this comment

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

we should also do a length check for number of descriptions, otherwise this will panic

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 have added length validation for descriptions. Thanks for the comment!

I reordered the empty check and length check because I think length mismatch of signatures and messages should always be an error even in case that signatures are empty and messages are non-empty.

nisdas
nisdas previously approved these changes Dec 19, 2022
Copy link
Member

@nisdas nisdas left a comment

Choose a reason for hiding this comment

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

Thanks for addressing all the comments, great work on this feature !

@nisdas
Copy link
Member

nisdas commented Dec 19, 2022

Build is failing @dyng

crypto/bls/signature_batch_test.go:657:9: Unhandled error for function call  (errcheck)
crypto/bls/signature_batch_test.go:681:9: Unhandled error for function call  (errcheck)

@dyng
Copy link
Contributor Author

dyng commented Dec 20, 2022

@nisdas I have fixed build failure, sorry for that (I should build locally at first). Thanks for your review and precious comments!

@nisdas nisdas merged commit e431521 into prysmaticlabs:develop Dec 20, 2022
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.

Add capability to narrow down to which signature fails
3 participants