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 }