prysm-pulse/crypto/bls/signature_batch.go
Ye Ding e43152102e
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>
2022-12-20 18:41:47 +08:00

205 lines
6.3 KiB
Go

package bls
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
Descriptions []string
}
// NewSet constructs an empty signature batch object.
func NewSet() *SignatureBatch {
return &SignatureBatch{
Signatures: [][]byte{},
PublicKeys: []PublicKey{},
Messages: [][32]byte{},
Descriptions: []string{},
}
}
// Join merges the provided signature batch to out current one.
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
}
// Verify the current signature batch using the batch verify algorithm.
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])
signatures[i] = sig
}
for i := range s.PublicKeys {
pubkeys[i] = s.PublicKeys[i].Copy()
}
for i := range s.Messages {
copy(messages[i][:], s.Messages[i][:])
}
copy(descriptions, s.Descriptions)
return &SignatureBatch{
Signatures: signatures,
PublicKeys: pubkeys,
Messages: messages,
Descriptions: descriptions,
}
}
// RemoveDuplicates removes duplicate signature sets from the signature batch.
func (s *SignatureBatch) RemoveDuplicates() (int, *SignatureBatch, error) {
if len(s.Signatures) == 0 || len(s.PublicKeys) == 0 || len(s.Messages) == 0 {
return 0, s, nil
}
if len(s.Signatures) != len(s.PublicKeys) || len(s.Signatures) != len(s.Messages) {
return 0, 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)
}
sigMap := make(map[string]int)
duplicateSet := make(map[int]bool)
for i := 0; i < len(s.Signatures); i++ {
if sigIdx, ok := sigMap[string(s.Signatures[i])]; ok {
if s.PublicKeys[sigIdx].Equals(s.PublicKeys[i]) &&
s.Messages[sigIdx] == s.Messages[i] {
duplicateSet[i] = true
continue
}
}
sigMap[string(s.Signatures[i])] = i
}
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] {
continue
}
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
}
// AggregateBatch aggregates common messages in the provided batch to
// reduce the number of pairings required when we finally verify the
// whole batch.
func (s *SignatureBatch) AggregateBatch() (*SignatureBatch, error) {
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) == 0 {
return s, nil
}
msgMap := make(map[[32]byte]*SignatureBatch)
for i := 0; i < len(s.Messages); i++ {
currMsg := s.Messages[i]
currBatch, ok := msgMap[currMsg]
if ok {
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]},
Descriptions: []string{s.Descriptions[i]},
}
msgMap[currMsg] = currBatch
}
newSt := NewSet()
for rt, b := range msgMap {
if len(b.PublicKeys) > 1 {
aggPub := AggregateMultiplePubkeys(b.PublicKeys)
aggSig, err := AggregateCompressedSignatures(b.Signatures)
if err != nil {
return nil, err
}
copiedRt := rt
b.PublicKeys = []PublicKey{aggPub}
b.Signatures = [][]byte{aggSig.Marshal()}
b.Messages = [][32]byte{copiedRt}
b.Descriptions = []string{AggregatedSignature}
}
newObj := *b
newSt = newSt.Join(&newObj)
}
return newSt, nil
}