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

* Identify invalid signature within batch verification (#11582)

* Fix issues found by linter

* Make deepsource happy

* Add signature description enums

* Make descriptions a mandatory field

* More readable error message of invalid signatures

* Add 'enable-verbose-sig-verification' option

* Fix format

* Move descriptions to package signing

* Add more validation and test cases

* Fix build failure

Co-authored-by: Nishant Das <nishdas93@gmail.com>
This commit is contained in:
Ye Ding
2022-12-20 18:41:47 +08:00
committed by GitHub
parent 90d5f6097c
commit e43152102e
20 changed files with 486 additions and 131 deletions

View File

@@ -337,11 +337,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []interfaces.SignedBeac
jCheckpoints := make([]*ethpb.Checkpoint, len(blks))
fCheckpoints := make([]*ethpb.Checkpoint, len(blks))
sigSet := &bls.SignatureBatch{
Signatures: [][]byte{},
PublicKeys: []bls.PublicKey{},
Messages: [][32]byte{},
}
sigSet := bls.NewSet()
type versionAndHeader struct {
version int
header interfaces.ExecutionData
@@ -381,7 +377,13 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []interfaces.SignedBeac
}
sigSet.Join(set)
}
verify, err := sigSet.Verify()
var verify bool
if features.Get().EnableVerboseSigVerification {
verify, err = sigSet.VerifyVerbosely()
} else {
verify, err = sigSet.Verify()
}
if err != nil {
return invalidBlock{error: err}
}
@@ -529,11 +531,7 @@ func (s *Service) insertBlockToForkchoiceStore(ctx context.Context, blk interfac
}
}
if err := s.cfg.ForkChoiceStore.InsertNode(ctx, st, root); err != nil {
return err
}
return nil
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, root)
}
// This feeds in the attestations included in the block to fork choice store. It's allows fork choice store

View File

