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