Skip to content

Commit 0860f44

Browse files
committed
[WIP] Use a mask for BLS aggregation and improve caching
fixes #592
1 parent 8e05800 commit 0860f44

19 files changed

+307
-220
lines changed

blssig/aggregation.go

+52-22
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ import (
1212

1313
"github.com/drand/kyber"
1414
"github.com/drand/kyber/sign"
15+
"github.com/drand/kyber/sign/bdn"
1516
)
1617

1718
// Max size of the point cache.
1819
const maxPointCacheSize = 10_000
1920

20-
func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg []byte, _err error) {
21+
type aggregation struct {
22+
mask *bdn.CachedMask
23+
scheme *bdn.Scheme
24+
}
25+
26+
func (a *aggregation) Aggregate(mask []int, signatures [][]byte) (_agg []byte, _err error) {
2127
defer func() {
2228
status := measurements.AttrStatusSuccess
2329
if _err != nil {
@@ -32,22 +38,24 @@ func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg
3238
}
3339

3440
metrics.aggregate.Record(
35-
context.TODO(), int64(len(pubkeys)),
41+
context.TODO(), int64(len(mask)),
3642
metric.WithAttributes(status),
3743
)
3844
}()
3945

40-
if len(pubkeys) != len(signatures) {
46+
if len(mask) != len(signatures) {
4147
return nil, fmt.Errorf("lengths of pubkeys and sigs does not match %d != %d",
42-
len(pubkeys), len(signatures))
48+
len(mask), len(signatures))
4349
}
4450

45-
mask, err := v.pubkeysToMask(pubkeys)
46-
if err != nil {
47-
return nil, fmt.Errorf("converting public keys to mask: %w", err)
51+
bdnMask := a.mask.Clone()
52+
for _, bit := range mask {
53+
if err := bdnMask.SetBit(bit, true); err != nil {
54+
return nil, err
55+
}
4856
}
4957

50-
aggSigPoint, err := v.scheme.AggregateSignatures(signatures, mask)
58+
aggSigPoint, err := a.scheme.AggregateSignatures(signatures, bdnMask)
5159
if err != nil {
5260
return nil, fmt.Errorf("computing aggregate signature: %w", err)
5361
}
@@ -59,7 +67,7 @@ func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey, signatures [][]byte) (_agg
5967
return aggSig, nil
6068
}
6169

62-
func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft.PubKey) (_err error) {
70+
func (a *aggregation) VerifyAggregate(mask []int, msg []byte, signature []byte) (_err error) {
6371
defer func() {
6472
status := measurements.AttrStatusSuccess
6573
if _err != nil {
@@ -75,25 +83,46 @@ func (v *Verifier) VerifyAggregate(msg []byte, signature []byte, pubkeys []gpbft
7583
}
7684

7785
metrics.verifyAggregate.Record(
78-
context.TODO(), int64(len(pubkeys)),
86+
context.TODO(), int64(len(mask)),
7987
metric.WithAttributes(status),
8088
)
8189
}()
8290

83-
mask, err := v.pubkeysToMask(pubkeys)
84-
if err != nil {
85-
return fmt.Errorf("converting public keys to mask: %w", err)
91+
bdnMask := a.mask.Clone()
92+
for _, bit := range mask {
93+
if err := bdnMask.SetBit(bit, true); err != nil {
94+
return err
95+
}
8696
}
8797

88-
aggPubKey, err := v.scheme.AggregatePublicKeys(mask)
98+
aggPubKey, err := a.scheme.AggregatePublicKeys(bdnMask)
8999
if err != nil {
90100
return fmt.Errorf("aggregating public keys: %w", err)
91101
}
92102

93-
return v.scheme.Verify(aggPubKey, msg, signature)
103+
return a.scheme.Verify(aggPubKey, msg, signature)
94104
}
95105

96-
func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*sign.Mask, error) {
106+
func (v *Verifier) Aggregate(pubkeys []gpbft.PubKey) (_agg gpbft.Aggregate, _err error) {
107+
defer func() {
108+
status := measurements.AttrStatusSuccess
109+
if _err != nil {
110+
status = measurements.AttrStatusError
111+
}
112+
113+
if perr := recover(); perr != nil {
114+
_err = fmt.Errorf("panicked aggregating public keys: %v\n%s",
115+
perr, string(debug.Stack()))
116+
log.Error(_err)
117+
status = measurements.AttrStatusPanic
118+
}
119+
120+
metrics.aggregate.Record(
121+
context.TODO(), int64(len(pubkeys)),
122+
metric.WithAttributes(status),
123+
)
124+
}()
125+
97126
kPubkeys := make([]kyber.Point, 0, len(pubkeys))
98127
for i, p := range pubkeys {
99128
point, err := v.pubkeyToPoint(p)
@@ -107,11 +136,12 @@ func (v *Verifier) pubkeysToMask(pubkeys []gpbft.PubKey) (*sign.Mask, error) {
107136
if err != nil {
108137
return nil, fmt.Errorf("creating key mask: %w", err)
109138
}
110-
for i := range kPubkeys {
111-
err := mask.SetBit(i, true)
112-
if err != nil {
113-
return nil, fmt.Errorf("setting mask bit %d: %w", i, err)
114-
}
139+
cmask, err := bdn.NewCachedMask(mask)
140+
if err != nil {
141+
return nil, fmt.Errorf("creating key mask: %w", err)
115142
}
116-
return mask, nil
143+
return &aggregation{
144+
mask: cmask,
145+
scheme: v.scheme,
146+
}, nil
117147
}

certs/certs.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf
149149
return fmt.Errorf("failed to scale power table: %w", err)
150150
}
151151

152-
signers := make([]gpbft.PubKey, 0, len(powerTable))
152+
keys := powerTable.PublicKeys()
153+
mask := make([]int, 0, len(powerTable))
153154
var signerPowers int64
154155
if err := cert.Signers.ForEach(func(i uint64) error {
155156
if i >= uint64(len(powerTable)) {
@@ -164,7 +165,7 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf
164165
cert.GPBFTInstance, powerTable[i].ID)
165166
}
166167
signerPowers += power
167-
signers = append(signers, powerTable[i].PubKey)
168+
mask = append(mask, int(i))
168169
return nil
169170
}); err != nil {
170171
return err
@@ -191,7 +192,12 @@ func verifyFinalityCertificateSignature(verifier gpbft.Verifier, powerTable gpbf
191192
signedBytes = payload.MarshalForSigning(nn)
192193
}
193194

194-
if err := verifier.VerifyAggregate(signedBytes, cert.Signature, signers); err != nil {
195+
aggregate, err := verifier.Aggregate(keys)
196+
if err != nil {
197+
return err
198+
}
199+
200+
if err := aggregate.VerifyAggregate(mask, signedBytes, cert.Signature); err != nil {
195201
return fmt.Errorf("invalid signature on finality certificate for instance %d: %w", cert.GPBFTInstance, err)
196202
}
197203
return nil

emulator/instance.go

+19-15
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import (
1414
// Instance represents a GPBFT instance capturing all the information necessary
1515
// for GPBFT to function, along with the final decision reached if any.
1616
type Instance struct {
17-
t *testing.T
18-
id uint64
19-
supplementalData gpbft.SupplementalData
20-
proposal gpbft.ECChain
21-
powerTable *gpbft.PowerTable
22-
beacon []byte
23-
decision *gpbft.Justification
17+
t *testing.T
18+
id uint64
19+
supplementalData gpbft.SupplementalData
20+
proposal gpbft.ECChain
21+
powerTable *gpbft.PowerTable
22+
aggregateVerifier gpbft.Aggregate
23+
beacon []byte
24+
decision *gpbft.Justification
2425
}
2526

2627
// NewInstance instantiates a new Instance for emulation. If absent, the
@@ -57,12 +58,17 @@ func NewInstance(t *testing.T, id uint64, powerEntries gpbft.PowerEntries, propo
5758
}
5859
proposalChain, err := gpbft.NewChain(proposal[0], proposal[1:]...)
5960
require.NoError(t, err)
61+
62+
aggVerifier, err := signing.Aggregate(pt.Entries.PublicKeys())
63+
require.NoError(t, err)
64+
6065
return &Instance{
61-
t: t,
62-
id: id,
63-
powerTable: pt,
64-
beacon: []byte(fmt.Sprintf("🥓%d", id)),
65-
proposal: proposalChain,
66+
t: t,
67+
id: id,
68+
powerTable: pt,
69+
aggregateVerifier: aggVerifier,
70+
beacon: []byte(fmt.Sprintf("🥓%d", id)),
71+
proposal: proposalChain,
6672
}
6773
}
6874

@@ -129,7 +135,6 @@ func (i *Instance) NewJustification(round uint64, step gpbft.Phase, vote gpbft.E
129135
msg := signing.MarshalPayloadForSigning(networkName, &payload)
130136
qr := gpbft.QuorumResult{
131137
Signers: make([]int, len(from)),
132-
PubKeys: make([]gpbft.PubKey, len(from)),
133138
Signatures: make([][]byte, len(from)),
134139
}
135140
for j, actor := range from {
@@ -139,10 +144,9 @@ func (i *Instance) NewJustification(round uint64, step gpbft.Phase, vote gpbft.E
139144
signature, err := signing.Sign(context.Background(), entry.PubKey, msg)
140145
require.NoError(i.t, err)
141146
qr.Signatures[j] = signature
142-
qr.PubKeys[j] = entry.PubKey
143147
qr.Signers[j] = index
144148
}
145-
aggregate, err := signing.Aggregate(qr.PubKeys, qr.Signatures)
149+
aggregate, err := i.aggregateVerifier.Aggregate(qr.Signers, qr.Signatures)
146150
require.NoError(i.t, err)
147151
return &gpbft.Justification{
148152
Vote: payload,

emulator/signing.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package emulator
33
import (
44
"bytes"
55
"context"
6+
"encoding/binary"
67
"errors"
78
"hash/crc32"
89

@@ -46,13 +47,22 @@ func (s adhocSigning) Verify(sender gpbft.PubKey, msg, got []byte) error {
4647
}
4748
}
4849

49-
func (s adhocSigning) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte, error) {
50-
if len(signers) != len(sigs) {
50+
type aggregate struct {
51+
keys []gpbft.PubKey
52+
signing adhocSigning
53+
}
54+
55+
// Aggregate implements gpbft.Aggregate.
56+
func (a *aggregate) Aggregate(signerMask []int, sigs [][]byte) ([]byte, error) {
57+
if len(signerMask) != len(sigs) {
5158
return nil, errors.New("public keys and signatures length mismatch")
5259
}
5360
hasher := crc32.NewIEEE()
54-
for i, signer := range signers {
55-
if _, err := hasher.Write(signer); err != nil {
61+
for i, bit := range signerMask {
62+
if err := binary.Write(hasher, binary.BigEndian, uint64(bit)); err != nil {
63+
return nil, err
64+
}
65+
if _, err := hasher.Write(a.keys[bit]); err != nil {
5666
return nil, err
5767
}
5868
if _, err := hasher.Write(sigs[i]); err != nil {
@@ -62,16 +72,17 @@ func (s adhocSigning) Aggregate(signers []gpbft.PubKey, sigs [][]byte) ([]byte,
6272
return hasher.Sum(nil), nil
6373
}
6474

65-
func (s adhocSigning) VerifyAggregate(payload, got []byte, signers []gpbft.PubKey) error {
66-
signatures := make([][]byte, len(signers))
75+
// VerifyAggregate implements gpbft.Aggregate.
76+
func (a *aggregate) VerifyAggregate(signerMask []int, payload []byte, got []byte) error {
77+
signatures := make([][]byte, len(signerMask))
6778
var err error
68-
for i, signer := range signers {
69-
signatures[i], err = s.Sign(context.Background(), signer, payload)
79+
for i, bit := range signerMask {
80+
signatures[i], err = a.signing.Sign(context.Background(), a.keys[bit], payload)
7081
if err != nil {
7182
return err
7283
}
7384
}
74-
want, err := s.Aggregate(signers, signatures)
85+
want, err := a.Aggregate(signerMask, signatures)
7586
if err != nil {
7687
return err
7788
}
@@ -81,6 +92,12 @@ func (s adhocSigning) VerifyAggregate(payload, got []byte, signers []gpbft.PubKe
8192
return nil
8293
}
8394

95+
func (s adhocSigning) Aggregate(keys []gpbft.PubKey) (gpbft.Aggregate, error) {
96+
return &aggregate{keys: keys,
97+
signing: s,
98+
}, nil
99+
}
100+
84101
func (s adhocSigning) MarshalPayloadForSigning(name gpbft.NetworkName, payload *gpbft.Payload) []byte {
85102
return payload.MarshalForSigning(name)
86103
}

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,5 @@ require (
143143
gopkg.in/yaml.v3 v3.0.1 // indirect
144144
lukechampine.com/blake3 v1.2.1 // indirect
145145
)
146+
147+
replace github.com/drand/kyber => github.com/Stebalien/kyber v1.3.2-0.20240827162216-c96a0e427578

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
1010
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
1111
github.com/Kubuxu/go-broadcast v0.0.0-20240621161059-1a8c90734cd6 h1:yh2/1fz3ajTaeKskSWxtSBNScdRZfQ/A5nyd9+64T6M=
1212
github.com/Kubuxu/go-broadcast v0.0.0-20240621161059-1a8c90734cd6/go.mod h1:5LOj/fF3Oc/cvJqzDiyfx4XwtBPRWUYEz+V+b13sH5U=
13+
github.com/Stebalien/kyber v1.3.2-0.20240827162216-c96a0e427578 h1:dx1hCR7KbG1HbehvPPRJKExoI9COfy8eMg7sCidKJEs=
14+
github.com/Stebalien/kyber v1.3.2-0.20240827162216-c96a0e427578/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA=
1315
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
1416
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
1517
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@@ -49,8 +51,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3
4951
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
5052
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
5153
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
52-
github.com/drand/kyber v1.3.1 h1:E0p6M3II+loMVwTlAp5zu4+GGZFNiRfq02qZxzw2T+Y=
53-
github.com/drand/kyber v1.3.1/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA=
5454
github.com/drand/kyber-bls12381 v0.3.1 h1:KWb8l/zYTP5yrvKTgvhOrk2eNPscbMiUOIeWBnmUxGo=
5555
github.com/drand/kyber-bls12381 v0.3.1/go.mod h1:H4y9bLPu7KZA/1efDg+jtJ7emKx+ro3PU7/jWUVt140=
5656
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=

gpbft/api.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,27 @@ type SigningMarshaler interface {
100100
MarshalPayloadForSigning(NetworkName, *Payload) []byte
101101
}
102102

103+
type Aggregate interface {
104+
// Aggregates signatures from a participants.
105+
//
106+
// Implementations must be safe for concurrent use.
107+
Aggregate(signerMask []int, sigs [][]byte) ([]byte, error)
108+
// VerifyAggregate verifies an aggregate signature.
109+
//
110+
// Implementations must be safe for concurrent use.
111+
VerifyAggregate(signerMask []int, payload, aggSig []byte) error
112+
}
113+
103114
type Verifier interface {
104115
// Verifies a signature for the given public key.
116+
//
105117
// Implementations must be safe for concurrent use.
106118
Verify(pubKey PubKey, msg, sig []byte) error
107-
// Aggregates signatures from a participants.
108-
Aggregate(pubKeys []PubKey, sigs [][]byte) ([]byte, error)
109-
// VerifyAggregate verifies an aggregate signature.
119+
// Return an Aggregate that can aggregate and verify aggregate signatures made by the given
120+
// public keys.
121+
//
110122
// Implementations must be safe for concurrent use.
111-
VerifyAggregate(payload, aggSig []byte, signers []PubKey) error
123+
Aggregate(pubKeys []PubKey) (Aggregate, error)
112124
}
113125

114126
type Signatures interface {

0 commit comments

Comments
 (0)