@@ -19,7 +19,7 @@ import (
)
// retrieves the signature batch from the raw data, public key,signature and domain provided.
func signatureBatch(signedData, pub, signature, domain []byte) (*bls.SignatureBatch, error) {
func signatureBatch(signedData, pub, signature, domain []byte, desc string) (*bls.SignatureBatch, error) {
publicKey, err := bls.PublicKeyFromBytes(pub)
if err != nil {
return nil, errors.Wrap(err, "could not convert bytes to public key")
@@ -33,15 +33,16 @@ func signatureBatch(signedData, pub, signature, domain []byte) (*bls.SignatureBa
return nil, errors.Wrap(err, "could not hash container")
}
return &bls.SignatureBatch{
Signatures: [][]byte{signature},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Signatures: [][]byte{signature},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Descriptions: []string{desc},
}, 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, signing.UnknownSignature)
if err != nil {
return err
}
@@ -146,7 +147,7 @@ func RandaoSignatureBatch(
if err != nil {
return nil, err
}
set, err := signatureBatch(buf, proposerPub, reveal, domain)
set, err := signatureBatch(buf, proposerPub, reveal, domain, signing.RandaoSignature)
if err != nil {
return nil, err
}
@@ -186,6 +187,7 @@ func createAttestationSignatureBatch(
sigs := make([][]byte, len(atts))
pks := make([]bls.PublicKey, len(atts))
msgs := make([][32]byte, len(atts))
descs := make([]string, len(atts))
for i, a := range atts {
sigs[i] = a.Signature
c, err := helpers.BeaconCommitteeFromState(ctx, beaconState, a.Data.Slot, a.Data.CommitteeIndex)
@@ -216,11 +218,14 @@ func createAttestationSignatureBatch(
return nil, errors.Wrap(err, "could not get signing root of object")
}
msgs[i] = root
descs[i] = signing.AttestationSignature
}
return &bls.SignatureBatch{
Signatures: sigs,
PublicKeys: pks,
Messages: msgs,
Signatures: sigs,
PublicKeys: pks,
Messages: msgs,
Descriptions: descs,
}, nil
}

View File

@@ -170,9 +170,10 @@ func BLSChangesSignatureBatch(
return bls.NewSet(), nil
}
batch := &bls.SignatureBatch{
Signatures: make([][]byte, len(changes)),
PublicKeys: make([]bls.PublicKey, len(changes)),
Messages: make([][32]byte, len(changes)),
Signatures: make([][]byte, len(changes)),
PublicKeys: make([]bls.PublicKey, len(changes)),
Messages: make([][32]byte, len(changes)),
Descriptions: make([]string, len(changes)),
}
epoch := slots.ToEpoch(st.Slot())
domain, err := signing.Domain(st.Fork(), epoch, params.BeaconConfig().DomainBLSToExecutionChange, st.GenesisValidatorsRoot())
@@ -191,6 +192,7 @@ func BLSChangesSignatureBatch(
return nil, errors.Wrap(err, "could not compute BLSToExecutionChange signing data")
}
batch.Messages[i] = htr
batch.Descriptions[i] = signing.BlsChangeSignature
}
return batch, nil
}

View File

@@ -21,6 +21,32 @@ const DomainByteLength = 4
// failed to verify.
var ErrSigFailedToVerify = errors.New("signature did not verify")
// List of descriptions for different kinds of signatures
const (
// UnknownSignature represents all signatures other than below types
UnknownSignature string = "unknown signature"
// BlockSignature represents the block signature from block proposer
BlockSignature = "block signature"
// RandaoSignature represents randao specific signature
RandaoSignature = "randao signature"
// SelectionProof represents selection proof
SelectionProof = "selection proof"
// AggregatorSignature represents aggregator's signature
AggregatorSignature = "aggregator signature"
// AttestationSignature represents aggregated attestation signature
AttestationSignature = "attestation signature"
// BlsChangeSignature represents signature to BLSToExecutionChange
BlsChangeSignature = "blschange signature"
// SyncCommitteeSignature represents sync committee signature
SyncCommitteeSignature = "sync committee signature"
// SyncSelectionProof represents sync committee selection proof
SyncSelectionProof = "sync selection proof"
// ContributionSignature represents sync committee contributor's signature
ContributionSignature = "sync committee contribution signature"
// SyncAggregateSignature represents sync committee aggregator's signature
SyncAggregateSignature = "sync committee aggregator signature"
)
// ComputeDomainAndSign computes the domain and signing root and sign it using the passed in private key.
func ComputeDomainAndSign(st state.ReadOnlyBeaconState, epoch types.Epoch, obj fssz.HashRoot, domain [4]byte, key bls.SecretKey) ([]byte, error) {
d, err := Domain(st.Fork(), epoch, domain, st.GenesisValidatorsRoot())
@@ -150,10 +176,12 @@ func BlockSignatureBatch(pub, signature, domain []byte, rootFunc func() ([32]byt
if err != nil {
return nil, errors.Wrap(err, "could not compute signing root")
}
desc := BlockSignature
return &bls.SignatureBatch{
Signatures: [][]byte{signature},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Signatures: [][]byte{signature},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Descriptions: []string{desc},
}, nil
}

View File

@@ -37,6 +37,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//beacon-chain/state/stateutil:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",

View File

@@ -17,6 +17,7 @@ import (
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/execution"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v3/config/features"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces"
@@ -66,7 +67,13 @@ func ExecuteStateTransition(
if err != nil {
return nil, errors.Wrap(err, "could not execute state transition")
}
valid, err := set.Verify()
var valid bool
if features.Get().EnableVerboseSigVerification {
valid, err = set.VerifyVerbosely()
} else {
valid, err = set.Verify()
}
if err != nil {
return nil, errors.Wrap(err, "could not batch verify signature")
}

View File

@@ -151,6 +151,18 @@ func TestProcessBlockNoVerifyAnySigAltair_OK(t *testing.T) {
require.Equal(t, true, verified, "Could not verify signature set")
}
func TestProcessBlockNoVerify_SigSetContainsDescriptions(t *testing.T) {
beaconState, block, _, _, _ := createFullBlockWithOperations(t)
wsb, err := blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)
set, _, err := transition.ProcessBlockNoVerifyAnySig(context.Background(), beaconState, wsb)
require.NoError(t, err)
assert.Equal(t, len(set.Signatures), len(set.Descriptions), "Signatures and descriptions do not match up")
assert.Equal(t, "block signature", set.Descriptions[0])
assert.Equal(t, "randao signature", set.Descriptions[1])
assert.Equal(t, "attestation signature", set.Descriptions[2])
}
func TestProcessOperationsNoVerifyAttsSigs_OK(t *testing.T) {
beaconState, block := createFullAltairBlockWithOperations(t)
wsb, err := blocks.NewSignedBeaconBlock(block)

View File

@@ -5,6 +5,7 @@ import (
"testing"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/signing"
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/util"
@@ -16,14 +17,16 @@ func TestValidateWithBatchVerifier(t *testing.T) {
sig := keys[0].Sign(make([]byte, 32))
badSig := keys[1].Sign(make([]byte, 32))
validSet := &bls.SignatureBatch{
Messages: [][32]byte{{}},
PublicKeys: []bls.PublicKey{keys[0].PublicKey()},
Signatures: [][]byte{sig.Marshal()},
Messages: [][32]byte{{}},
PublicKeys: []bls.PublicKey{keys[0].PublicKey()},
Signatures: [][]byte{sig.Marshal()},
Descriptions: []string{signing.UnknownSignature},
}
invalidSet := &bls.SignatureBatch{
Messages: [][32]byte{{}},
PublicKeys: []bls.PublicKey{keys[0].PublicKey()},
Signatures: [][]byte{badSig.Marshal()},
Messages: [][32]byte{{}},
PublicKeys: []bls.PublicKey{keys[0].PublicKey()},
Signatures: [][]byte{badSig.Marshal()},
Descriptions: []string{signing.UnknownSignature},
}
tests := []struct {
name string

View File

@@ -299,9 +299,10 @@ func validateSelectionIndex(
return nil, err
}
return &bls.SignatureBatch{
Signatures: [][]byte{proof},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Signatures: [][]byte{proof},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Descriptions: []string{signing.SelectionProof},
}, nil
}
@@ -326,8 +327,9 @@ func aggSigSet(s state.ReadOnlyBeaconState, a *ethpb.SignedAggregateAttestationA
return nil, err
}
return &bls.SignatureBatch{
Signatures: [][]byte{a.Signature},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Signatures: [][]byte{a.Signature},
PublicKeys: []bls.PublicKey{publicKey},
Messages: [][32]byte{root},
Descriptions: []string{signing.AggregatorSignature},
}, nil
}

View File

@@ -243,9 +243,10 @@ func (s *Service) rejectInvalidSyncCommitteeSignature(m *ethpb.SyncCommitteeMess
// the signature to a G2 point if batch verification is
// enabled.
set := &bls.SignatureBatch{
Messages: [][32]byte{sigRoot},
PublicKeys: []bls.PublicKey{pKey},
Signatures: [][]byte{m.Signature},
Messages: [][32]byte{sigRoot},
PublicKeys: []bls.PublicKey{pKey},
Signatures: [][]byte{m.Signature},
Descriptions: []string{signing.SyncCommitteeSignature},
}
return s.validateWithBatchVerifier(ctx, "sync committee message", set)
}

View File

@@ -236,9 +236,10 @@ func (s *Service) rejectInvalidContributionSignature(m *ethpb.SignedContribution
return pubsub.ValidationReject, err
}
set := &bls.SignatureBatch{
Messages: [][32]byte{root},
PublicKeys: []bls.PublicKey{publicKey},
Signatures: [][]byte{m.Signature},
Messages: [][32]byte{root},
PublicKeys: []bls.PublicKey{publicKey},
Signatures: [][]byte{m.Signature},
Descriptions: []string{signing.ContributionSignature},
}
return s.validateWithBatchVerifier(ctx, "sync contribution signature", set)
}
@@ -292,9 +293,10 @@ func (s *Service) rejectInvalidSyncAggregateSignature(m *ethpb.SignedContributio
return pubsub.ValidationIgnore, err
}
set := &bls.SignatureBatch{
Messages: [][32]byte{sigRoot},
PublicKeys: []bls.PublicKey{aggKey},
Signatures: [][]byte{m.Message.Contribution.Signature},
Messages: [][32]byte{sigRoot},
PublicKeys: []bls.PublicKey{aggKey},
Signatures: [][]byte{m.Message.Contribution.Signature},
Descriptions: []string{signing.SyncAggregateSignature},
}
return s.validateWithBatchVerifier(ctx, "sync contribution aggregate signature", set)
}
@@ -403,9 +405,10 @@ func (s *Service) verifySyncSelectionData(ctx context.Context, m *ethpb.Contribu
return err
}
set := &bls.SignatureBatch{
Messages: [][32]byte{root},
PublicKeys: []bls.PublicKey{publicKey},
Signatures: [][]byte{m.SelectionProof},
Messages: [][32]byte{root},
PublicKeys: []bls.PublicKey{publicKey},
Signatures: [][]byte{m.SelectionProof},
Descriptions: []string{signing.SyncSelectionProof},
}
valid, err := s.validateWithBatchVerifier(ctx, "sync contribution selection signature", set)
if err != nil {

View File

@@ -72,6 +72,8 @@ type Flags struct {
DisableStakinContractCheck bool // Disables check for deposit contract when proposing blocks
EnableVerboseSigVerification bool // EnableVerboseSigVerification specifies whether to verify individual signature if batch verification fails
// KeystoreImportDebounceInterval specifies the time duration the validator waits to reload new keys if they have
// changed on disk. This feature is for advanced use cases only.
KeystoreImportDebounceInterval time.Duration
@@ -257,6 +259,10 @@ func ConfigureBeaconChain(ctx *cli.Context) error {
logEnabled(enableFullSSZDataLogging)
cfg.EnableFullSSZDataLogging = true
}
if ctx.IsSet(enableVerboseSigVerification.Name) {
logEnabled(enableVerboseSigVerification)
cfg.EnableVerboseSigVerification = true
}
Init(cfg)
return nil
}

View File

@@ -132,6 +132,10 @@ var (
Name: "enable-beacon-rest-api",
Usage: "Experimental enable of the beacon REST API when querying a beacon node",
}
enableVerboseSigVerification = &cli.BoolFlag{
Name: "enable-verbose-sig-verification",
Usage: "Enables identifying invalid signatures if batch verification fails when processing block",
}
)
// devModeFlags holds list of flags that are set when development mode is on.
@@ -178,6 +182,7 @@ var BeaconChainFlags = append(deprecatedBeaconFlags, append(deprecatedFlags, []c
enableStartupOptimistic,
disableDefensivePull,
enableFullSSZDataLogging,
enableVerboseSigVerification,
}...)...)
// E2EBeaconChainFlags contains a list of the beacon chain feature flags to be tested in E2E.

View File

@@ -54,6 +54,11 @@ func AggregateCompressedSignatures(multiSigs [][]byte) (common.Signature, error)
return blst.AggregateCompressedSignatures(multiSigs)
}
// VerifySignature verifies a single signature. For performance reason, always use VerifyMultipleSignatures if possible.
func VerifySignature(sig []byte, msg [32]byte, pubKey common.PublicKey) (bool, error) {
return blst.VerifySignature(sig, msg, pubKey)
}
// VerifyMultipleSignatures verifies multiple signatures for distinct messages securely.
func VerifyMultipleSignatures(sigs [][]byte, msgs [][32]byte, pubKeys []common.PublicKey) (bool, error) {
return blst.VerifyMultipleSignatures(sigs, msgs, pubKeys)

View File

@@ -196,6 +196,15 @@ func AggregateSignatures(sigs []common.Signature) common.Signature {
return &Signature{s: signature.ToAffine()}
}
// VerifySignature verifies a single signature using public key and message.
func VerifySignature(sig []byte, msg [32]byte, pubKey common.PublicKey) (bool, error) {
rSig, err := SignatureFromBytes(sig)
if err != nil {
return false, err
}
return rSig.Verify(pubKey, msg[:]), nil
}
// VerifyMultipleSignatures verifies a non-singular set of signatures and its respective pubkeys and messages.
// This method provides a safe way to verify multiple signatures at once. We pick a number randomly from 1 to max
// uint64 and then multiply the signature by it. We continue doing this for all signatures and its respective pubkeys.

View File

@@ -36,6 +36,7 @@ func TestAggregateVerify(t *testing.T) {
msgs = append(msgs, msg)
}
aggSig := AggregateSignatures(sigs)
// skipcq: GO-W1009
assert.Equal(t, true, aggSig.AggregateVerify(pubkeys, msgs), "Signature did not verify")
}
@@ -56,6 +57,7 @@ func TestAggregateVerify_CompressedSignatures(t *testing.T) {
msgs = append(msgs, msg)
}
aggSig := AggregateSignatures(sigs)
// skipcq: GO-W1009
assert.Equal(t, true, aggSig.AggregateVerify(pubkeys, msgs), "Signature did not verify")
aggSig2, err := AggregateCompressedSignatures(sigBytes)
@@ -90,6 +92,29 @@ func TestVerifyCompressed(t *testing.T) {
assert.Equal(t, true, VerifyCompressed(sig.Marshal(), pub.Marshal(), msg), "Compressed signatures and pubkeys did not verify")
}
func TestVerifySingleSignature_InvalidSignature(t *testing.T) {
priv, err := RandKey()
require.NoError(t, err)
pub := priv.PublicKey()
msgA := [32]byte{'h', 'e', 'l', 'l', 'o'}
msgB := [32]byte{'o', 'l', 'l', 'e', 'h'}
sigA := priv.Sign(msgA[:]).Marshal()
valid, err := VerifySignature(sigA, msgB, pub)
assert.NoError(t, err)
assert.Equal(t, false, valid, "Signature did verify")
}
func TestVerifySingleSignature_ValidSignature(t *testing.T) {
priv, err := RandKey()
require.NoError(t, err)
pub := priv.PublicKey()
msg := [32]byte{'h', 'e', 'l', 'l', 'o'}
sig := priv.Sign(msg[:]).Marshal()
valid, err := VerifySignature(sig, msg, pub)
assert.NoError(t, err)
assert.Equal(t, true, valid, "Signature did not verify")
}
func TestMultipleSignatureVerification(t *testing.T) {
pubkeys := make([]common.PublicKey, 0, 100)
sigs := make([][]byte, 0, 100)

View File

@@ -1,22 +1,32 @@
package bls
import "github.com/pkg/errors"
import (
"encoding/hex"
"fmt"
"github.com/pkg/errors"
)
// AggregatedSignature represents aggregated signature produced by AggregateBatch()
const AggregatedSignature = "bls aggregated signature"
// SignatureBatch refers to the defined set of
// signatures and its respective public keys and
// messages required to verify it.
type SignatureBatch struct {
Signatures [][]byte
PublicKeys []PublicKey
Messages [][32]byte
Signatures [][]byte
PublicKeys []PublicKey
Messages [][32]byte
Descriptions []string
}
// NewSet constructs an empty signature batch object.
func NewSet() *SignatureBatch {
return &SignatureBatch{
Signatures: [][]byte{},
PublicKeys: []PublicKey{},
Messages: [][32]byte{},
Signatures: [][]byte{},
PublicKeys: []PublicKey{},
Messages: [][32]byte{},
Descriptions: []string{},
}
}
@@ -25,6 +35,7 @@ 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...)
s.Descriptions = append(s.Descriptions, set.Descriptions...)
return s
}
@@ -33,12 +44,49 @@ func (s *SignatureBatch) Verify() (bool, error) {
return VerifyMultipleSignatures(s.Signatures, s.Messages, s.PublicKeys)
}
// VerifyVerbosely verifies signatures as a whole at first, if fails, fallback
// to verify each single signature to identify invalid ones.
func (s *SignatureBatch) VerifyVerbosely() (bool, error) {
valid, err := s.Verify()
if err != nil || valid {
return valid, err
}
// if signature batch is invalid, we then verify signatures one by one.
errmsg := "some signatures are invalid. details:"
for i := 0; i < len(s.Signatures); i++ {
sig := s.Signatures[i]
msg := s.Messages[i]
pubKey := s.PublicKeys[i]
valid, err := VerifySignature(sig, msg, pubKey)
if !valid {
desc := s.Descriptions[i]
if err != nil {
errmsg += fmt.Sprintf("\nsignature '%s' is invalid."+
" signature: 0x%s, public key: 0x%s, message: 0x%v, error: %v",
desc, hex.EncodeToString(sig), hex.EncodeToString(pubKey.Marshal()),
hex.EncodeToString(msg[:]), err)
} else {
errmsg += fmt.Sprintf("\nsignature '%s' is invalid."+
" signature: 0x%s, public key: 0x%s, message: 0x%v",
desc, hex.EncodeToString(sig), hex.EncodeToString(pubKey.Marshal()),
hex.EncodeToString(msg[:]))
}
}
}
return false, errors.Errorf(errmsg)
}
// Copy the attached signature batch and return it
// to the caller.
func (s *SignatureBatch) Copy() *SignatureBatch {
signatures := make([][]byte, len(s.Signatures))
pubkeys := make([]PublicKey, len(s.PublicKeys))
messages := make([][32]byte, len(s.Messages))
descriptions := make([]string, len(s.Descriptions))
for i := range s.Signatures {
sig := make([]byte, len(s.Signatures[i]))
copy(sig, s.Signatures[i])
@@ -50,10 +98,12 @@ func (s *SignatureBatch) Copy() *SignatureBatch {
for i := range s.Messages {
copy(messages[i][:], s.Messages[i][:])
}
copy(descriptions, s.Descriptions)
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubkeys,
Messages: messages,
Signatures: signatures,
PublicKeys: pubkeys,
Messages: messages,
Descriptions: descriptions,
}
}
@@ -82,6 +132,7 @@ func (s *SignatureBatch) RemoveDuplicates() (int, *SignatureBatch, error) {
sigs := s.Signatures[:0]
pubs := s.PublicKeys[:0]
msgs := s.Messages[:0]
descs := s.Descriptions[:0]
for i := 0; i < len(s.Signatures); i++ {
if duplicateSet[i] {
@@ -90,11 +141,13 @@ func (s *SignatureBatch) RemoveDuplicates() (int, *SignatureBatch, error) {
sigs = append(sigs, s.Signatures[i])
pubs = append(pubs, s.PublicKeys[i])
msgs = append(msgs, s.Messages[i])
descs = append(descs, s.Descriptions[i])
}
s.Signatures = sigs
s.PublicKeys = pubs
s.Messages = msgs
s.Descriptions = descs
return len(duplicateSet), s, nil
}
@@ -103,12 +156,12 @@ func (s *SignatureBatch) RemoveDuplicates() (int, *SignatureBatch, error) {
// reduce the number of pairings required when we finally verify the
// whole batch.
func (s *SignatureBatch) AggregateBatch() (*SignatureBatch, error) {
if len(s.Signatures) == 0 || len(s.PublicKeys) == 0 || len(s.Messages) == 0 {
return s, nil
if len(s.Signatures) != len(s.PublicKeys) || len(s.Signatures) != len(s.Messages) || len(s.Signatures) != len(s.Descriptions) {
return s, errors.Errorf("mismatch number of signatures, publickeys, messages and descriptions in signature batch. "+
"Signatures %d, Public Keys %d , Messages %d, Descriptions %d", len(s.Signatures), len(s.PublicKeys), len(s.Messages), len(s.Descriptions))
}
if len(s.Signatures) != len(s.PublicKeys) || len(s.Signatures) != len(s.Messages) {
return s, errors.Errorf("mismatch number of signatures, publickeys and messages in signature batch. "+
"Signatures %d, Public Keys %d , Messages %d", s.Signatures, s.PublicKeys, s.Messages)
if len(s.Signatures) == 0 {
return s, nil
}
msgMap := make(map[[32]byte]*SignatureBatch)
@@ -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])
continue
}
currBatch = &SignatureBatch{
Signatures: [][]byte{s.Signatures[i]},
Messages: [][32]byte{s.Messages[i]},
PublicKeys: []PublicKey{s.PublicKeys[i]},
Signatures: [][]byte{s.Signatures[i]},
Messages: [][32]byte{s.Messages[i]},
PublicKeys: []PublicKey{s.PublicKeys[i]},
Descriptions: []string{s.Descriptions[i]},
}
msgMap[currMsg] = currBatch
}
@@ -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}
}
newObj := *b
newSt = newSt.Join(&newObj)

View File

@@ -2,13 +2,18 @@ package bls
import (
"bytes"
"fmt"
"reflect"
"sort"
"testing"
"github.com/prysmaticlabs/prysm/v3/crypto/bls/common"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
)
const TestSignature = "test signature"
func TestCopySignatureSet(t *testing.T) {
t.Run("blst", func(t *testing.T) {
key, err := RandKey()
@@ -27,19 +32,22 @@ func TestCopySignatureSet(t *testing.T) {
sig3 := key3.Sign(message3[:])
set := &SignatureBatch{
Signatures: [][]byte{sig.Marshal()},
PublicKeys: []PublicKey{key.PublicKey()},
Messages: [][32]byte{message},
Signatures: [][]byte{sig.Marshal()},
PublicKeys: []PublicKey{key.PublicKey()},
Messages: [][32]byte{message},
Descriptions: createDescriptions(1),
}
set2 := &SignatureBatch{
Signatures: [][]byte{sig2.Marshal()},
PublicKeys: []PublicKey{key.PublicKey()},
Messages: [][32]byte{message},
Signatures: [][]byte{sig2.Marshal()},
PublicKeys: []PublicKey{key.PublicKey()},
Messages: [][32]byte{message},
Descriptions: createDescriptions(1),
}
set3 := &SignatureBatch{
Signatures: [][]byte{sig3.Marshal()},
PublicKeys: []PublicKey{key.PublicKey()},
Messages: [][32]byte{message},
Signatures: [][]byte{sig3.Marshal()},
PublicKeys: []PublicKey{key.PublicKey()},
Messages: [][32]byte{message},
Descriptions: createDescriptions(1),
}
aggSet := set.Join(set2).Join(set3)
aggSet2 := aggSet.Copy()
@@ -48,6 +56,38 @@ func TestCopySignatureSet(t *testing.T) {
})
}
func TestVerifyVerbosely_AllSignaturesValid(t *testing.T) {
set := NewValidSignatureSet(t, "good", 3)
valid, err := set.VerifyVerbosely()
assert.NoError(t, err)
assert.Equal(t, true, valid, "SignatureSet is expected to be valid")
}
func TestVerifyVerbosely_SomeSignaturesInvalid(t *testing.T) {
goodSet := NewValidSignatureSet(t, "good", 3)
badSet := NewInvalidSignatureSet(t, "bad", 3, false)
set := NewSet().Join(goodSet).Join(badSet)
valid, err := set.VerifyVerbosely()
assert.Equal(t, false, valid, "SignatureSet is expected to be invalid")
assert.StringContains(t, "signature 'signature of bad0' is invalid", err.Error())
assert.StringContains(t, "signature 'signature of bad1' is invalid", err.Error())
assert.StringContains(t, "signature 'signature of bad2' is invalid", err.Error())
assert.StringNotContains(t, "signature 'signature of good0' is invalid", err.Error())
assert.StringNotContains(t, "signature 'signature of good1' is invalid", err.Error())
assert.StringNotContains(t, "signature 'signature of good2' is invalid", err.Error())
}
func TestVerifyVerbosely_VerificationThrowsError(t *testing.T) {
goodSet := NewValidSignatureSet(t, "good", 1)
badSet := NewInvalidSignatureSet(t, "bad", 1, true)
set := NewSet().Join(goodSet).Join(badSet)
valid, err := set.VerifyVerbosely()
assert.Equal(t, false, valid, "SignatureSet is expected to be invalid")
assert.StringContains(t, "signature 'signature of bad0' is invalid", err.Error())
assert.StringContains(t, "error: could not unmarshal bytes into signature", err.Error())
assert.StringNotContains(t, "signature 'signature of good0' is invalid", err.Error())
}
func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
var keys []SecretKey
for i := 0; i < 100; i++ {
@@ -86,13 +126,15 @@ func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
allPubs := append(pubs, pubs...)
allMsgs := append(messages, messages...)
return &SignatureBatch{
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Descriptions: createDescriptions(len(allMsgs)),
}, &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(allMsgs)),
}
},
want: 20,
@@ -130,13 +172,15 @@ func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
allPubs := append(pubs, pubs...)
allMsgs := append(messages, messages...)
return &SignatureBatch{
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Descriptions: createDescriptions(len(allMsgs)),
}, &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(allMsgs)),
}
},
want: 30,
@@ -171,13 +215,15 @@ func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
pubs = append(pubs, k.PublicKey())
}
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}, &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}
},
want: 0,
@@ -223,13 +269,15 @@ func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
// Zero out to expected result
signatures[10] = make([]byte, 96)
return &SignatureBatch{
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Descriptions: createDescriptions(len(allMsgs)),
}, &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(allMsgs)),
}
},
want: 29,
@@ -294,13 +342,15 @@ func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
messages[29] = [32]byte{'j', 'u', 'n', 'k'}
return &SignatureBatch{
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Signatures: allSigs,
PublicKeys: allPubs,
Messages: allMsgs,
Descriptions: createDescriptions(len(allMsgs)),
}, &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}
},
want: 27,
@@ -342,11 +392,37 @@ func TestSignatureBatch_AggregateBatch(t *testing.T) {
{
name: "empty batch",
batchCreator: func(t *testing.T) (*SignatureBatch, *SignatureBatch) {
return &SignatureBatch{Signatures: nil, Messages: nil, PublicKeys: nil},
&SignatureBatch{Signatures: nil, Messages: nil, PublicKeys: nil}
return &SignatureBatch{Signatures: nil, Messages: nil, PublicKeys: nil, Descriptions: nil},
&SignatureBatch{Signatures: nil, Messages: nil, PublicKeys: nil, Descriptions: nil}
},
wantErr: false,
},
{
name: "mismatch number of signatures and messages in batch",
batchCreator: func(t *testing.T) (*SignatureBatch, *SignatureBatch) {
key1 := keys[0]
key2 := keys[1]
msg := [32]byte{'r', 'a', 'n', 'd', 'o', 'm'}
sig1 := key1.Sign(msg[:])
sig2 := key2.Sign(msg[:])
signatures := [][]byte{sig1.Marshal(), sig2.Marshal()}
pubs := []common.PublicKey{key1.PublicKey(), key2.PublicKey()}
messages := [][32]byte{msg}
descs := createDescriptions(2)
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: descs,
}, &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: descs,
}
},
wantErr: true,
},
{
name: "valid signatures in batch",
batchCreator: func(t *testing.T) (*SignatureBatch, *SignatureBatch) {
@@ -366,13 +442,15 @@ func TestSignatureBatch_AggregateBatch(t *testing.T) {
assert.NoError(t, err)
aggPub := AggregateMultiplePubkeys(pubs)
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}, &SignatureBatch{
Signatures: [][]byte{aggSig.Marshal()},
PublicKeys: []PublicKey{aggPub},
Messages: [][32]byte{msg},
Signatures: [][]byte{aggSig.Marshal()},
PublicKeys: []PublicKey{aggPub},
Messages: [][32]byte{msg},
Descriptions: createDescriptions(1, AggregatedSignature),
}
},
wantErr: false,
@@ -394,9 +472,10 @@ func TestSignatureBatch_AggregateBatch(t *testing.T) {
}
signatures[10] = make([]byte, 96)
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}, nil
},
wantErr: true,
@@ -440,13 +519,15 @@ func TestSignatureBatch_AggregateBatch(t *testing.T) {
aggPub2 := AggregateMultiplePubkeys(pubs[10:20])
aggPub3 := AggregateMultiplePubkeys(pubs[20:30])
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}, &SignatureBatch{
Signatures: [][]byte{aggSig1.Marshal(), aggSig2.Marshal(), aggSig3.Marshal()},
PublicKeys: []PublicKey{aggPub1, aggPub2, aggPub3},
Messages: [][32]byte{msg, msg1, msg2},
Signatures: [][]byte{aggSig1.Marshal(), aggSig2.Marshal(), aggSig3.Marshal()},
PublicKeys: []PublicKey{aggPub1, aggPub2, aggPub3},
Messages: [][32]byte{msg, msg1, msg2},
Descriptions: createDescriptions(3, AggregatedSignature),
}
},
wantErr: false,
@@ -521,13 +602,15 @@ func TestSignatureBatch_AggregateBatch(t *testing.T) {
aggPub3 := AggregateMultiplePubkeys(newPubs)
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Signatures: signatures,
PublicKeys: pubs,
Messages: messages,
Descriptions: createDescriptions(len(messages)),
}, &SignatureBatch{
Signatures: [][]byte{aggSig1.Marshal(), signatures[5], aggSig2.Marshal(), signatures[15], aggSig3.Marshal(), signatures[25]},
PublicKeys: []PublicKey{aggPub1, pubs[5], aggPub2, pubs[15], aggPub3, pubs[25]},
Messages: [][32]byte{msg, messages[5], msg1, messages[15], msg2, messages[25]},
Signatures: [][]byte{aggSig1.Marshal(), signatures[5], aggSig2.Marshal(), signatures[15], aggSig3.Marshal(), signatures[25]},
PublicKeys: []PublicKey{aggPub1, pubs[5], aggPub2, pubs[15], aggPub3, pubs[25]},
Messages: [][32]byte{msg, messages[5], msg1, messages[15], msg2, messages[25]},
Descriptions: []string{AggregatedSignature, TestSignature, AggregatedSignature, TestSignature, AggregatedSignature, TestSignature},
}
},
wantErr: false,
@@ -556,10 +639,87 @@ func TestSignatureBatch_AggregateBatch(t *testing.T) {
if !reflect.DeepEqual(got.Messages, output.Messages) {
t.Errorf("AggregateBatch() Messages got = %v, want %v", got.Messages, output.Messages)
}
if !reflect.DeepEqual(got.Descriptions, output.Descriptions) {
t.Errorf("AggregateBatch() Descriptions got = %v, want %v", got.Descriptions, output.Descriptions)
}
})
}
}
func NewValidSignatureSet(t *testing.T, msgBody string, num int) *SignatureBatch {
set := &SignatureBatch{
Signatures: make([][]byte, num),
PublicKeys: make([]common.PublicKey, num),
Messages: make([][32]byte, num),
Descriptions: make([]string, num),
}
for i := 0; i < num; i++ {
priv, err := RandKey()
require.NoError(t, err)
pubkey := priv.PublicKey()
msg := messageBytes(fmt.Sprintf("%s%d", msgBody, i))
sig := priv.Sign(msg[:]).Marshal()
desc := fmt.Sprintf("signature of %s%d", msgBody, i)
set.Signatures[i] = sig
set.PublicKeys[i] = pubkey
set.Messages[i] = msg
set.Descriptions[i] = desc
}
return set
}
func NewInvalidSignatureSet(t *testing.T, msgBody string, num int, throwErr bool) *SignatureBatch {
set := &SignatureBatch{
Signatures: make([][]byte, num),
PublicKeys: make([]common.PublicKey, num),
Messages: make([][32]byte, num),
Descriptions: make([]string, num),
}
for i := 0; i < num; i++ {
priv, err := RandKey()
require.NoError(t, err)
pubkey := priv.PublicKey()
msg := messageBytes(fmt.Sprintf("%s%d", msgBody, i))
var sig []byte
if throwErr {
sig = make([]byte, 96)
} else {
badMsg := messageBytes("badmsg")
sig = priv.Sign(badMsg[:]).Marshal()
}
desc := fmt.Sprintf("signature of %s%d", msgBody, i)
set.Signatures[i] = sig
set.PublicKeys[i] = pubkey
set.Messages[i] = msg
set.Descriptions[i] = desc
}
return set
}
func messageBytes(message string) [32]byte {
bytes := [32]byte{}
copy(bytes[:], []byte(message))
return bytes
}
func createDescriptions(length int, text ...string) []string {
desc := make([]string, length)
for i := range desc {
if len(text) > 0 {
desc[i] = text[0]
} else {
desc[i] = TestSignature
}
}
return desc
}
func sortSet(s *SignatureBatch) *SignatureBatch {
sort.Sort(sorter{set: s})
return s

View File

@@ -41,6 +41,16 @@ func DeepNotSSZEqual(tb assertions.AssertionTestingTB, expected, actual interfac
assertions.DeepNotSSZEqual(tb.Errorf, expected, actual, msg...)
}
// StringContains asserts a string contains specified substring.
func StringContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...interface{}) {
assertions.StringContains(tb.Errorf, expected, actual, true, msg...)
}
// StringContains asserts a string does not contain specified substring.
func StringNotContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...interface{}) {
assertions.StringContains(tb.Errorf, expected, actual, false, msg...)
}
// NoError asserts that error is nil.
func NoError(tb assertions.AssertionTestingTB, err error, msg ...interface{}) {
assertions.NoError(tb.Errorf, err, msg...)

View File

@@ -78,6 +78,23 @@ func DeepNotSSZEqual(loggerFn assertionLoggerFn, expected, actual interface{}, m
}
}
// StringContains checks whether a string contains specified substring. If flag is false, inverse is checked.
func StringContains(loggerFn assertionLoggerFn, expected, actual string, flag bool, msg ...interface{}) {
if flag {
if !strings.Contains(actual, expected) {
errMsg := parseMsg("Expected substring is not found", msg...)
_, file, line, _ := runtime.Caller(2)
loggerFn("%s:%d %s, got: %v, want: %s", filepath.Base(file), line, errMsg, actual, expected)
}
} else {
if strings.Contains(actual, expected) {
errMsg := parseMsg("Unexpected substring is found", msg...)
_, file, line, _ := runtime.Caller(2)
loggerFn("%s:%d %s, got: %v, not want: %s", filepath.Base(file), line, errMsg, actual, expected)
}
}
}
// NoError asserts that error is nil.
func NoError(loggerFn assertionLoggerFn, err error, msg ...interface{}) {
if err != nil {