github.com/koko1123/flow-go-1@v0.29.6/module/signature/aggregation_test.go (about)

     1  //go:build relic
     2  // +build relic
     3  
     4  package signature
     5  
     6  import (
     7  	"crypto/rand"
     8  	mrand "math/rand"
     9  	"sort"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/onflow/flow-go/crypto"
    17  )
    18  
    19  func createAggregationData(t *testing.T, signersNumber int) (*SignatureAggregatorSameMessage, []crypto.Signature) {
    20  	// create message and tag
    21  	msgLen := 100
    22  	msg := make([]byte, msgLen)
    23  	tag := "random_tag"
    24  	hasher := NewBLSHasher(tag)
    25  
    26  	// create keys and signatures
    27  	keys := make([]crypto.PublicKey, 0, signersNumber)
    28  	sigs := make([]crypto.Signature, 0, signersNumber)
    29  	seed := make([]byte, crypto.KeyGenSeedMinLenBLSBLS12381)
    30  	for i := 0; i < signersNumber; i++ {
    31  		_, err := rand.Read(seed)
    32  		require.NoError(t, err)
    33  		sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
    34  		require.NoError(t, err)
    35  		keys = append(keys, sk.PublicKey())
    36  		sig, err := sk.Sign(msg, hasher)
    37  		require.NoError(t, err)
    38  		sigs = append(sigs, sig)
    39  	}
    40  	aggregator, err := NewSignatureAggregatorSameMessage(msg, tag, keys)
    41  	require.NoError(t, err)
    42  	return aggregator, sigs
    43  }
    44  
    45  func TestAggregatorSameMessage(t *testing.T) {
    46  
    47  	signersNum := 20
    48  
    49  	// constructor edge cases
    50  	t.Run("constructor", func(t *testing.T) {
    51  		msg := []byte("random_msg")
    52  		tag := "random_tag"
    53  		// empty keys
    54  		_, err := NewSignatureAggregatorSameMessage(msg, tag, []crypto.PublicKey{})
    55  		assert.Error(t, err)
    56  		// wrong key types
    57  		seed := make([]byte, crypto.KeyGenSeedMinLenECDSAP256)
    58  		_, err = rand.Read(seed)
    59  		require.NoError(t, err)
    60  		sk, err := crypto.GeneratePrivateKey(crypto.ECDSAP256, seed)
    61  		require.NoError(t, err)
    62  		_, err = NewSignatureAggregatorSameMessage(msg, tag, []crypto.PublicKey{sk.PublicKey()})
    63  		assert.Error(t, err)
    64  		_, err = NewSignatureAggregatorSameMessage(msg, tag, []crypto.PublicKey{nil})
    65  		assert.Error(t, err)
    66  	})
    67  
    68  	// Happy paths
    69  	t.Run("happy path", func(t *testing.T) {
    70  		aggregator, sigs := createAggregationData(t, signersNum)
    71  		// only add half of the signatures
    72  		subSet := signersNum / 2
    73  		for i, sig := range sigs[subSet:] {
    74  			index := i + subSet
    75  			// test Verify
    76  			ok, err := aggregator.Verify(index, sig)
    77  			assert.NoError(t, err)
    78  			assert.True(t, ok)
    79  			// test HasSignature with existing sig
    80  			ok, err = aggregator.HasSignature(index)
    81  			assert.NoError(t, err)
    82  			assert.False(t, ok)
    83  			// test TrustedAdd
    84  			err = aggregator.TrustedAdd(index, sig)
    85  			assert.NoError(t, err)
    86  			// test HasSignature with non existing sig
    87  			ok, err = aggregator.HasSignature(index)
    88  			assert.NoError(t, err)
    89  			assert.True(t, ok)
    90  		}
    91  		signers, agg, err := aggregator.Aggregate()
    92  		assert.NoError(t, err)
    93  		ok, err := aggregator.VerifyAggregate(signers, agg)
    94  		assert.NoError(t, err)
    95  		assert.True(t, ok)
    96  		// check signers
    97  		sort.Ints(signers)
    98  		for i := 0; i < subSet; i++ {
    99  			index := i + subSet
   100  			assert.Equal(t, index, signers[i])
   101  		}
   102  		// cached aggregated signature
   103  		_, aggCached, err := aggregator.Aggregate()
   104  		assert.NoError(t, err)
   105  		// make sure signature is equal, even though this doesn't mean caching is working
   106  		assert.Equal(t, agg, aggCached)
   107  		// In the following, new signatures are added which makes sure cached signature
   108  		// was cleared.
   109  
   110  		// add remaining signatures, this time using VerifyAndAdd
   111  		for i, sig := range sigs[:subSet] {
   112  			ok, err = aggregator.VerifyAndAdd(i, sig)
   113  			assert.True(t, ok)
   114  			assert.NoError(t, err)
   115  		}
   116  		signers, agg, err = aggregator.Aggregate()
   117  		assert.NoError(t, err)
   118  		ok, err = aggregator.VerifyAggregate(signers, agg)
   119  		assert.NoError(t, err)
   120  		assert.True(t, ok)
   121  		// check signers
   122  		sort.Ints(signers)
   123  		for i := 0; i < signersNum; i++ {
   124  			assert.Equal(t, i, signers[i])
   125  		}
   126  	})
   127  
   128  	// Unhappy paths
   129  	t.Run("invalid inputs", func(t *testing.T) {
   130  		aggregator, sigs := createAggregationData(t, signersNum)
   131  		// loop through invalid inputs
   132  		for _, index := range []int{-1, signersNum} {
   133  			ok, err := aggregator.Verify(index, sigs[0])
   134  			assert.False(t, ok)
   135  			assert.True(t, IsInvalidSignerIdxError(err))
   136  
   137  			ok, err = aggregator.VerifyAndAdd(index, sigs[0])
   138  			assert.False(t, ok)
   139  			assert.True(t, IsInvalidSignerIdxError(err))
   140  
   141  			err = aggregator.TrustedAdd(index, sigs[0])
   142  			assert.True(t, IsInvalidSignerIdxError(err))
   143  
   144  			ok, err = aggregator.HasSignature(index)
   145  			assert.False(t, ok)
   146  			assert.True(t, IsInvalidSignerIdxError(err))
   147  
   148  			ok, err = aggregator.VerifyAggregate([]int{index}, sigs[0])
   149  			assert.False(t, ok)
   150  			assert.True(t, IsInvalidSignerIdxError(err))
   151  		}
   152  		// empty list
   153  		ok, err := aggregator.VerifyAggregate([]int{}, sigs[0])
   154  		assert.False(t, ok)
   155  		assert.True(t, IsInsufficientSignaturesError(err))
   156  	})
   157  
   158  	t.Run("duplicate signature", func(t *testing.T) {
   159  		aggregator, sigs := createAggregationData(t, signersNum)
   160  		for i, sig := range sigs {
   161  			err := aggregator.TrustedAdd(i, sig)
   162  			require.NoError(t, err)
   163  		}
   164  		// TrustedAdd
   165  		for i := range sigs {
   166  			err := aggregator.TrustedAdd(i, sigs[i]) // same signature for same index
   167  			assert.True(t, IsDuplicatedSignerIdxError(err))
   168  			err = aggregator.TrustedAdd(i, sigs[(i+1)%signersNum]) // different signature for same index
   169  			assert.True(t, IsDuplicatedSignerIdxError(err))
   170  			ok, err := aggregator.VerifyAndAdd(i, sigs[i]) // same signature for same index
   171  			assert.False(t, ok)
   172  			assert.True(t, IsDuplicatedSignerIdxError(err))
   173  			ok, err = aggregator.VerifyAndAdd(i, sigs[(i+1)%signersNum]) // different signature for same index
   174  			assert.False(t, ok)
   175  			assert.True(t, IsDuplicatedSignerIdxError(err))
   176  		}
   177  	})
   178  
   179  	// Generally, `Aggregate()` can fail in two places, when invalid signatures were added via `TrustedAdd`:
   180  	//  1. The signature itself has an invalid structure, i.e. it can't be deserialized successfully. In this
   181  	//     case, already the aggregation step fails.
   182  	//  2. The signature was deserialized successfully, but the aggregate signature doesn't verify to the aggregate public key. In
   183  	//     this case, the aggregation step succeeds. But the post-check fails.
   184  	t.Run("invalid signature", func(t *testing.T) {
   185  		_, s := createAggregationData(t, 1)
   186  		invalidStructureSig := (crypto.Signature)([]byte{0, 0})
   187  		mismatchingSig := s[0]
   188  
   189  		for _, invalidSig := range []crypto.Signature{invalidStructureSig, mismatchingSig} {
   190  			aggregator, sigs := createAggregationData(t, signersNum)
   191  			ok, err := aggregator.VerifyAndAdd(0, sigs[0]) // first, add a valid signature
   192  			require.NoError(t, err)
   193  			assert.True(t, ok)
   194  
   195  			// add invalid signature for signer with index 1:
   196  			// method that check validity should reject it:
   197  			ok, err = aggregator.Verify(1, invalidSig) // stand-alone verification
   198  			require.NoError(t, err)
   199  			assert.False(t, ok)
   200  			ok, err = aggregator.VerifyAndAdd(1, invalidSig) // verification plus addition
   201  			require.NoError(t, err)
   202  			assert.False(t, ok)
   203  			// check signature is still not added
   204  			ok, err = aggregator.HasSignature(1)
   205  			require.NoError(t, err)
   206  			assert.False(t, ok)
   207  
   208  			// TrustedAdd should accept invalid signature
   209  			err = aggregator.TrustedAdd(1, invalidSig)
   210  			require.NoError(t, err)
   211  
   212  			// Aggregation should validate its own aggregation result and error with sentinel InvalidSignatureIncludedError
   213  			signers, agg, err := aggregator.Aggregate()
   214  			assert.Error(t, err)
   215  			assert.True(t, IsInvalidSignatureIncludedError(err))
   216  			assert.Nil(t, agg)
   217  			assert.Nil(t, signers)
   218  		}
   219  	})
   220  
   221  }
   222  
   223  func TestKeyAggregator(t *testing.T) {
   224  	r := time.Now().UnixNano()
   225  	mrand.Seed(r)
   226  	t.Logf("math rand seed is %d", r)
   227  
   228  	signersNum := 20
   229  	// create keys
   230  	indices := make([]int, 0, signersNum)
   231  	keys := make([]crypto.PublicKey, 0, signersNum)
   232  	seed := make([]byte, crypto.KeyGenSeedMinLenBLSBLS12381)
   233  	for i := 0; i < signersNum; i++ {
   234  		indices = append(indices, i)
   235  		_, err := rand.Read(seed)
   236  		require.NoError(t, err)
   237  		sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
   238  		require.NoError(t, err)
   239  		keys = append(keys, sk.PublicKey())
   240  	}
   241  	aggregator, err := NewPublicKeyAggregator(keys)
   242  	require.NoError(t, err)
   243  
   244  	// constructor edge cases
   245  	t.Run("constructor", func(t *testing.T) {
   246  		// wrong key types
   247  		seed := make([]byte, crypto.KeyGenSeedMinLenECDSAP256)
   248  		_, err = rand.Read(seed)
   249  		require.NoError(t, err)
   250  		sk, err := crypto.GeneratePrivateKey(crypto.ECDSAP256, seed)
   251  		require.NoError(t, err)
   252  		// wrong key type
   253  		_, err = NewPublicKeyAggregator([]crypto.PublicKey{sk.PublicKey()})
   254  		assert.Error(t, err)
   255  		_, err = NewPublicKeyAggregator([]crypto.PublicKey{nil})
   256  		assert.Error(t, err)
   257  		// empty keys
   258  		_, err = NewPublicKeyAggregator([]crypto.PublicKey{})
   259  		assert.Error(t, err)
   260  	})
   261  
   262  	// aggregation edge cases
   263  	t.Run("empty signers", func(t *testing.T) {
   264  		// empty input
   265  		_, err = aggregator.KeyAggregate(indices[:0])
   266  		assert.Error(t, err)
   267  		// invalid signer
   268  		_, err = aggregator.KeyAggregate([]int{-1})
   269  		assert.True(t, IsInvalidSignerIdxError(err))
   270  
   271  		_, err = aggregator.KeyAggregate([]int{signersNum})
   272  		assert.True(t, IsInvalidSignerIdxError(err))
   273  
   274  		aggregateTwice := func(l1, h1, l2, h2 int) {
   275  			var key, expectedKey crypto.PublicKey
   276  			key, err = aggregator.KeyAggregate(indices[l1:h1])
   277  			require.NoError(t, err)
   278  			expectedKey, err = crypto.AggregateBLSPublicKeys(keys[l1:h1])
   279  			require.NoError(t, err)
   280  			assert.True(t, key.Equals(expectedKey))
   281  
   282  			key, err = aggregator.KeyAggregate(indices[l2:h2])
   283  			require.NoError(t, err)
   284  			expectedKey, err = crypto.AggregateBLSPublicKeys(keys[l2:h2])
   285  			require.NoError(t, err)
   286  			assert.True(t, key.Equals(expectedKey))
   287  		}
   288  
   289  		// No overlaps
   290  		aggregateTwice(1, 5, 7, 13)
   291  
   292  		// big overlap (greedy algorithm)
   293  		aggregateTwice(1, 9, 2, 10)
   294  
   295  		// small overlap (aggregate from scratch)
   296  		aggregateTwice(1, 9, 8, 11)
   297  
   298  		// same signers
   299  		aggregateTwice(1, 9, 1, 9)
   300  	})
   301  
   302  	t.Run("greedy algorithm with randomized intervals", func(t *testing.T) {
   303  		// iterate over different random cases to make sure
   304  		// the delta algorithm works
   305  		rounds := 30
   306  		for i := 0; i < rounds; i++ {
   307  			go func() { // test module concurrency
   308  				low := mrand.Intn(signersNum - 1)
   309  				high := low + 1 + mrand.Intn(signersNum-1-low)
   310  				var key, expectedKey crypto.PublicKey
   311  				var err error
   312  				key, err = aggregator.KeyAggregate(indices[low:high])
   313  
   314  				require.NoError(t, err)
   315  				if low == high {
   316  					expectedKey = crypto.NeutralBLSPublicKey()
   317  				} else {
   318  					expectedKey, err = crypto.AggregateBLSPublicKeys(keys[low:high])
   319  					require.NoError(t, err)
   320  				}
   321  				assert.True(t, key.Equals(expectedKey))
   322  			}()
   323  		}
   324  	})
   325  }