github.com/onflow/flow-go@v0.33.17/module/signature/aggregation_test.go (about)

     1  package signature
     2  
     3  import (
     4  	"errors"
     5  	mrand "math/rand"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/onflow/flow-go/crypto"
    14  )
    15  
    16  func getPRG(t *testing.T) *mrand.Rand {
    17  	random := time.Now().UnixNano()
    18  	t.Logf("rng seed is %d", random)
    19  	rng := mrand.New(mrand.NewSource(random))
    20  	return rng
    21  }
    22  
    23  // Utility function that flips a point sign bit to negate the point
    24  // this is shortcut which works only for zcash BLS12-381 compressed serialization
    25  // that is currently supported by the flow crypto module
    26  // Applicable to both signatures and public keys
    27  func negatePoint(pointbytes []byte) {
    28  	pointbytes[0] ^= 0x20
    29  }
    30  
    31  func createAggregationData(t *testing.T, rand *mrand.Rand, signersNumber int) (
    32  	[]byte, string, []crypto.Signature, []crypto.PublicKey,
    33  ) {
    34  	// create message and tag
    35  	msgLen := 100
    36  	msg := make([]byte, msgLen)
    37  	_, err := rand.Read(msg)
    38  	require.NoError(t, err)
    39  	tag := "random_tag"
    40  	hasher := NewBLSHasher(tag)
    41  
    42  	// create keys and signatures
    43  	keys := make([]crypto.PublicKey, 0, signersNumber)
    44  	sigs := make([]crypto.Signature, 0, signersNumber)
    45  	seed := make([]byte, crypto.KeyGenSeedMinLen)
    46  	for i := 0; i < signersNumber; i++ {
    47  		_, err := rand.Read(seed)
    48  		require.NoError(t, err)
    49  		sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
    50  		require.NoError(t, err)
    51  		keys = append(keys, sk.PublicKey())
    52  		sig, err := sk.Sign(msg, hasher)
    53  		require.NoError(t, err)
    54  		sigs = append(sigs, sig)
    55  	}
    56  	return msg, tag, sigs, keys
    57  }
    58  
    59  func TestAggregatorSameMessage(t *testing.T) {
    60  	rand := getPRG(t)
    61  	signersNum := 20
    62  
    63  	// constructor edge cases
    64  	t.Run("constructor", func(t *testing.T) {
    65  		msg := []byte("random_msg")
    66  		tag := "random_tag"
    67  		// empty keys
    68  		_, err := NewSignatureAggregatorSameMessage(msg, tag, []crypto.PublicKey{})
    69  		assert.Error(t, err)
    70  		// wrong key types
    71  		seed := make([]byte, crypto.KeyGenSeedMinLen)
    72  		_, err = rand.Read(seed)
    73  		require.NoError(t, err)
    74  		sk, err := crypto.GeneratePrivateKey(crypto.ECDSAP256, seed)
    75  		require.NoError(t, err)
    76  		_, err = NewSignatureAggregatorSameMessage(msg, tag, []crypto.PublicKey{sk.PublicKey()})
    77  		assert.Error(t, err)
    78  		_, err = NewSignatureAggregatorSameMessage(msg, tag, []crypto.PublicKey{nil})
    79  		assert.Error(t, err)
    80  	})
    81  
    82  	// Happy paths
    83  	// all signatures are valid
    84  	t.Run("happy path", func(t *testing.T) {
    85  		msg, tag, sigs, pks := createAggregationData(t, rand, signersNum)
    86  		aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
    87  		require.NoError(t, err)
    88  
    89  		// only add half of the signatures
    90  		subSet := signersNum / 2
    91  		for i, sig := range sigs[subSet:] {
    92  			index := i + subSet
    93  			// test Verify
    94  			ok, err := aggregator.Verify(index, sig)
    95  			assert.NoError(t, err)
    96  			assert.True(t, ok)
    97  			// test HasSignature with existing sig
    98  			ok, err = aggregator.HasSignature(index)
    99  			assert.NoError(t, err)
   100  			assert.False(t, ok)
   101  			// test TrustedAdd
   102  			err = aggregator.TrustedAdd(index, sig)
   103  			assert.NoError(t, err)
   104  			// test HasSignature with non existing sig
   105  			ok, err = aggregator.HasSignature(index)
   106  			assert.NoError(t, err)
   107  			assert.True(t, ok)
   108  		}
   109  		signers, agg, err := aggregator.Aggregate()
   110  		assert.NoError(t, err)
   111  		ok, aggKey, err := aggregator.VerifyAggregate(signers, agg)
   112  		assert.NoError(t, err)
   113  		assert.True(t, ok)
   114  		// check aggregated public key
   115  		expectedKey, err := crypto.AggregateBLSPublicKeys(pks[subSet:])
   116  		assert.NoError(t, err)
   117  		assert.True(t, expectedKey.Equals(aggKey))
   118  		// check signers
   119  		sort.Ints(signers)
   120  		for i := 0; i < subSet; i++ {
   121  			index := i + subSet
   122  			assert.Equal(t, index, signers[i])
   123  		}
   124  		// cached aggregated signature
   125  		_, aggCached, err := aggregator.Aggregate()
   126  		assert.NoError(t, err)
   127  		// make sure signature is equal, even though this doesn't mean caching is working
   128  		assert.Equal(t, agg, aggCached)
   129  		// In the following, new signatures are added which makes sure cached signature
   130  		// was cleared.
   131  
   132  		// add remaining signatures, this time using VerifyAndAdd
   133  		for i, sig := range sigs[:subSet] {
   134  			ok, err = aggregator.VerifyAndAdd(i, sig)
   135  			assert.True(t, ok)
   136  			assert.NoError(t, err)
   137  		}
   138  		signers, agg, err = aggregator.Aggregate()
   139  		assert.NoError(t, err)
   140  		ok, aggKey, err = aggregator.VerifyAggregate(signers, agg)
   141  		assert.NoError(t, err)
   142  		assert.True(t, ok)
   143  		// check aggregated public key
   144  		expectedKey, err = crypto.AggregateBLSPublicKeys(pks[:])
   145  		assert.NoError(t, err)
   146  		assert.True(t, expectedKey.Equals(aggKey))
   147  		// check signers
   148  		sort.Ints(signers)
   149  		for i := 0; i < signersNum; i++ {
   150  			assert.Equal(t, i, signers[i])
   151  		}
   152  	})
   153  
   154  	// Unhappy paths
   155  	t.Run("invalid inputs", func(t *testing.T) {
   156  		msg, tag, sigs, pks := createAggregationData(t, rand, signersNum)
   157  		aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   158  		require.NoError(t, err)
   159  		// invalid indices for different methods
   160  		for _, index := range []int{-1, signersNum} {
   161  			// loop through invalid index inputs
   162  			ok, err := aggregator.Verify(index, sigs[0])
   163  			assert.False(t, ok)
   164  			assert.True(t, IsInvalidSignerIdxError(err))
   165  
   166  			ok, err = aggregator.VerifyAndAdd(index, sigs[0])
   167  			assert.False(t, ok)
   168  			assert.True(t, IsInvalidSignerIdxError(err))
   169  
   170  			err = aggregator.TrustedAdd(index, sigs[0])
   171  			assert.True(t, IsInvalidSignerIdxError(err))
   172  
   173  			ok, err = aggregator.HasSignature(index)
   174  			assert.False(t, ok)
   175  			assert.True(t, IsInvalidSignerIdxError(err))
   176  
   177  			ok, aggKey, err := aggregator.VerifyAggregate([]int{index}, sigs[0])
   178  			assert.False(t, ok)
   179  			assert.Nil(t, aggKey)
   180  			assert.True(t, IsInvalidSignerIdxError(err))
   181  		}
   182  		// empty list on VerifyAggregate
   183  		ok, aggKey, err := aggregator.VerifyAggregate([]int{}, sigs[0])
   184  		assert.False(t, ok)
   185  		assert.Nil(t, aggKey)
   186  		assert.True(t, IsInsufficientSignaturesError(err))
   187  	})
   188  
   189  	t.Run("duplicate signers", func(t *testing.T) {
   190  		msg, tag, sigs, pks := createAggregationData(t, rand, signersNum)
   191  		aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   192  		require.NoError(t, err)
   193  
   194  		// first add non-duplicate signatures
   195  		for i, sig := range sigs {
   196  			err := aggregator.TrustedAdd(i, sig)
   197  			require.NoError(t, err)
   198  		}
   199  		// add duplicate signers which expects errors
   200  		for i := range sigs {
   201  			// `TrustedAdd`
   202  			err := aggregator.TrustedAdd(i, sigs[i]) // same signature for same index
   203  			assert.True(t, IsDuplicatedSignerIdxError(err))
   204  			err = aggregator.TrustedAdd(i, sigs[(i+1)%signersNum]) // different signature for same index
   205  			assert.True(t, IsDuplicatedSignerIdxError(err))
   206  			// `VerifyAndAdd``
   207  			ok, err := aggregator.VerifyAndAdd(i, sigs[i]) // same signature for same index
   208  			assert.False(t, ok)
   209  			assert.True(t, IsDuplicatedSignerIdxError(err))
   210  			ok, err = aggregator.VerifyAndAdd(i, sigs[(i+1)%signersNum]) // different signature for same index
   211  			assert.False(t, ok)
   212  			assert.True(t, IsDuplicatedSignerIdxError(err))
   213  		}
   214  	})
   215  
   216  	// The following tests are related to the `Aggregate()` method.
   217  	// Generally, `Aggregate()` can fail in four cases:
   218  	//  1. No signature has been added.
   219  	//  2. A signature added via `TrustedAdd` has an invalid structure (fails to deserialize)
   220  	//      2.a. aggregated public key is not identity
   221  	//      2.b. aggregated public key is identity
   222  	//  3. Signatures serialization is valid but some signatures are invalid w.r.t their respective public keys.
   223  	//      3.a. aggregated public key is not identity
   224  	//      3.b. aggregated public key is identity
   225  	//  4. All signatures are valid but aggregated key is identity
   226  
   227  	// 1: No signature has been added.
   228  	t.Run("aggregate with no signatures", func(t *testing.T) {
   229  		msg, tag, _, pks := createAggregationData(t, rand, 1)
   230  		aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   231  		require.NoError(t, err)
   232  		// Aggregation should error with sentinel InsufficientSignaturesError
   233  		signers, agg, err := aggregator.Aggregate()
   234  		assert.Error(t, err)
   235  		assert.True(t, IsInsufficientSignaturesError(err))
   236  		assert.Nil(t, agg)
   237  		assert.Nil(t, signers)
   238  
   239  	})
   240  
   241  	//  2. A signature added via `TrustedAdd` has an invalid structure (fails to deserialize)
   242  	//      2.a. aggregated public key is not identity
   243  	//      2.b. aggregated public key is identity
   244  	t.Run("invalid signature serialization", func(t *testing.T) {
   245  		msg, tag, sigs, pks := createAggregationData(t, rand, 2)
   246  		invalidStructureSig := (crypto.Signature)([]byte{0, 0})
   247  
   248  		t.Run("with non-identity aggregated public key", func(t *testing.T) {
   249  			aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   250  			require.NoError(t, err)
   251  
   252  			// add invalid signature for signer with index 0
   253  			// sanity check : methods that check validity should reject it
   254  			ok, err := aggregator.Verify(0, invalidStructureSig) // stand-alone verification
   255  			require.NoError(t, err)
   256  			assert.False(t, ok)
   257  			ok, err = aggregator.VerifyAndAdd(0, invalidStructureSig) // verification plus addition
   258  			require.NoError(t, err)
   259  			assert.False(t, ok)
   260  			// check signature is still not added
   261  			ok, err = aggregator.HasSignature(0)
   262  			require.NoError(t, err)
   263  			assert.False(t, ok)
   264  
   265  			// TrustedAdd should accept the invalid signature
   266  			err = aggregator.TrustedAdd(0, invalidStructureSig)
   267  			require.NoError(t, err)
   268  
   269  			// Aggregation should error with sentinel InvalidSignatureIncludedError
   270  			// aggregated public key is not identity (equal to pk[0])
   271  			signers, agg, err := aggregator.Aggregate()
   272  			assert.Error(t, err)
   273  			assert.True(t, IsInvalidSignatureIncludedError(err))
   274  			assert.Nil(t, agg)
   275  			assert.Nil(t, signers)
   276  		})
   277  
   278  		t.Run("with identity aggregated public key", func(t *testing.T) {
   279  			// assign  pk1 to -pk0 so that the aggregated public key is identity
   280  			pkBytes := pks[0].Encode()
   281  			negatePoint(pkBytes)
   282  			var err error
   283  			pks[1], err = crypto.DecodePublicKey(crypto.BLSBLS12381, pkBytes)
   284  			require.NoError(t, err)
   285  
   286  			aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   287  			require.NoError(t, err)
   288  
   289  			// add the invalid signature on index 0
   290  			err = aggregator.TrustedAdd(0, invalidStructureSig)
   291  			require.NoError(t, err)
   292  
   293  			// add a second signature for index 1
   294  			err = aggregator.TrustedAdd(1, sigs[1])
   295  			require.NoError(t, err)
   296  
   297  			// Aggregation should error with sentinel InvalidSignatureIncludedError
   298  			// aggregated public key is identity
   299  			signers, agg, err := aggregator.Aggregate()
   300  			assert.Error(t, err)
   301  			assert.True(t, IsInvalidSignatureIncludedError(err))
   302  			assert.Nil(t, agg)
   303  			assert.Nil(t, signers)
   304  		})
   305  	})
   306  
   307  	//  3. Signatures serialization is valid but some signatures are invalid w.r.t their respective public keys.
   308  	//      3.a. aggregated public key is not identity
   309  	//      3.b. aggregated public key is identity
   310  	t.Run("correct serialization and invalid signature", func(t *testing.T) {
   311  		msg, tag, sigs, pks := createAggregationData(t, rand, 2)
   312  
   313  		t.Run("with non-identity aggregated public key", func(t *testing.T) {
   314  			aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   315  			require.NoError(t, err)
   316  
   317  			// first, add a valid signature
   318  			ok, err := aggregator.VerifyAndAdd(0, sigs[0])
   319  			require.NoError(t, err)
   320  			assert.True(t, ok)
   321  
   322  			// add invalid signature for signer with index 1
   323  			// sanity check: methods that check validity should reject it
   324  			ok, err = aggregator.Verify(1, sigs[0]) // stand-alone verification
   325  			require.NoError(t, err)
   326  			assert.False(t, ok)
   327  			ok, err = aggregator.VerifyAndAdd(1, sigs[0]) // verification plus addition
   328  			require.NoError(t, err)
   329  			assert.False(t, ok)
   330  			// check signature is still not added
   331  			ok, err = aggregator.HasSignature(1)
   332  			require.NoError(t, err)
   333  			assert.False(t, ok)
   334  
   335  			// TrustedAdd should accept invalid signature
   336  			err = aggregator.TrustedAdd(1, sigs[0])
   337  			require.NoError(t, err)
   338  
   339  			// Aggregation should error with sentinel InvalidSignatureIncludedError
   340  			// aggregated public key is not identity (equal to pk[0] + pk[1])
   341  			signers, agg, err := aggregator.Aggregate()
   342  			assert.Error(t, err)
   343  			assert.True(t, IsInvalidSignatureIncludedError(err))
   344  			assert.Nil(t, agg)
   345  			assert.Nil(t, signers)
   346  		})
   347  
   348  		t.Run("with identity aggregated public key", func(t *testing.T) {
   349  			// assign  pk1 to -pk0 so that the aggregated public key is identity
   350  			// this is a shortcut since PoPs are not checked in this test
   351  			pkBytes := pks[0].Encode()
   352  			negatePoint(pkBytes)
   353  			var err error
   354  			pks[1], err = crypto.DecodePublicKey(crypto.BLSBLS12381, pkBytes)
   355  			require.NoError(t, err)
   356  
   357  			aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   358  			require.NoError(t, err)
   359  
   360  			// add a valid signature
   361  			err = aggregator.TrustedAdd(0, sigs[0])
   362  			require.NoError(t, err)
   363  
   364  			// add an invalid signature via `TrustedAdd`
   365  			err = aggregator.TrustedAdd(1, sigs[0])
   366  			require.NoError(t, err)
   367  
   368  			// Aggregation should error with sentinel ErrIdentityPublicKey
   369  			// aggregated public key is identity
   370  			signers, agg, err := aggregator.Aggregate()
   371  			assert.Error(t, err)
   372  			assert.True(t, errors.Is(err, ErrIdentityPublicKey))
   373  			assert.Nil(t, agg)
   374  			assert.Nil(t, signers)
   375  		})
   376  	})
   377  
   378  	// 4. All signatures are valid but aggregated key is identity
   379  	t.Run("all valid signatures and identity aggregated key", func(t *testing.T) {
   380  		msg, tag, sigs, pks := createAggregationData(t, rand, 2)
   381  
   382  		// public key at index 1 is opposite of public key at index 0 (pks[1] = -pks[0])
   383  		// so that aggregation of pks[0] and pks[1] is identity
   384  		// this is a shortcut given PoPs are not checked in this test
   385  		pkBytes := pks[0].Encode()
   386  		negatePoint(pkBytes)
   387  		var err error
   388  		pks[1], err = crypto.DecodePublicKey(crypto.BLSBLS12381, pkBytes)
   389  		require.NoError(t, err)
   390  
   391  		// given how pks[1] was constructed,
   392  		// sig[1]= -sigs[0] is a valid signature for signer with index 1
   393  		copy(sigs[1], sigs[0])
   394  		negatePoint(sigs[1])
   395  
   396  		aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, pks)
   397  		require.NoError(t, err)
   398  
   399  		// add a valid signature for index 0
   400  		ok, err := aggregator.VerifyAndAdd(0, sigs[0])
   401  		require.NoError(t, err)
   402  		assert.True(t, ok)
   403  
   404  		// add a valid signature for index 1
   405  		ok, err = aggregator.VerifyAndAdd(1, sigs[1])
   406  		require.NoError(t, err)
   407  		assert.True(t, ok)
   408  
   409  		// Aggregation should error with sentinel ErrIdentityPublicKey
   410  		signers, agg, err := aggregator.Aggregate()
   411  		assert.Error(t, err)
   412  		assert.True(t, errors.Is(err, ErrIdentityPublicKey))
   413  		assert.Nil(t, agg)
   414  		assert.Nil(t, signers)
   415  	})
   416  }
   417  
   418  func TestKeyAggregator(t *testing.T) {
   419  	rand := getPRG(t)
   420  
   421  	signersNum := 20
   422  	// create keys
   423  	indices := make([]int, 0, signersNum)
   424  	keys := make([]crypto.PublicKey, 0, signersNum)
   425  	seed := make([]byte, crypto.KeyGenSeedMinLen)
   426  	for i := 0; i < signersNum; i++ {
   427  		indices = append(indices, i)
   428  		_, err := rand.Read(seed)
   429  		require.NoError(t, err)
   430  		sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
   431  		require.NoError(t, err)
   432  		keys = append(keys, sk.PublicKey())
   433  	}
   434  	aggregator, err := NewPublicKeyAggregator(keys)
   435  	require.NoError(t, err)
   436  
   437  	// constructor edge cases
   438  	t.Run("constructor", func(t *testing.T) {
   439  		// wrong key types
   440  		seed := make([]byte, crypto.KeyGenSeedMinLen)
   441  		_, err = rand.Read(seed)
   442  		require.NoError(t, err)
   443  		sk, err := crypto.GeneratePrivateKey(crypto.ECDSAP256, seed)
   444  		require.NoError(t, err)
   445  		// wrong key type
   446  		_, err = NewPublicKeyAggregator([]crypto.PublicKey{sk.PublicKey()})
   447  		assert.Error(t, err)
   448  		_, err = NewPublicKeyAggregator([]crypto.PublicKey{nil})
   449  		assert.Error(t, err)
   450  		// empty keys
   451  		_, err = NewPublicKeyAggregator([]crypto.PublicKey{})
   452  		assert.Error(t, err)
   453  	})
   454  
   455  	// aggregation edge cases
   456  	t.Run("empty signers", func(t *testing.T) {
   457  		// empty input
   458  		_, err = aggregator.KeyAggregate(indices[:0])
   459  		assert.Error(t, err)
   460  		// invalid signer
   461  		_, err = aggregator.KeyAggregate([]int{-1})
   462  		assert.True(t, IsInvalidSignerIdxError(err))
   463  
   464  		_, err = aggregator.KeyAggregate([]int{signersNum})
   465  		assert.True(t, IsInvalidSignerIdxError(err))
   466  
   467  		aggregateTwice := func(l1, h1, l2, h2 int) {
   468  			var key, expectedKey crypto.PublicKey
   469  			key, err = aggregator.KeyAggregate(indices[l1:h1])
   470  			require.NoError(t, err)
   471  			expectedKey, err = crypto.AggregateBLSPublicKeys(keys[l1:h1])
   472  			require.NoError(t, err)
   473  			assert.True(t, key.Equals(expectedKey))
   474  
   475  			key, err = aggregator.KeyAggregate(indices[l2:h2])
   476  			require.NoError(t, err)
   477  			expectedKey, err = crypto.AggregateBLSPublicKeys(keys[l2:h2])
   478  			require.NoError(t, err)
   479  			assert.True(t, key.Equals(expectedKey))
   480  		}
   481  
   482  		// No overlaps
   483  		aggregateTwice(1, 5, 7, 13)
   484  
   485  		// big overlap (greedy algorithm)
   486  		aggregateTwice(1, 9, 2, 10)
   487  
   488  		// small overlap (aggregate from scratch)
   489  		aggregateTwice(1, 9, 8, 11)
   490  
   491  		// same signers
   492  		aggregateTwice(1, 9, 1, 9)
   493  	})
   494  
   495  	t.Run("greedy algorithm with randomized intervals", func(t *testing.T) {
   496  		// iterate over different random cases to make sure
   497  		// the delta algorithm works
   498  		rounds := 30
   499  		for i := 0; i < rounds; i++ {
   500  			go func() { // test module concurrency
   501  				low := rand.Intn(signersNum - 1)
   502  				high := low + 1 + rand.Intn(signersNum-1-low)
   503  				var key, expectedKey crypto.PublicKey
   504  				var err error
   505  				key, err = aggregator.KeyAggregate(indices[low:high])
   506  
   507  				require.NoError(t, err)
   508  				if low == high {
   509  					expectedKey = crypto.IdentityBLSPublicKey()
   510  				} else {
   511  					expectedKey, err = crypto.AggregateBLSPublicKeys(keys[low:high])
   512  					require.NoError(t, err)
   513  				}
   514  				assert.True(t, key.Equals(expectedKey))
   515  			}()
   516  		}
   517  	})
   518  }