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 }