From 7738a1ff8bc688eef0b1ccfd54921e312f02679a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 26 Aug 2024 15:40:19 -0700 Subject: [PATCH 1/8] Add BDN test fixtures --- sign/bdn/bdn_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/sign/bdn/bdn_test.go b/sign/bdn/bdn_test.go index 46aa659d8..cacfc99a0 100644 --- a/sign/bdn/bdn_test.go +++ b/sign/bdn/bdn_test.go @@ -1,6 +1,8 @@ package bdn import ( + "encoding" + "encoding/hex" "fmt" "testing" @@ -158,3 +160,61 @@ func Benchmark_BDN_AggregateSigs(b *testing.B) { AggregateSignatures(suite, [][]byte{sig1, sig2}, mask) } } + +func unmarshalHex[T encoding.BinaryUnmarshaler](t *testing.T, into T, s string) T { + t.Helper() + b, err := hex.DecodeString(s) + require.NoError(t, err) + require.NoError(t, into.UnmarshalBinary(b)) + return into +} + +// This tests exists to make sure we don't accidentally make breaking changes to signature +// aggregation by using checking against known aggregated signatures and keys. +func TestBDNFixtures(t *testing.T) { + suite := bn256.NewSuite() + schemeOnG1 := NewSchemeOnG1(suite) + + public1 := unmarshalHex(t, suite.G2().Point(), "1a30714035c7a161e286e54c191b8c68345bd8239c74925a26290e8e1ae97ed6657958a17dca12c943fadceb11b824402389ff427179e0f10194da3c1b771c6083797d2b5915ea78123cbdb99ea6389d6d6b67dcb512a2b552c373094ee5693524e3ebb4a176f7efa7285c25c80081d8cb598745978f1a63b886c09a316b1493") + private1 := unmarshalHex(t, suite.G2().Scalar(), "49cfe5e9f4532670137184d43c0299f8b635bcacf6b0af7cab262494602d9f38") + public2 := unmarshalHex(t, suite.G2().Point(), "603bc61466ec8762ec6de2ba9a80b9d302d08f580d1685ac45a8e404a6ed549719dc0faf94d896a9983ff23423772720e3de5d800bc200de6f7d7e146162d3183b8880c5c0d8b71ca4b3b40f30c12d8cc0679c81a47c239c6aa7e9cc2edab4a927fe865cd413c1c17e3df8f74108e784cd77dd3e161bdaf30019a55826a32a1f") + private2 := unmarshalHex(t, suite.G2().Scalar(), "493abea4bb35b74c78ad9245f9d37883aeb6ee91f7fb0d8a8e11abf7aa2be581") + public3 := unmarshalHex(t, suite.G2().Point(), "56118769a1f0b6286abacaa32109c1497ab0819c5d21f27317e184b6681c283007aa981cb4760de044946febdd6503ab77a4586bc29c04159e53a6fa5dcb9c0261ccd1cb2e28db5204ca829ac9f6be95f957a626544adc34ba3bc542533b6e2f5cbd0567e343641a61a42b63f26c3625f74b66f6f46d17b3bf1688fae4d455ec") + private3 := unmarshalHex(t, suite.G2().Scalar(), "7fb0ebc317e161502208c3c16a4af890dedc3c7b275e8a04e99c0528aa6a19aa") + + sig1Exp, err := hex.DecodeString("0913b76987be19f943be23b636cab9a2484507717326bd8bbdcdbbb6b8d5eb9253cfb3597c3fa550ee4972a398813650825a871f8e0b242ae5ddbce1b7c0e2a8") + require.NoError(t, err) + sig2Exp, err := hex.DecodeString("21195d29b1863bca1559e24375211d1411d8a28a8f4c772870b07f4ccda2fd5e337c1315c210475c683e3aa8b87d3aed3f7255b3087daa30d1e1432dd61d7484") + require.NoError(t, err) + sig3Exp, err := hex.DecodeString("3c1ac80345c1733630dbdc8106925c867544b521c259f9fa9678d477e6e5d3d212b09bc0d95137c3dbc0af2241415156c56e757d5577a609293584d045593195") + require.NoError(t, err) + + aggSigExp := unmarshalHex(t, suite.G1().Point(), "43c1d2ad5a7d71a08f3cd7495db6b3c81a4547af1b76438b2f215e85ec178fea048f93f6ffed65a69ea757b47761e7178103bb347fd79689652e55b6e0054af2") + aggKeyExp := unmarshalHex(t, suite.G2().Point(), "43b5161ede207b9a69fc93114b0c5022b76cc22e813ba739c7e622d826b132333cd637505399963b94e393ec7f5d4875f82391620b34be1fde1f232204fa4f723935d4dbfb725f059456bcf2557f846c03190969f7b800e904d25b0b5bcbdd421c9877d443f0313c3425dfc1e7e646b665d27b9e649faadef1129f95670d70e1") + + msg := []byte("Hello many times Boneh-Lynn-Shacham") + sig1, err := schemeOnG1.Sign(private1, msg) + require.Nil(t, err) + require.Equal(t, sig1Exp, sig1) + + sig2, err := schemeOnG1.Sign(private2, msg) + require.Nil(t, err) + require.Equal(t, sig2Exp, sig2) + + sig3, err := schemeOnG1.Sign(private3, msg) + require.Nil(t, err) + require.Equal(t, sig3Exp, sig3) + + mask, _ := sign.NewMask([]kyber.Point{public1, public2, public3}, nil) + mask.SetBit(0, true) + mask.SetBit(1, false) + mask.SetBit(2, true) + + aggSig, err := schemeOnG1.AggregateSignatures([][]byte{sig1, sig3}, mask) + require.NoError(t, err) + require.True(t, aggSigExp.Equal(aggSig)) + + aggKey, err := schemeOnG1.AggregatePublicKeys(mask) + require.NoError(t, err) + require.True(t, aggKeyExp.Equal(aggKey)) +} From fa46ad8bf3ed612609c4e158662172b049b70529 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 9 Aug 2024 14:51:09 -0700 Subject: [PATCH 2/8] Remove n^2 algorithm from signature/key aggregation CountEnabled and IndexOfNthEnabled are both O(n) in the size of the mask, making this loop n^2. The BLS operations still tend to be the slow part, but the n^2 factor will start to show up with thousands of keys. --- sign/bdn/bdn.go | 50 ++++++++++++++++++++++++++++------------------- sign/mask.go | 11 +++++++++++ sign/mask_test.go | 43 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/sign/bdn/bdn.go b/sign/bdn/bdn.go index b7716f6ce..d50e00cd0 100644 --- a/sign/bdn/bdn.go +++ b/sign/bdn/bdn.go @@ -12,6 +12,7 @@ package bdn import ( "crypto/cipher" "errors" + "fmt" "math/big" "go.dedis.ch/kyber/v4" @@ -129,36 +130,44 @@ 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) { - if len(sigs) != mask.CountEnabled() { - return nil, errors.New("length of signatures and public keys must match") - } - - coefs, err := hashPointToR(mask.Publics()) + publics := mask.Publics() + coefs, err := hashPointToR(publics) if err != nil { return nil, err } - agg := scheme.sigGroup.Point() - for i, buf := range sigs { - peerIndex := mask.IndexOfNthEnabled(i) - if peerIndex < 0 { - // this should never happen as we check the lenths at the beginning - // an error here is probably a bug in the mask - return nil, errors.New("couldn't find the index") + for i := range 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 + return nil, fmt.Errorf("couldn't find the index %d: %w", i, err) + } else if !enabled { + continue } + if len(sigs) == 0 { + return nil, errors.New("length of signatures and public keys must match") + } + + buf := sigs[0] + sigs = sigs[1:] + sig := scheme.sigGroup.Point() err = sig.UnmarshalBinary(buf) if err != nil { return nil, err } - sigC := sig.Clone().Mul(coefs[peerIndex], sig) + sigC := sig.Clone().Mul(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) } + if len(sigs) > 0 { + return nil, errors.New("length of signatures and public keys must match") + } + return agg, nil } @@ -166,22 +175,23 @@ func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *sign.Mask) (kyber // 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) { - coefs, err := hashPointToR(mask.Publics()) + publics := mask.Publics() + coefs, err := hashPointToR(publics) if err != nil { return nil, err } agg := scheme.keyGroup.Point() - for i := 0; i < mask.CountEnabled(); i++ { - peerIndex := mask.IndexOfNthEnabled(i) - if peerIndex < 0 { + for i, pub := range 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 - return nil, errors.New("couldn't find the index") + return nil, fmt.Errorf("couldn't find the index %d: %w", i, err) + } else if !enabled { + continue } - pub := mask.Publics()[peerIndex] - pubC := pub.Clone().Mul(coefs[peerIndex], pub) + pubC := pub.Clone().Mul(coefs[i], pub) pubC = pubC.Add(pubC, pub) agg = agg.Add(agg, pubC) } diff --git a/sign/mask.go b/sign/mask.go index 7c68c9132..d02692c32 100644 --- a/sign/mask.go +++ b/sign/mask.go @@ -58,6 +58,17 @@ func (m *Mask) SetMask(mask []byte) error { return nil } +// GetBit returns true if the given bit is set. +func (m *Mask) GetBit(i int) (bool, error) { + if i >= len(m.publics) || i < 0 { + return false, errors.New("index out of range") + } + + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + return m.mask[byteIndex]&mask != 0, nil +} + // SetBit turns on or off the bit at the given index. func (m *Mask) SetBit(i int, enable bool) error { if i >= len(m.publics) || i < 0 { diff --git a/sign/mask_test.go b/sign/mask_test.go index 7a3eeb118..5eb911755 100644 --- a/sign/mask_test.go +++ b/sign/mask_test.go @@ -49,22 +49,63 @@ func TestMask_SetBit(t *testing.T) { mask, err := NewMask(publics, publics[2]) require.NoError(t, err) + // Make sure the mask is initially as we'd expect. + + bit, err := mask.GetBit(1) + require.NoError(t, err) + require.False(t, bit) + + bit, err = mask.GetBit(2) + require.NoError(t, err) + require.True(t, bit) + + // Set bit 1 + err = mask.SetBit(1, true) require.NoError(t, err) require.Equal(t, uint8(0x6), mask.Mask()[0]) require.Equal(t, 2, len(mask.Participants())) - // Set it again, nothing should change. + bit, err = mask.GetBit(1) + require.NoError(t, err) + require.True(t, bit) + + // Set bit 1 again, nothing should change + err = mask.SetBit(1, true) require.NoError(t, err) require.Equal(t, uint8(0x6), mask.Mask()[0]) require.Equal(t, 2, len(mask.Participants())) + bit, err = mask.GetBit(1) + require.NoError(t, err) + require.True(t, bit) + + // Unset bit 2 + err = mask.SetBit(2, false) require.NoError(t, err) require.Equal(t, uint8(0x2), mask.Mask()[0]) require.Equal(t, 1, len(mask.Participants())) + bit, err = mask.GetBit(2) + require.NoError(t, err) + require.False(t, bit) + + // Set bit 10 (using byte 2 now) + + err = mask.SetBit(10, true) + require.NoError(t, err) + require.Equal(t, uint8(0x2), mask.Mask()[0]) + require.Equal(t, uint8(0x4), mask.Mask()[1]) + require.Equal(t, 2, len(mask.Participants())) + + bit, err = mask.GetBit(10) + require.NoError(t, err) + require.True(t, bit) + + // And make sure the range limit works. + err = mask.SetBit(-1, true) require.Error(t, err) err = mask.SetBit(len(publics), true) From ff60afc436af3446887ab06d00e905ca482a3cce Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 13 Aug 2024 07:49:22 -0700 Subject: [PATCH 3/8] Remove an unnecessary loop from hashPointToR --- sign/bdn/bdn.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/sign/bdn/bdn.go b/sign/bdn/bdn.go index d50e00cd0..2781792b4 100644 --- a/sign/bdn/bdn.go +++ b/sign/bdn/bdn.go @@ -32,23 +32,16 @@ var modulus128 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 128), big.NewI // We also use the entire roster so that the coefficient will vary for the same // public key used in different roster func hashPointToR(pubs []kyber.Point) ([]kyber.Scalar, error) { - peers := make([][]byte, len(pubs)) - for i, pub := range pubs { - peer, err := pub.MarshalBinary() - if err != nil { - return nil, err - } - - peers[i] = peer - } - h, err := blake2s.NewXOF(blake2s.OutputLengthUnknown, nil) if err != nil { return nil, err } - - for _, peer := range peers { - _, err := h.Write(peer) + for _, pub := range pubs { + peer, err := pub.MarshalBinary() + if err != nil { + return nil, err + } + _, err = h.Write(peer) if err != nil { return nil, err } From 9acfb6fcce75d2432ed08e6230d6ab4599a5785a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 26 Aug 2024 17:40:46 -0700 Subject: [PATCH 4/8] 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). --- sign/bdn/bdn.go | 24 ++++------ sign/bdn/bdn_test.go | 41 ++++++++++++++++ sign/bdn/mask.go | 112 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 sign/bdn/mask.go diff --git a/sign/bdn/bdn.go b/sign/bdn/bdn.go index 2781792b4..cd93ef735 100644 --- a/sign/bdn/bdn.go +++ b/sign/bdn/bdn.go @@ -122,14 +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 @@ -151,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) @@ -167,15 +166,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 @@ -184,9 +182,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 @@ -220,7 +216,7 @@ 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) } @@ -228,6 +224,6 @@ func AggregateSignatures(suite pairing.Suite, sigs [][]byte, mask *sign.Mask) (k // 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) } diff --git a/sign/bdn/bdn_test.go b/sign/bdn/bdn_test.go index cacfc99a0..55f929aa0 100644 --- a/sign/bdn/bdn_test.go +++ b/sign/bdn/bdn_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" "go.dedis.ch/kyber/v4/pairing/bn256" "go.dedis.ch/kyber/v4/sign" "go.dedis.ch/kyber/v4/sign/bls" @@ -161,6 +162,46 @@ func Benchmark_BDN_AggregateSigs(b *testing.B) { } } +func Benchmark_BDN_BLS12381_AggregateVerify(b *testing.B) { + suite := kilic.NewBLS12381Suite() + schemeOnG2 := NewSchemeOnG2(suite) + + rng := random.New() + pubKeys := make([]kyber.Point, 3000) + privKeys := make([]kyber.Scalar, 3000) + for i := range pubKeys { + privKeys[i], pubKeys[i] = schemeOnG2.NewKeyPair(rng) + } + + baseMask, err := sign.NewMask(pubKeys, nil) + require.NoError(b, err) + mask, err := NewCachedMask(baseMask) + require.NoError(b, err) + for i := range pubKeys { + require.NoError(b, mask.SetBit(i, true)) + } + + msg := []byte("Hello many times Boneh-Lynn-Shacham") + sigs := make([][]byte, len(privKeys)) + for i, k := range privKeys { + s, err := schemeOnG2.Sign(k, msg) + require.NoError(b, err) + sigs[i] = s + } + + sig, err := schemeOnG2.AggregateSignatures(sigs, mask) + require.NoError(b, err) + sigb, err := sig.MarshalBinary() + require.NoError(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + pk, err := schemeOnG2.AggregatePublicKeys(mask) + require.NoError(b, err) + require.NoError(b, schemeOnG2.Verify(pk, msg, sigb)) + } +} + func unmarshalHex[T encoding.BinaryUnmarshaler](t *testing.T, into T, s string) T { t.Helper() b, err := hex.DecodeString(s) diff --git a/sign/bdn/mask.go b/sign/bdn/mask.go new file mode 100644 index 000000000..f3899c702 --- /dev/null +++ b/sign/bdn/mask.go @@ -0,0 +1,112 @@ +package bdn + +import ( + "fmt" + + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/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 mask with pre-computed terms. +// +// This cached mask will: +// +// 1. Pre-compute coefficients for signature aggregation. Once the CachedMask has been instantiated, +// distinct sets of signatures can be aggregated without any BLAKE2S hashing. +// 2. Pre-computes the terms for public key aggregation. Once the CachedMask has been instantiated, +// distinct sets of public keys can be aggregated by simply summing the cached terms, ~2 orders +// of magnitude faster than aggregating from scratch. +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(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.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] +} From eb8f760ca2dfd944b76103c6484943c6c1a071b3 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 6 Sep 2024 07:57:53 -0700 Subject: [PATCH 5/8] Ignore golangci lint --- sign/bdn/mask.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sign/bdn/mask.go b/sign/bdn/mask.go index f3899c702..3cb90bc60 100644 --- a/sign/bdn/mask.go +++ b/sign/bdn/mask.go @@ -7,6 +7,7 @@ import ( "go.dedis.ch/kyber/v4/sign" ) +//nolint:interfacebloat type Mask interface { GetBit(i int) (bool, error) SetBit(i int, enable bool) error From 39ccfaabc6c0b88608792a4d7ca395fe9f1de664 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 9 Sep 2024 11:03:16 -0700 Subject: [PATCH 6/8] Move Mask into BDN and remove the interface --- sign/bdn/bdn.go | 27 ++-- sign/bdn/bdn_test.go | 17 +-- sign/bdn/mask.go | 256 +++++++++++++++++++++++++----------- sign/{ => bdn}/mask_test.go | 4 +- sign/mask.go | 183 -------------------------- 5 files changed, 196 insertions(+), 291 deletions(-) rename sign/{ => bdn}/mask_test.go (97%) delete mode 100644 sign/mask.go diff --git a/sign/bdn/bdn.go b/sign/bdn/bdn.go index cd93ef735..71dc63267 100644 --- a/sign/bdn/bdn.go +++ b/sign/bdn/bdn.go @@ -122,13 +122,9 @@ 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 Mask) (kyber.Point, error) { - bdnMask, err := newCachedMask(mask, false) - if err != nil { - return nil, err - } +func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask *Mask) (kyber.Point, error) { agg := scheme.sigGroup.Point() - for i := range bdnMask.publics { + for i := range mask.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 @@ -145,12 +141,12 @@ func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask Mask) (kyber.Point sigs = sigs[1:] sig := scheme.sigGroup.Point() - err = sig.UnmarshalBinary(buf) + err := sig.UnmarshalBinary(buf) if err != nil { return nil, err } - sigC := sig.Clone().Mul(bdnMask.coefs[i], sig) + sigC := sig.Clone().Mul(mask.publicCoefs[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) @@ -166,14 +162,9 @@ func (scheme *Scheme) AggregateSignatures(sigs [][]byte, mask Mask) (kyber.Point // 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 Mask) (kyber.Point, error) { - bdnMask, err := newCachedMask(mask, false) - if err != nil { - return nil, err - } - +func (scheme *Scheme) AggregatePublicKeys(mask *Mask) (kyber.Point, error) { agg := scheme.keyGroup.Point() - for i := range bdnMask.publics { + for i := range mask.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 @@ -182,7 +173,7 @@ func (scheme *Scheme) AggregatePublicKeys(mask Mask) (kyber.Point, error) { continue } - agg = agg.Add(agg, bdnMask.getOrComputePubC(i)) + agg = agg.Add(agg, mask.publicTerms[i]) } return agg, nil @@ -216,7 +207,7 @@ 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 Mask) (kyber.Point, error) { +func AggregateSignatures(suite pairing.Suite, sigs [][]byte, mask *Mask) (kyber.Point, error) { return NewSchemeOnG1(suite).AggregateSignatures(sigs, mask) } @@ -224,6 +215,6 @@ func AggregateSignatures(suite pairing.Suite, sigs [][]byte, mask Mask) (kyber.P // 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 Mask) (kyber.Point, error) { +func AggregatePublicKeys(suite pairing.Suite, mask *Mask) (kyber.Point, error) { return NewSchemeOnG1(suite).AggregatePublicKeys(mask) } diff --git a/sign/bdn/bdn_test.go b/sign/bdn/bdn_test.go index 55f929aa0..b686e4a66 100644 --- a/sign/bdn/bdn_test.go +++ b/sign/bdn/bdn_test.go @@ -10,7 +10,6 @@ import ( "go.dedis.ch/kyber/v4" "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" "go.dedis.ch/kyber/v4/pairing/bn256" - "go.dedis.ch/kyber/v4/sign" "go.dedis.ch/kyber/v4/sign/bls" "go.dedis.ch/kyber/v4/util/random" ) @@ -33,7 +32,7 @@ func TestBDN_HashPointToR_BN256(t *testing.T) { require.Equal(t, "933f6013eb3f654f9489d6d45ad04eaf", coefs[2].String()) require.Equal(t, 16, coefs[0].MarshalSize()) - mask, _ := sign.NewMask([]kyber.Point{p1, p2, p3}, nil) + mask, _ := NewMask([]kyber.Point{p1, p2, p3}, nil) mask.SetBit(0, true) mask.SetBit(1, true) mask.SetBit(2, true) @@ -57,7 +56,7 @@ func TestBDN_AggregateSignatures(t *testing.T) { sig2, err := Sign(suite, private2, msg) require.NoError(t, err) - mask, _ := sign.NewMask([]kyber.Point{public1, public2}, nil) + mask, _ := NewMask([]kyber.Point{public1, public2}, nil) mask.SetBit(0, true) mask.SetBit(1, true) @@ -95,7 +94,7 @@ func TestBDN_SubsetSignature(t *testing.T) { sig2, err := Sign(suite, private2, msg) require.NoError(t, err) - mask, _ := sign.NewMask([]kyber.Point{public1, public3, public2}, nil) + mask, _ := NewMask([]kyber.Point{public1, public3, public2}, nil) mask.SetBit(0, true) mask.SetBit(2, true) @@ -134,7 +133,7 @@ func TestBDN_RogueAttack(t *testing.T) { require.NoError(t, scheme.Verify(agg, msg, sig)) // New scheme that should detect - mask, _ := sign.NewMask(pubs, nil) + mask, _ := NewMask(pubs, nil) mask.SetBit(0, true) mask.SetBit(1, true) agg, err = AggregatePublicKeys(suite, mask) @@ -152,7 +151,7 @@ func Benchmark_BDN_AggregateSigs(b *testing.B) { sig2, err := Sign(suite, private2, msg) require.Nil(b, err) - mask, _ := sign.NewMask([]kyber.Point{public1, public2}, nil) + mask, _ := NewMask([]kyber.Point{public1, public2}, nil) mask.SetBit(0, true) mask.SetBit(1, false) @@ -173,9 +172,7 @@ func Benchmark_BDN_BLS12381_AggregateVerify(b *testing.B) { privKeys[i], pubKeys[i] = schemeOnG2.NewKeyPair(rng) } - baseMask, err := sign.NewMask(pubKeys, nil) - require.NoError(b, err) - mask, err := NewCachedMask(baseMask) + mask, err := NewMask(pubKeys, nil) require.NoError(b, err) for i := range pubKeys { require.NoError(b, mask.SetBit(i, true)) @@ -246,7 +243,7 @@ func TestBDNFixtures(t *testing.T) { require.Nil(t, err) require.Equal(t, sig3Exp, sig3) - mask, _ := sign.NewMask([]kyber.Point{public1, public2, public3}, nil) + mask, _ := NewMask([]kyber.Point{public1, public2, public3}, nil) mask.SetBit(0, true) mask.SetBit(1, false) mask.SetBit(2, true) diff --git a/sign/bdn/mask.go b/sign/bdn/mask.go index 3cb90bc60..e83a3002c 100644 --- a/sign/bdn/mask.go +++ b/sign/bdn/mask.go @@ -1,113 +1,215 @@ package bdn import ( + "errors" "fmt" + "slices" "go.dedis.ch/kyber/v4" - "go.dedis.ch/kyber/v4/sign" ) -//nolint:interfacebloat -type Mask interface { - GetBit(i int) (bool, error) - SetBit(i int, enable bool) error +// Mask is a bitmask of the participation to a collective signature. +type Mask struct { + mask []byte + publics []kyber.Point - IndexOfNthEnabled(nth int) int - NthEnabledAtIndex(idx int) int + // Coefficients used when aggregating signatures. + publicCoefs []kyber.Scalar + // Terms used to aggregate public keys + publicTerms []kyber.Point +} - Publics() []kyber.Point - Participants() []kyber.Point +// NewMask creates a new mask from a list of public keys. If a key is provided, it +// will set the bit of the key to 1 or return an error if it is not found. +// +// The returned Mask will will contain pre-computed terms and coefficients for all provided public +// keys, so it should be re-used for optimal performance (e.g., by creating a "base" mask and +// cloning it whenever aggregating signstures and/or public keys). +func NewMask(publics []kyber.Point, myKey kyber.Point) (*Mask, error) { + m := &Mask{ + publics: publics, + } + m.mask = make([]byte, m.Len()) + + if myKey != nil { + for i, key := range publics { + if key.Equal(myKey) { + err := m.SetBit(i, true) + return m, err + } + } - CountEnabled() int - CountTotal() int + return nil, errors.New("key not found") + } + + var err error + m.publicCoefs, err = hashPointToR(publics) + if err != nil { + return nil, fmt.Errorf("failed to hash public keys: %w", err) + } + + m.publicTerms = make([]kyber.Point, len(publics)) + for i, pub := range publics { + pubC := pub.Clone().Mul(m.publicCoefs[i], pub) + m.publicTerms[i] = pubC.Add(pubC, pub) + } - Len() int - Mask() []byte - SetMask(mask []byte) error - Merge(mask []byte) error + return m, nil } -var _ Mask = (*sign.Mask)(nil) +// Mask returns the bitmask as a byte array. +func (m *Mask) Mask() []byte { + clone := make([]byte, len(m.mask)) + copy(clone, m.mask) + return clone +} -// 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 +// Len returns the length of the byte array necessary to store the bitmask. +func (m *Mask) Len() int { + return (len(m.publics) + 7) / 8 +} -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 +// SetMask replaces the current mask by the new one if the length matches. +func (m *Mask) SetMask(mask []byte) error { + if m.Len() != len(mask) { + return fmt.Errorf("mismatching mask lengths") + } + + m.mask = mask + return nil } -// Convert the passed mask (likely a *sign.Mask) into a BDN-specific mask with pre-computed terms. -// -// This cached mask will: -// -// 1. Pre-compute coefficients for signature aggregation. Once the CachedMask has been instantiated, -// distinct sets of signatures can be aggregated without any BLAKE2S hashing. -// 2. Pre-computes the terms for public key aggregation. Once the CachedMask has been instantiated, -// distinct sets of public keys can be aggregated by simply summing the cached terms, ~2 orders -// of magnitude faster than aggregating from scratch. -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 +// GetBit returns true if the given bit is set. +func (m *Mask) GetBit(i int) (bool, error) { + if i >= len(m.publics) || i < 0 { + return false, errors.New("index out of range") } - publics := mask.Publics() - coefs, err := hashPointToR(publics) - if err != nil { - return nil, fmt.Errorf("failed to hash public keys: %w", err) + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + return m.mask[byteIndex]&mask != 0, nil +} + +// SetBit turns on or off the bit at the given index. +func (m *Mask) SetBit(i int, enable bool) error { + if i >= len(m.publics) || i < 0 { + return errors.New("index out of range") } - cm := &CachedMask{ - maskI: mask, - coefs: coefs, - publics: publics, + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + if enable { + m.mask[byteIndex] |= mask + } else { + m.mask[byteIndex] &^= mask } + return nil +} - if precomputePubC { - pubKeyC := make([]kyber.Point, len(publics)) - for i := range publics { - pubKeyC[i] = cm.getOrComputePubC(i) +// forEachBitEnabled is a helper to iterate over the bits set to 1 in the mask +// and to return the result of the callback only if it is positive. +func (m *Mask) forEachBitEnabled(f func(i, j, n int) int) int { + n := 0 + for i, b := range m.mask { + for j := uint(0); j < 8; j++ { + mm := byte(1) << (j & 7) + + if b&mm != 0 { + if res := f(i, int(j), n); res >= 0 { + return res + } + + n++ + } } - cm.pubKeyC = pubKeyC } - return cm, err + return -1 } -// Clone copies the BDN mask while keeping the precomputed coefficients, etc. -func (cm *CachedMask) Clone() *CachedMask { - newMask, err := sign.NewMask(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)) +// IndexOfNthEnabled returns the index of the nth enabled bit or -1 if out of bounds. +func (m *Mask) IndexOfNthEnabled(nth int) int { + return m.forEachBitEnabled(func(i, j, n int) int { + if n == nth { + return i*8 + int(j) + } + + return -1 + }) +} + +// NthEnabledAtIndex returns the sum of bits set to 1 until the given index. In other +// words, it returns how many bits are enabled before the given index. +func (m *Mask) NthEnabledAtIndex(idx int) int { + return m.forEachBitEnabled(func(i, j, n int) int { + if i*8+int(j) == idx { + return n + } + + return -1 + }) +} + +// Publics returns a copy of the list of public keys. +func (m *Mask) Publics() []kyber.Point { + pubs := make([]kyber.Point, len(m.publics)) + copy(pubs, m.publics) + return pubs +} + +// Participants returns the list of public keys participating. +func (m *Mask) Participants() []kyber.Point { + pp := []kyber.Point{} + for i, p := range m.publics { + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + if (m.mask[byteIndex] & mask) != 0 { + pp = append(pp, p) + } + } + + return pp +} + +// CountEnabled returns the number of bit set to 1 +func (m *Mask) CountEnabled() int { + count := 0 + for i := range m.publics { + byteIndex := i / 8 + mask := byte(1) << uint(i&7) + if (m.mask[byteIndex] & mask) != 0 { + count++ + } } - if err := newMask.SetMask(cm.Mask()); err != nil { - // Not possible given that we're using the same sized mask. - panic(fmt.Sprintf("failed to create mask: %s", err)) + return count +} + +// CountTotal returns the number of potential participants +func (m *Mask) CountTotal() int { + return len(m.publics) +} + +// Merge merges the given mask to the current one only if +// the length matches +func (m *Mask) Merge(mask []byte) error { + if len(m.mask) != len(mask) { + return errors.New("mismatching mask length") } - return &CachedMask{ - maskI: newMask, - coefs: cm.coefs, - pubKeyC: cm.pubKeyC, - publics: cm.publics, + + for i := range m.mask { + m.mask[i] |= mask[i] } + + return nil } -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) +// Clone copies the mask while keeping the precomputed coefficients, etc. This method is thread safe +// and does not modify the original mask. Modifications to the new Mask will not affect the original. +func (m *Mask) Clone() *Mask { + return &Mask{ + mask: slices.Clone(m.mask), + publics: m.publics, + publicCoefs: m.publicCoefs, + publicTerms: m.publicTerms, } - return cm.pubKeyC[i] } diff --git a/sign/mask_test.go b/sign/bdn/mask_test.go similarity index 97% rename from sign/mask_test.go rename to sign/bdn/mask_test.go index 5eb911755..f87cf2706 100644 --- a/sign/mask_test.go +++ b/sign/bdn/mask_test.go @@ -1,4 +1,4 @@ -package sign +package bdn import ( "crypto/rand" @@ -6,13 +6,11 @@ import ( "github.com/stretchr/testify/require" "go.dedis.ch/kyber/v4" - "go.dedis.ch/kyber/v4/pairing/bn256" "go.dedis.ch/kyber/v4/util/key" ) const n = 17 -var suite = bn256.NewSuiteBn256() var publics []kyber.Point func init() { diff --git a/sign/mask.go b/sign/mask.go deleted file mode 100644 index d02692c32..000000000 --- a/sign/mask.go +++ /dev/null @@ -1,183 +0,0 @@ -// Package sign contains useful tools for the different signing algorithms. -package sign - -import ( - "errors" - "fmt" - - "go.dedis.ch/kyber/v4" -) - -// Mask is a bitmask of the participation to a collective signature. -type Mask struct { - mask []byte - publics []kyber.Point -} - -// NewMask creates a new mask from a list of public keys. If a key is provided, it -// will set the bit of the key to 1 or return an error if it is not found. -func NewMask(publics []kyber.Point, myKey kyber.Point) (*Mask, error) { - m := &Mask{ - publics: publics, - } - m.mask = make([]byte, m.Len()) - - if myKey != nil { - for i, key := range publics { - if key.Equal(myKey) { - err := m.SetBit(i, true) - return m, err - } - } - - return nil, errors.New("key not found") - } - - return m, nil -} - -// Mask returns the bitmask as a byte array. -func (m *Mask) Mask() []byte { - clone := make([]byte, len(m.mask)) - copy(clone, m.mask) - return clone -} - -// Len returns the length of the byte array necessary to store the bitmask. -func (m *Mask) Len() int { - return (len(m.publics) + 7) / 8 -} - -// SetMask replaces the current mask by the new one if the length matches. -func (m *Mask) SetMask(mask []byte) error { - if m.Len() != len(mask) { - return fmt.Errorf("mismatching mask lengths") - } - - m.mask = mask - return nil -} - -// GetBit returns true if the given bit is set. -func (m *Mask) GetBit(i int) (bool, error) { - if i >= len(m.publics) || i < 0 { - return false, errors.New("index out of range") - } - - byteIndex := i / 8 - mask := byte(1) << uint(i&7) - return m.mask[byteIndex]&mask != 0, nil -} - -// SetBit turns on or off the bit at the given index. -func (m *Mask) SetBit(i int, enable bool) error { - if i >= len(m.publics) || i < 0 { - return errors.New("index out of range") - } - - byteIndex := i / 8 - mask := byte(1) << uint(i&7) - if enable { - m.mask[byteIndex] |= mask - } else { - m.mask[byteIndex] &^= mask - } - return nil -} - -// forEachBitEnabled is a helper to iterate over the bits set to 1 in the mask -// and to return the result of the callback only if it is positive. -func (m *Mask) forEachBitEnabled(f func(i, j, n int) int) int { - n := 0 - for i, b := range m.mask { - for j := uint(0); j < 8; j++ { - mm := byte(1) << (j & 7) - - if b&mm != 0 { - if res := f(i, int(j), n); res >= 0 { - return res - } - - n++ - } - } - } - - return -1 -} - -// IndexOfNthEnabled returns the index of the nth enabled bit or -1 if out of bounds. -func (m *Mask) IndexOfNthEnabled(nth int) int { - return m.forEachBitEnabled(func(i, j, n int) int { - if n == nth { - return i*8 + int(j) - } - - return -1 - }) -} - -// NthEnabledAtIndex returns the sum of bits set to 1 until the given index. In other -// words, it returns how many bits are enabled before the given index. -func (m *Mask) NthEnabledAtIndex(idx int) int { - return m.forEachBitEnabled(func(i, j, n int) int { - if i*8+int(j) == idx { - return n - } - - return -1 - }) -} - -// Publics returns a copy of the list of public keys. -func (m *Mask) Publics() []kyber.Point { - pubs := make([]kyber.Point, len(m.publics)) - copy(pubs, m.publics) - return pubs -} - -// Participants returns the list of public keys participating. -func (m *Mask) Participants() []kyber.Point { - pp := []kyber.Point{} - for i, p := range m.publics { - byteIndex := i / 8 - mask := byte(1) << uint(i&7) - if (m.mask[byteIndex] & mask) != 0 { - pp = append(pp, p) - } - } - - return pp -} - -// CountEnabled returns the number of bit set to 1 -func (m *Mask) CountEnabled() int { - count := 0 - for i := range m.publics { - byteIndex := i / 8 - mask := byte(1) << uint(i&7) - if (m.mask[byteIndex] & mask) != 0 { - count++ - } - } - return count -} - -// CountTotal returns the number of potential participants -func (m *Mask) CountTotal() int { - return len(m.publics) -} - -// Merge merges the given mask to the current one only if -// the length matches -func (m *Mask) Merge(mask []byte) error { - if len(m.mask) != len(mask) { - return errors.New("mismatching mask length") - } - - for i := range m.mask { - m.mask[i] |= mask[i] - } - - return nil -} From 2c83bb2bfc33f6092f4d983b41bc6422202d06d5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 11 Sep 2024 17:48:39 +0000 Subject: [PATCH 7/8] fix docs Co-authored-by: AnomalRoil --- sign/bdn/mask.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sign/bdn/mask.go b/sign/bdn/mask.go index e83a3002c..88068b6ff 100644 --- a/sign/bdn/mask.go +++ b/sign/bdn/mask.go @@ -22,9 +22,9 @@ type Mask struct { // NewMask creates a new mask from a list of public keys. If a key is provided, it // will set the bit of the key to 1 or return an error if it is not found. // -// The returned Mask will will contain pre-computed terms and coefficients for all provided public +// The returned Mask will contain pre-computed terms and coefficients for all provided public // keys, so it should be re-used for optimal performance (e.g., by creating a "base" mask and -// cloning it whenever aggregating signstures and/or public keys). +// cloning it whenever aggregating signatures and/or public keys). func NewMask(publics []kyber.Point, myKey kyber.Point) (*Mask, error) { m := &Mask{ publics: publics, From 3be781734c492feebcff560d4d6d8743ecd78da5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 11 Sep 2024 10:53:21 -0700 Subject: [PATCH 8/8] Document mutability of Mask fields --- sign/bdn/mask.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sign/bdn/mask.go b/sign/bdn/mask.go index 88068b6ff..a6a6fd78c 100644 --- a/sign/bdn/mask.go +++ b/sign/bdn/mask.go @@ -10,9 +10,15 @@ import ( // Mask is a bitmask of the participation to a collective signature. type Mask struct { - mask []byte - publics []kyber.Point + // The bitmask indicating which public keys are enabled/disabled for aggregation. This is + // the only mutable field. + mask []byte + + // The following fields are immutable and should not be changed after the mask is created. + // They may be shared between multiple masks. + // Public keys for aggregation & signature verification. + publics []kyber.Point // Coefficients used when aggregating signatures. publicCoefs []kyber.Scalar // Terms used to aggregate public keys