Skip to content

Commit

Permalink
[WIP] 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 Aug 29, 2024
1 parent 3cd2e35 commit fa0a974
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 220 deletions.
74 changes: 52 additions & 22 deletions blssig/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ import (

"github.com/drand/kyber"
"github.com/drand/kyber/sign"
"github.com/drand/kyber/sign/bdn"
)

// 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.CachedMask
scheme *bdn.Scheme
}

func (a *aggregation) Aggregate(mask []int, signatures [][]byte) (_agg []byte, _err error) {
defer func() {
status := measurements.AttrStatusSuccess
if _err != nil {
Expand All @@ -32,22 +38,24 @@ func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg
}

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 +67,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 +83,46 @@ 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) (*sign.Mask, error) {
func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey) (_agg gpbft.Aggregate, _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",
perr, string(debug.Stack()))
log.Error(_err)
status = measurements.AttrStatusPanic
}

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

kPubkeys := make([]kyber.Point, 0, len(pubkeys))
for i, p := range pubkeys {
point, err := v.pubkeyToPoint(p)
Expand All @@ -107,11 +136,12 @@ func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*sign.Mask, error) {
if err != nil {
return nil, fmt.Errorf("creating key 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)
}
cmask, err := bdn.NewCachedMask(mask)
if err != nil {
return nil, fmt.Errorf("creating key mask: %w", err)
}
return mask, nil
return &aggregation{
mask: cmask,
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 @@ -149,7 +149,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 @@ -164,7 +165,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 @@ -191,7 +192,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
34 changes: 19 additions & 15 deletions emulator/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ 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
t *testing.T
id uint64
supplementalData gpbft.SupplementalData
proposal gpbft.ECChain
powerTable *gpbft.PowerTable
aggregateVerifier gpbft.Aggregate
beacon []byte
decision *gpbft.Justification
}

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

aggVerifier, err := signing.Aggregate(pt.Entries.PublicKeys())
require.NoError(t, err)

return &Instance{
t: t,
id: id,
powerTable: pt,
beacon: []byte(fmt.Sprintf("🥓%d", id)),
proposal: proposalChain,
t: t,
id: id,
powerTable: pt,
aggregateVerifier: aggVerifier,
beacon: []byte(fmt.Sprintf("🥓%d", id)),
proposal: proposalChain,
}
}

Expand Down Expand Up @@ -129,7 +135,6 @@ func (i *Instance) NewJustification(round uint64, step gpbft.Phase, vote gpbft.E
msg := 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 @@ -139,10 +144,9 @@ func (i *Instance) NewJustification(round uint64, step gpbft.Phase, vote gpbft.E
signature, err := 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 := 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
35 changes: 26 additions & 9 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 @@ -46,13 +47,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 @@ -62,16 +72,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 @@ -81,6 +92,12 @@ 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)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,5 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)

replace github.com/drand/kyber => github.com/Stebalien/kyber v1.3.2-0.20240827162216-c96a0e427578
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Kubuxu/go-broadcast v0.0.0-20240621161059-1a8c90734cd6 h1:yh2/1fz3ajTaeKskSWxtSBNScdRZfQ/A5nyd9+64T6M=
github.com/Kubuxu/go-broadcast v0.0.0-20240621161059-1a8c90734cd6/go.mod h1:5LOj/fF3Oc/cvJqzDiyfx4XwtBPRWUYEz+V+b13sH5U=
github.com/Stebalien/kyber v1.3.2-0.20240827162216-c96a0e427578 h1:dx1hCR7KbG1HbehvPPRJKExoI9COfy8eMg7sCidKJEs=
github.com/Stebalien/kyber v1.3.2-0.20240827162216-c96a0e427578/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
Expand Down Expand Up @@ -49,8 +51,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/drand/kyber v1.3.1 h1:E0p6M3II+loMVwTlAp5zu4+GGZFNiRfq02qZxzw2T+Y=
github.com/drand/kyber v1.3.1/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA=
github.com/drand/kyber-bls12381 v0.3.1 h1:KWb8l/zYTP5yrvKTgvhOrk2eNPscbMiUOIeWBnmUxGo=
github.com/drand/kyber-bls12381 v0.3.1/go.mod h1:H4y9bLPu7KZA/1efDg+jtJ7emKx+ro3PU7/jWUVt140=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
Expand Down
20 changes: 16 additions & 4 deletions gpbft/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,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 fa0a974

Please sign in to comment.