Skip to content

Commit

Permalink
Introduce a new CachedMask for BDN
Browse files Browse the repository at this point in the history
This new mask will pre-compute reusable values, speeding up repeated
verification and aggregation of aggregate signatures (mostly the former).
  • Loading branch information
Stebalien committed Aug 27, 2024
1 parent 307e6dc commit 8fd7d55
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 15 deletions.
25 changes: 10 additions & 15 deletions sign/bdn/bdn.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,13 @@ func (scheme *Scheme) Verify(x kyber.Point, msg, sig []byte) error {

// AggregateSignatures aggregates the signatures using a coefficient for each
// one of them where c = H(pk) and H: keyGroup -> R with R = {1, ..., 2^128}
func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *sign.Mask) (kyber.Point, error) {
publics := mask.Publics()
coefs, err := hashPointToR(publics)
func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask Mask) (kyber.Point, error) {
bdnMask, err := newCachedMask(mask, false)
if err != nil {
return nil, err
}

agg := scheme.sigGroup.Point()
for i := range publics {
for i := range bdnMask.publics {
if enabled, err := mask.GetBit(i); err != nil {
// this should never happen because of the loop boundary
// an error here is probably a bug in the mask implementation
Expand All @@ -152,7 +150,7 @@ func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *sign.Mask) (kyber
return nil, err
}

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

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

pubC := pub.Clone().Mul(coefs[i], pub)
pubC = pubC.Add(pubC, pub)
agg = agg.Add(agg, pubC)
agg = agg.Add(agg, bdnMask.getOrComputePubC(i))
}

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

// AggregatePublicKeys aggregates a set of public keys (similarly to
// AggregateSignatures for signatures) using the hash function
// H: G2 -> R with R = {1, ..., 2^128}.
// Deprecated: use the new scheme methods instead.
func AggregatePublicKeys(suite pairing.Suite, mask *sign.Mask) (kyber.Point, error) {
func AggregatePublicKeys(suite pairing.Suite, mask Mask) (kyber.Point, error) {
return NewSchemeOnG1(suite).AggregatePublicKeys(mask)
}
106 changes: 106 additions & 0 deletions sign/bdn/mask.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package bdn

import (
"fmt"

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

type Mask interface {
GetBit(i int) (bool, error)
SetBit(i int, enable bool) error

IndexOfNthEnabled(nth int) int
NthEnabledAtIndex(idx int) int

Publics() []kyber.Point
Participants() []kyber.Point

CountEnabled() int
CountTotal() int

Len() int
Mask() []byte
SetMask(mask []byte) error
Merge(mask []byte) error
}

var _ Mask = (*sign.Mask)(nil)

// We need to rename this, otherwise we have a public field named Mask (when we embed it) which
// conflicts with the function named Mask. It also makes it private, which is nice.
type maskI = Mask

type CachedMask struct {
maskI
coefs []kyber.Scalar
pubKeyC []kyber.Point
// We could call Mask.Publics() instead of keeping these here, but that function copies the
// slice and this field lets us avoid that copy.
publics []kyber.Point
}

// Convert the passed mask (likely a *sign.Mask) into a BDN-specific cached mask. This cached mask
// will pre-compute coefficients and pre-aggregation public key points, slightly speeding up
// signature aggregation and significantly speeding up public key aggregation.
func NewCachedMask(mask Mask) (*CachedMask, error) {
return newCachedMask(mask, true)
}

func newCachedMask(mask Mask, precomputePubC bool) (*CachedMask, error) {
if m, ok := mask.(*CachedMask); ok {
return m, nil
}

publics := mask.Publics()
coefs, err := hashPointToR(publics)
if err != nil {
return nil, fmt.Errorf("failed to hash public keys: %w", err)
}

cm := &CachedMask{
maskI: mask,
coefs: coefs,
publics: publics,
}

if precomputePubC {
pubKeyC := make([]kyber.Point, len(publics))
for i := range publics {
pubKeyC[i] = cm.getOrComputePubC(i)
}
cm.pubKeyC = pubKeyC
}

return cm, err
}

// Clone copies the BDN mask while keeping the precomputed coefficients, etc.
func (cm *CachedMask) Clone() *CachedMask {
newMask, err := sign.NewMask(nil, cm.publics, nil)
if err != nil {
// Not possible given that we didn't pass our own key.
panic(fmt.Sprintf("failed to create mask: %s", err))
}
if err := newMask.SetMask(cm.maskI.Mask()); err != nil {
// Not possible given that we're using the same sized mask.
panic(fmt.Sprintf("failed to create mask: %s", err))
}
return &CachedMask{
maskI: newMask,
coefs: cm.coefs,
pubKeyC: cm.pubKeyC,
publics: cm.publics,
}
}

func (cm *CachedMask) getOrComputePubC(i int) kyber.Point {
if cm.pubKeyC == nil {
// NOTE: don't cache here as we may be sharing this mask between threads.
pub := cm.publics[i]
pubC := pub.Clone().Mul(cm.coefs[i], pub)
return pubC.Add(pubC, pub)
}
return cm.pubKeyC[i]
}

0 comments on commit 8fd7d55

Please sign in to comment.