Skip to content

Commit 6b09955

Browse files
committed
Introduce a new CachedMask for BDN
This new mask will pre-compute reusable values, speeding up repeated verification and aggregation of aggregate signatures (mostly the former).
1 parent 549526a commit 6b09955

File tree

3 files changed

+163
-15
lines changed

3 files changed

+163
-15
lines changed

sign/bdn/bdn.go

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,13 @@ func (scheme *Scheme) Verify(x kyber.Point, msg, sig []byte) error {
122122

123123
// AggregateSignatures aggregates the signatures using a coefficient for each
124124
// one of them where c = H(pk) and H: keyGroup -> R with R = {1, ..., 2^128}
125-
func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *sign.Mask) (kyber.Point, error) {
126-
publics := mask.Publics()
127-
coefs, err := hashPointToR(publics)
125+
func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask Mask) (kyber.Point, error) {
126+
bdnMask, err := newCachedMask(mask, false)
128127
if err != nil {
129128
return nil, err
130129
}
131-
132130
agg := scheme.sigGroup.Point()
133-
for i := range publics {
131+
for i := range bdnMask.publics {
134132
if enabled, err := mask.GetBit(i); err != nil {
135133
// this should never happen because of the loop boundary
136134
// an error here is probably a bug in the mask implementation
@@ -152,7 +150,7 @@ func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *sign.Mask) (kyber
152150
return nil, err
153151
}
154152

155-
sigC := sig.Clone().Mul(coefs[i], sig)
153+
sigC := sig.Clone().Mul(bdnMask.coefs[i], sig)
156154
// c+1 because R is in the range [1, 2^128] and not [0, 2^128-1]
157155
sigC = sigC.Add(sigC, sig)
158156
agg = agg.Add(agg, sigC)
@@ -164,15 +162,14 @@ func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *sign.Mask) (kyber
164162
// AggregatePublicKeys aggregates a set of public keys (similarly to
165163
// AggregateSignatures for signatures) using the hash function
166164
// H: keyGroup -> R with R = {1, ..., 2^128}.
167-
func (scheme *Scheme) AggregatePublicKeys(mask *sign.Mask) (kyber.Point, error) {
168-
publics := mask.Publics()
169-
coefs, err := hashPointToR(publics)
165+
func (scheme *Scheme) AggregatePublicKeys(mask Mask) (kyber.Point, error) {
166+
bdnMask, err := newCachedMask(mask, false)
170167
if err != nil {
171168
return nil, err
172169
}
173170

174171
agg := scheme.keyGroup.Point()
175-
for i, pub := range publics {
172+
for i := range bdnMask.publics {
176173
if enabled, err := mask.GetBit(i); err != nil {
177174
// this should never happen because of the loop boundary
178175
// an error here is probably a bug in the mask implementation
@@ -181,9 +178,7 @@ func (scheme *Scheme) AggregatePublicKeys(mask *sign.Mask) (kyber.Point, error)
181178
continue
182179
}
183180

184-
pubC := pub.Clone().Mul(coefs[i], pub)
185-
pubC = pubC.Add(pubC, pub)
186-
agg = agg.Add(agg, pubC)
181+
agg = agg.Add(agg, bdnMask.getOrComputePubC(i))
187182
}
188183

189184
return agg, nil
@@ -217,14 +212,14 @@ func Verify(suite pairing.Suite, x kyber.Point, msg, sig []byte) error {
217212
// AggregateSignatures aggregates the signatures using a coefficient for each
218213
// one of them where c = H(pk) and H: G2 -> R with R = {1, ..., 2^128}
219214
// Deprecated: use the new scheme methods instead.
220-
func AggregateSignatures(suite pairing.Suite, sigs [][]byte, mask *sign.Mask) (kyber.Point, error) {
215+
func AggregateSignatures(suite pairing.Suite, sigs [][]byte, mask Mask) (kyber.Point, error) {
221216
return NewSchemeOnG1(suite).AggregateSignatures(sigs, mask)
222217
}
223218

224219
// AggregatePublicKeys aggregates a set of public keys (similarly to
225220
// AggregateSignatures for signatures) using the hash function
226221
// H: G2 -> R with R = {1, ..., 2^128}.
227222
// Deprecated: use the new scheme methods instead.
228-
func AggregatePublicKeys(suite pairing.Suite, mask *sign.Mask) (kyber.Point, error) {
223+
func AggregatePublicKeys(suite pairing.Suite, mask Mask) (kyber.Point, error) {
229224
return NewSchemeOnG1(suite).AggregatePublicKeys(mask)
230225
}

sign/bdn/bdn_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/stretchr/testify/require"
1010
"go.dedis.ch/kyber/v4"
11+
"go.dedis.ch/kyber/v4/pairing/bls12381/kilic"
1112
"go.dedis.ch/kyber/v4/pairing/bn256"
1213
"go.dedis.ch/kyber/v4/sign"
1314
"go.dedis.ch/kyber/v4/sign/bls"
@@ -161,6 +162,46 @@ func Benchmark_BDN_AggregateSigs(b *testing.B) {
161162
}
162163
}
163164

165+
func Benchmark_BDN_BLS12381_AggregateVerify(b *testing.B) {
166+
suite := kilic.NewBLS12381Suite()
167+
schemeOnG2 := NewSchemeOnG2(suite)
168+
169+
rng := random.New()
170+
pubKeys := make([]kyber.Point, 3000)
171+
privKeys := make([]kyber.Scalar, 3000)
172+
for i := range pubKeys {
173+
privKeys[i], pubKeys[i] = schemeOnG2.NewKeyPair(rng)
174+
}
175+
176+
baseMask, err := sign.NewMask(pubKeys, nil)
177+
require.NoError(b, err)
178+
mask, err := NewCachedMask(baseMask)
179+
require.NoError(b, err)
180+
for i := range pubKeys {
181+
require.NoError(b, mask.SetBit(i, true))
182+
}
183+
184+
msg := []byte("Hello many times Boneh-Lynn-Shacham")
185+
sigs := make([][]byte, len(privKeys))
186+
for i, k := range privKeys {
187+
s, err := schemeOnG2.Sign(k, msg)
188+
require.NoError(b, err)
189+
sigs[i] = s
190+
}
191+
192+
sig, err := schemeOnG2.AggregateSignatures(sigs, mask)
193+
require.NoError(b, err)
194+
sigb, err := sig.MarshalBinary()
195+
require.NoError(b, err)
196+
197+
b.ResetTimer()
198+
for i := 0; i < b.N; i++ {
199+
pk, err := schemeOnG2.AggregatePublicKeys(mask)
200+
require.NoError(b, err)
201+
require.NoError(b, schemeOnG2.Verify(pk, msg, sigb))
202+
}
203+
}
204+
164205
func unmarshalHex[T encoding.BinaryUnmarshaler](t *testing.T, into T, s string) T {
165206
t.Helper()
166207
b, err := hex.DecodeString(s)

sign/bdn/mask.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package bdn
2+
3+
import (
4+
"fmt"
5+
6+
"go.dedis.ch/kyber/v4"
7+
"go.dedis.ch/kyber/v4/sign"
8+
)
9+
10+
type Mask interface {
11+
GetBit(i int) (bool, error)
12+
SetBit(i int, enable bool) error
13+
14+
IndexOfNthEnabled(nth int) int
15+
NthEnabledAtIndex(idx int) int
16+
17+
Publics() []kyber.Point
18+
Participants() []kyber.Point
19+
20+
CountEnabled() int
21+
CountTotal() int
22+
23+
Len() int
24+
Mask() []byte
25+
SetMask(mask []byte) error
26+
Merge(mask []byte) error
27+
}
28+
29+
var _ Mask = (*sign.Mask)(nil)
30+
31+
// We need to rename this, otherwise we have a public field named Mask (when we embed it) which
32+
// conflicts with the function named Mask. It also makes it private, which is nice.
33+
type maskI = Mask
34+
35+
type CachedMask struct {
36+
maskI
37+
coefs []kyber.Scalar
38+
pubKeyC []kyber.Point
39+
// We could call Mask.Publics() instead of keeping these here, but that function copies the
40+
// slice and this field lets us avoid that copy.
41+
publics []kyber.Point
42+
}
43+
44+
// Convert the passed mask (likely a *sign.Mask) into a BDN-specific mask with pre-computed terms.
45+
//
46+
// This cached mask will:
47+
//
48+
// 1. Pre-compute coefficients for signature aggregation. Once the CachedMask has been instantiated,
49+
// distinct sets of signatures can be aggregated without any BLAKE2S hashing.
50+
// 2. Pre-computes the terms for public key aggregation. Once the CachedMask has been instantiated,
51+
// distinct sets of public keys can be aggregated by simply summing the cached terms, ~2 orders
52+
// of magnitude faster than aggregating from scratch.
53+
func NewCachedMask(mask Mask) (*CachedMask, error) {
54+
return newCachedMask(mask, true)
55+
}
56+
57+
func newCachedMask(mask Mask, precomputePubC bool) (*CachedMask, error) {
58+
if m, ok := mask.(*CachedMask); ok {
59+
return m, nil
60+
}
61+
62+
publics := mask.Publics()
63+
coefs, err := hashPointToR(publics)
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to hash public keys: %w", err)
66+
}
67+
68+
cm := &CachedMask{
69+
maskI: mask,
70+
coefs: coefs,
71+
publics: publics,
72+
}
73+
74+
if precomputePubC {
75+
pubKeyC := make([]kyber.Point, len(publics))
76+
for i := range publics {
77+
pubKeyC[i] = cm.getOrComputePubC(i)
78+
}
79+
cm.pubKeyC = pubKeyC
80+
}
81+
82+
return cm, err
83+
}
84+
85+
// Clone copies the BDN mask while keeping the precomputed coefficients, etc.
86+
func (cm *CachedMask) Clone() *CachedMask {
87+
newMask, err := sign.NewMask(cm.publics, nil)
88+
if err != nil {
89+
// Not possible given that we didn't pass our own key.
90+
panic(fmt.Sprintf("failed to create mask: %s", err))
91+
}
92+
if err := newMask.SetMask(cm.Mask()); err != nil {
93+
// Not possible given that we're using the same sized mask.
94+
panic(fmt.Sprintf("failed to create mask: %s", err))
95+
}
96+
return &CachedMask{
97+
maskI: newMask,
98+
coefs: cm.coefs,
99+
pubKeyC: cm.pubKeyC,
100+
publics: cm.publics,
101+
}
102+
}
103+
104+
func (cm *CachedMask) getOrComputePubC(i int) kyber.Point {
105+
if cm.pubKeyC == nil {
106+
// NOTE: don't cache here as we may be sharing this mask between threads.
107+
pub := cm.publics[i]
108+
pubC := pub.Clone().Mul(cm.coefs[i], pub)
109+
return pubC.Add(pubC, pub)
110+
}
111+
return cm.pubKeyC[i]
112+
}

0 commit comments

Comments
 (0)