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 }