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 Sep 5, 2024
1 parent a4deda5 commit fc022b1
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 221 deletions.
65 changes: 42 additions & 23 deletions blssig/aggregation.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,50 @@ 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 {
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 +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,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) (*sign.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 @@ -107,11 +125,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 cached bdn 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 @@ -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
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("π") }
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
Loading

0 comments on commit fc022b1

Please sign in to comment.