Skip to content

Commit

Permalink
Use a mask for BLS aggregation and improve caching
Browse files Browse the repository at this point in the history
fixes #592
  • Loading branch information
Stebalien committed Sep 23, 2024
1 parent abc8169 commit f5af3ff
Show file tree
Hide file tree
Showing 18 changed files with 323 additions and 222 deletions.
64 changes: 39 additions & 25 deletions blssig/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,44 @@ import (
// Max size of the point cache.
const maxPointCacheSize = 10_000

func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg []byte, _err error) {
type aggregation struct {
mask *bdn.Mask
scheme *bdn.Scheme
}

func (a *aggregation) Aggregate(mask []int, signatures [][]byte) (_agg []byte, _err error) {
defer func() {
status := measurements.AttrStatusSuccess
if _err != nil {
status = measurements.AttrStatusError
}

if perr := recover(); perr != nil {
_err = fmt.Errorf("panicked aggregating public keys: %v\n%s",
_err = fmt.Errorf("panicked aggregating signatures: %v\n%s",
perr, string(debug.Stack()))
log.Error(_err)
status = measurements.AttrStatusPanic
}

metrics.aggregate.Record(
context.TODO(), int64(len(pubkeys)),
context.TODO(), int64(len(mask)),
metric.WithAttributes(status),
)
}()

if len(pubkeys) != len(signatures) {
if len(mask) != len(signatures) {
return nil, fmt.Errorf("lengths of pubkeys and sigs does not match %d != %d",
len(pubkeys), len(signatures))
len(mask), len(signatures))
}

mask, err := v.pubkeysToMask(pubkeys)
if err != nil {
return nil, fmt.Errorf("converting public keys to mask: %w", err)
bdnMask := a.mask.Clone()
for _, bit := range mask {
if err := bdnMask.SetBit(bit, true); err != nil {
return nil, err
}
}

aggSigPoint, err := v.scheme.AggregateSignatures(signatures, mask)
aggSigPoint, err := a.scheme.AggregateSignatures(signatures, bdnMask)
if err != nil {
return nil, fmt.Errorf("computing aggregate signature: %w", err)
}
Expand All @@ -59,7 +66,7 @@ func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg
return aggSig, nil
}

func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft.PubKey) (_err error) {
func (a *aggregation) VerifyAggregate(mask []int, msg []byte, signature []byte) (_err error) {
defer func() {
status := measurements.AttrStatusSuccess
if _err != nil {
Expand All @@ -75,25 +82,35 @@ func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft
}

metrics.verifyAggregate.Record(
context.TODO(), int64(len(pubkeys)),
context.TODO(), int64(len(mask)),
metric.WithAttributes(status),
)
}()

mask, err := v.pubkeysToMask(pubkeys)
if err != nil {
return fmt.Errorf("converting public keys to mask: %w", err)
bdnMask := a.mask.Clone()
for _, bit := range mask {
if err := bdnMask.SetBit(bit, true); err != nil {
return err
}
}

aggPubKey, err := v.scheme.AggregatePublicKeys(mask)
aggPubKey, err := a.scheme.AggregatePublicKeys(bdnMask)
if err != nil {
return fmt.Errorf("aggregating public keys: %w", err)
}

return v.scheme.Verify(aggPubKey, msg, signature)
return a.scheme.Verify(aggPubKey, msg, signature)
}

func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*bdn.Mask, error) {
func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey) (_agg gpbft.Aggregate, _err error) {
defer func() {
if perr := recover(); perr != nil {
_err = fmt.Errorf("panicked aggregating public keys: %v\n%s",
perr, string(debug.Stack()))
log.Error(_err)
}
}()

kPubkeys := make([]kyber.Point, 0, len(pubkeys))
for i, p := range pubkeys {
point, err := v.pubkeyToPoint(p)
Expand All @@ -105,13 +122,10 @@ func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*bdn.Mask, error) {

mask, err := bdn.NewMask(v.keyGroup, kPubkeys, nil)
if err != nil {
return nil, fmt.Errorf("creating bdn mask: %w", err)
}
for i := range kPubkeys {
err := mask.SetBit(i, true)
if err != nil {
return nil, fmt.Errorf("setting mask bit %d: %w", i, err)
}
return nil, fmt.Errorf("creating key mask: %w", err)
}
return mask, nil
return &aggregation{
mask: mask,
scheme: v.scheme,
}, nil
}
12 changes: 9 additions & 3 deletions certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf
return fmt.Errorf("failed to scale power table: %w", err)
}

signers := make([]gpbft.PubKey, 0, len(powerTable))
keys := powerTable.PublicKeys()
mask := make([]int, 0, len(powerTable))
var signerPowers int64
if err := cert.Signers.ForEach(func(i uint64) error {
if i >= uint64(len(powerTable)) {
Expand All @@ -165,7 +166,7 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf
cert.GPBFTInstance, powerTable[i].ID)
}
signerPowers += power
signers = append(signers, powerTable[i].PubKey)
mask = append(mask, int(i))
return nil
}); err != nil {
return err
Expand All @@ -192,7 +193,12 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf
signedBytes = payload.MarshalForSigning(nn)
}

if err := verifier.VerifyAggregate(signedBytes, cert.Signature, signers); err != nil {
aggregate, err := verifier.Aggregate(keys)
if err != nil {
return err
}

if err := aggregate.VerifyAggregate(mask, signedBytes, cert.Signature); err != nil {
return fmt.Errorf("invalid signature on finality certificate for instance %d: %w", cert.GPBFTInstance, err)
}
return nil
Expand Down
5 changes: 3 additions & 2 deletions emulator/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ func (h *driverHost) GetCommittee(id uint64) (*gpbft.Committee, error) {
return nil, fmt.Errorf("instance ID %d not found", id)
}
return &gpbft.Committee{
PowerTable: instance.powerTable,
Beacon: instance.beacon,
PowerTable: instance.powerTable,
Beacon: instance.beacon,
AggregateVerifier: instance.aggregateVerifier,
}, nil
}

Expand Down
35 changes: 21 additions & 14 deletions emulator/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import (
// Instance represents a GPBFT instance capturing all the information necessary
// for GPBFT to function, along with the final decision reached if any.
type Instance struct {
t *testing.T
id uint64
supplementalData gpbft.SupplementalData
proposal gpbft.ECChain
powerTable *gpbft.PowerTable
beacon []byte
decision *gpbft.Justification
signing Signing
t *testing.T
id uint64
supplementalData gpbft.SupplementalData
proposal gpbft.ECChain
powerTable *gpbft.PowerTable
beacon []byte
decision *gpbft.Justification
signing Signing
aggregateVerifier gpbft.Aggregate
}

// NewInstance instantiates a new Instance for emulation. If absent, the
Expand Down Expand Up @@ -58,7 +59,8 @@ func NewInstance(t *testing.T, id uint64, powerEntries gpbft.PowerEntries, propo
}
proposalChain, err := gpbft.NewChain(proposal[0], proposal[1:]...)
require.NoError(t, err)
return &Instance{

i := &Instance{
t: t,
id: id,
powerTable: pt,
Expand All @@ -68,11 +70,18 @@ func NewInstance(t *testing.T, id uint64, powerEntries gpbft.PowerEntries, propo
Commitments: [32]byte{},
PowerTable: ptCid,
},
signing: AdhocSigning(),
}

i.SetSigning(AdhocSigning())
return i
}

func (i *Instance) SetSigning(signing Signing) { i.signing = signing }
func (i *Instance) SetSigning(signing Signing) {
var err error
i.signing = signing
i.aggregateVerifier, err = signing.Aggregate(i.powerTable.Entries.PublicKeys())
require.NoError(i.t, err)
}
func (i *Instance) Proposal() gpbft.ECChain { return i.proposal }
func (i *Instance) GetDecision() *gpbft.Justification { return i.decision }
func (i *Instance) ID() uint64 { return i.id }
Expand Down Expand Up @@ -140,7 +149,6 @@ func (i *Instance) NewJustificationWithPayload(payload gpbft.Payload, from ...gp
msg := i.signing.MarshalPayloadForSigning(networkName, &payload)
qr := gpbft.QuorumResult{
Signers: make([]int, len(from)),
PubKeys: make([]gpbft.PubKey, len(from)),
Signatures: make([][]byte, len(from)),
}
for j, actor := range from {
Expand All @@ -150,10 +158,9 @@ func (i *Instance) NewJustificationWithPayload(payload gpbft.Payload, from ...gp
signature, err := i.signing.Sign(context.Background(), entry.PubKey, msg)
require.NoError(i.t, err)
qr.Signatures[j] = signature
qr.PubKeys[j] = entry.PubKey
qr.Signers[j] = index
}
aggregate, err := i.signing.Aggregate(qr.PubKeys, qr.Signatures)
aggregate, err := i.aggregateVerifier.Aggregate(qr.Signers, qr.Signatures)
require.NoError(i.t, err)
return &gpbft.Justification{
Vote: payload,
Expand Down
53 changes: 41 additions & 12 deletions emulator/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package emulator
import (
"bytes"
"context"
"encoding/binary"
"errors"
"hash/crc32"

Expand Down Expand Up @@ -58,13 +59,22 @@ func (s adhocSigning) Verify(sender gpbft.PubKey, msg, got []byte) error {
}
}

func (s adhocSigning) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte, error) {
if len(signers) != len(sigs) {
type aggregate struct {
keys []gpbft.PubKey
signing adhocSigning
}

// Aggregate implements gpbft.Aggregate.
func (a *aggregate) Aggregate(signerMask []int, sigs [][]byte) ([]byte, error) {
if len(signerMask) != len(sigs) {
return nil, errors.New("public keys and signatures length mismatch")
}
hasher := crc32.NewIEEE()
for i, signer := range signers {
if _, err := hasher.Write(signer); err != nil {
for i, bit := range signerMask {
if err := binary.Write(hasher, binary.BigEndian, uint64(bit)); err != nil {
return nil, err
}
if _, err := hasher.Write(a.keys[bit]); err != nil {
return nil, err
}
if _, err := hasher.Write(sigs[i]); err != nil {
Expand All @@ -74,16 +84,17 @@ func (s adhocSigning) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte,
return hasher.Sum(nil), nil
}

func (s adhocSigning) VerifyAggregate(payload, got []byte, signers []gpbft.PubKey) error {
signatures := make([][]byte, len(signers))
// VerifyAggregate implements gpbft.Aggregate.
func (a *aggregate) VerifyAggregate(signerMask []int, payload []byte, got []byte) error {
signatures := make([][]byte, len(signerMask))
var err error
for i, signer := range signers {
signatures[i], err = s.Sign(context.Background(), signer, payload)
for i, bit := range signerMask {
signatures[i], err = a.signing.Sign(context.Background(), a.keys[bit], payload)
if err != nil {
return err
}
}
want, err := s.Aggregate(signers, signatures)
want, err := a.Aggregate(signerMask, signatures)
if err != nil {
return err
}
Expand All @@ -93,23 +104,34 @@ func (s adhocSigning) VerifyAggregate(payload, got []byte, signers []gpbft.PubKe
return nil
}

func (s adhocSigning) Aggregate(keys []gpbft.PubKey) (gpbft.Aggregate, error) {
return &aggregate{keys: keys,
signing: s,
}, nil
}

func (s adhocSigning) MarshalPayloadForSigning(name gpbft.NetworkName, payload *gpbft.Payload) []byte {
return payload.MarshalForSigning(name)
}

type erroneousSigning struct{}
type erroneousAggregate struct{}

func (p erroneousSigning) Verify(gpbft.PubKey, []byte, []byte) error {
return errors.New("err Verify")
}

func (p erroneousSigning) VerifyAggregate([]byte, []byte, []gpbft.PubKey) error {
func (p erroneousAggregate) VerifyAggregate([]int, []byte, []byte) error {
return errors.New("err VerifyAggregate")
}

func (p erroneousSigning) Aggregate([]gpbft.PubKey, [][]byte) ([]byte, error) {
func (p erroneousAggregate) Aggregate([]int, [][]byte) ([]byte, error) {
return nil, errors.New("err Aggregate")
}

func (p erroneousSigning) Aggregate([]gpbft.PubKey) (gpbft.Aggregate, error) {
return erroneousAggregate{}, nil
}
func (p erroneousSigning) Sign(context.Context, gpbft.PubKey, []byte) ([]byte, error) {
return nil, errors.New("err Sign")
}
Expand All @@ -119,9 +141,16 @@ func (p erroneousSigning) MarshalPayloadForSigning(gpbft.NetworkName, *gpbft.Pay
}

type panicSigning struct{}
type panicAggregate struct{}

func (p panicSigning) Verify(gpbft.PubKey, []byte, []byte) error { panic("π") }
func (p panicSigning) VerifyAggregate([]byte, []byte, []gpbft.PubKey) error { panic("π") }
func (p panicSigning) Aggregate([]gpbft.PubKey, [][]byte) ([]byte, error) { panic("π") }
func (p panicSigning) Sign(context.Context, gpbft.PubKey, []byte) ([]byte, error) { panic("π") }
func (p panicSigning) MarshalPayloadForSigning(gpbft.NetworkName, *gpbft.Payload) []byte { panic("π") }

func (p panicSigning) Aggregate([]gpbft.PubKey) (gpbft.Aggregate, error) {
return panicAggregate{}, nil
}

func (p panicAggregate) VerifyAggregate([]int, []byte, []byte) error { panic("π") }
func (p panicAggregate) Aggregate([]int, [][]byte) ([]byte, error) { panic("π") }
23 changes: 19 additions & 4 deletions gpbft/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type Committee struct {
PowerTable *PowerTable
// Beacon is the unique beacon value associated with the committee.
Beacon []byte
// AggregateVerifier is used to aggregate and verify aggregate signatures made by the
// committee.
AggregateVerifier Aggregate
}

// Endpoint to which participants can send messages.
Expand Down Expand Up @@ -116,15 +119,27 @@ type SigningMarshaler interface {
MarshalPayloadForSigning(NetworkName, *Payload) []byte
}

type Aggregate interface {
// Aggregates signatures from a participants.
//
// Implementations must be safe for concurrent use.
Aggregate(signerMask []int, sigs [][]byte) ([]byte, error)
// VerifyAggregate verifies an aggregate signature.
//
// Implementations must be safe for concurrent use.
VerifyAggregate(signerMask []int, payload, aggSig []byte) error
}

type Verifier interface {
// Verifies a signature for the given public key.
//
// Implementations must be safe for concurrent use.
Verify(pubKey PubKey, msg, sig []byte) error
// Aggregates signatures from a participants.
Aggregate(pubKeys []PubKey, sigs [][]byte) ([]byte, error)
// VerifyAggregate verifies an aggregate signature.
// Return an Aggregate that can aggregate and verify aggregate signatures made by the given
// public keys.
//
// Implementations must be safe for concurrent use.
VerifyAggregate(payload, aggSig []byte, signers []PubKey) error
Aggregate(pubKeys []PubKey) (Aggregate, error)
}

type Signatures interface {
Expand Down
Loading

0 comments on commit f5af3ff

Please sign in to comment.