github.com/status-im/status-go@v1.1.0/protocol/transport/filters_manager.go (about) 1 package transport 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/ecdsa" 7 "encoding/hex" 8 "sync" 9 10 "github.com/pkg/errors" 11 "go.uber.org/zap" 12 13 "github.com/status-im/status-go/eth-node/types" 14 "github.com/status-im/status-go/protocol/common/shard" 15 ) 16 17 const ( 18 minPow = 0.0 19 ) 20 21 type RawFilter struct { 22 FilterID string 23 Topic types.TopicType 24 SymKeyID string 25 } 26 27 type KeysPersistence interface { 28 All() (map[string][]byte, error) 29 Add(chatID string, key []byte) error 30 } 31 32 type FiltersService interface { 33 AddKeyPair(key *ecdsa.PrivateKey) (string, error) 34 DeleteKeyPair(keyID string) bool 35 36 AddSymKeyDirect(key []byte) (string, error) 37 AddSymKeyFromPassword(password string) (string, error) 38 GetSymKey(id string) ([]byte, error) 39 DeleteSymKey(id string) bool 40 41 Subscribe(opts *types.SubscriptionOptions) (string, error) 42 Unsubscribe(ctx context.Context, id string) error 43 UnsubscribeMany(ids []string) error 44 } 45 46 type FiltersManager struct { 47 service FiltersService 48 persistence KeysPersistence 49 privateKey *ecdsa.PrivateKey 50 keys map[string][]byte // a cache of symmetric manager derived from passwords 51 logger *zap.Logger 52 mutex sync.Mutex 53 filters map[string]*Filter 54 } 55 56 // NewFiltersManager returns a new filtersManager. 57 func NewFiltersManager(persistence KeysPersistence, service FiltersService, privateKey *ecdsa.PrivateKey, logger *zap.Logger) (*FiltersManager, error) { 58 if logger == nil { 59 logger = zap.NewNop() 60 } 61 62 keys, err := persistence.All() 63 if err != nil { 64 return nil, err 65 } 66 67 return &FiltersManager{ 68 privateKey: privateKey, 69 service: service, 70 persistence: persistence, 71 keys: keys, 72 filters: make(map[string]*Filter), 73 logger: logger.With(zap.Namespace("filtersManager")), 74 }, nil 75 } 76 77 func (f *FiltersManager) Init( 78 filtersToInit []FiltersToInitialize, 79 publicKeys []*ecdsa.PublicKey, 80 ) ([]*Filter, error) { 81 82 // Load our contact code. 83 _, err := f.LoadContactCode(&f.privateKey.PublicKey) 84 if err != nil { 85 return nil, errors.Wrap(err, "failed to load contact code") 86 } 87 88 // Load partitioned topic. 89 _, err = f.loadMyPartitioned() 90 if err != nil { 91 return nil, err 92 } 93 94 // Add discovery topic. 95 _, err = f.LoadDiscovery() 96 if err != nil { 97 return nil, err 98 } 99 100 // Add public, one-to-one and negotiated filters. 101 for _, fi := range filtersToInit { 102 _, err := f.LoadPublic(fi.ChatID, fi.PubsubTopic) 103 if err != nil { 104 return nil, err 105 } 106 } 107 108 for _, publicKey := range publicKeys { 109 _, err := f.LoadContactCode(publicKey) 110 if err != nil { 111 return nil, err 112 } 113 } 114 115 f.mutex.Lock() 116 defer f.mutex.Unlock() 117 118 var allFilters []*Filter 119 for _, f := range f.filters { 120 allFilters = append(allFilters, f) 121 } 122 return allFilters, nil 123 } 124 125 type FiltersToInitialize struct { 126 ChatID string 127 PubsubTopic string 128 } 129 130 func (f *FiltersManager) InitPublicFilters(publicFiltersToInit []FiltersToInitialize) ([]*Filter, error) { 131 var filters []*Filter 132 // Add public, one-to-one and negotiated filters. 133 for _, pf := range publicFiltersToInit { 134 f, err := f.LoadPublic(pf.ChatID, pf.PubsubTopic) 135 if err != nil { 136 return nil, err 137 } 138 filters = append(filters, f) 139 } 140 return filters, nil 141 } 142 143 type CommunityFilterToInitialize struct { 144 Shard *shard.Shard 145 PrivKey *ecdsa.PrivateKey 146 } 147 148 func (f *FiltersManager) InitCommunityFilters(communityFiltersToInitialize []CommunityFilterToInitialize) ([]*Filter, error) { 149 var filters []*Filter 150 f.mutex.Lock() 151 defer f.mutex.Unlock() 152 153 for _, communityFilter := range communityFiltersToInitialize { 154 // to satisfy gosec: C601 checks 155 cf := communityFilter 156 if cf.PrivKey == nil { 157 continue 158 } 159 160 topics := make([]string, 0) 161 topics = append(topics, shard.DefaultNonProtectedPubsubTopic()) 162 topics = append(topics, communityFilter.Shard.PubsubTopic()) 163 164 for _, pubsubTopic := range topics { 165 pk := &cf.PrivKey.PublicKey 166 identityStr := PublicKeyToStr(pk) 167 rawFilter, err := f.addAsymmetric(identityStr, pubsubTopic, cf.PrivKey, true) 168 if err != nil { 169 f.logger.Debug("could not register community filter", zap.Error(err)) 170 return nil, err 171 172 } 173 filterID := identityStr + "-admin" + pubsubTopic 174 filter := &Filter{ 175 ChatID: filterID, 176 FilterID: rawFilter.FilterID, 177 PubsubTopic: pubsubTopic, 178 ContentTopic: rawFilter.Topic, 179 Identity: identityStr, 180 Listen: true, 181 OneToOne: true, 182 } 183 184 f.filters[filterID] = filter 185 186 f.logger.Debug("registering filter for", zap.String("chatID", filterID), zap.String("type", "community"), zap.String("pubsubTopic", pubsubTopic), zap.String("contentTopic", rawFilter.Topic.String())) 187 188 filters = append(filters, filter) 189 } 190 } 191 return filters, nil 192 } 193 194 // DEPRECATED 195 func (f *FiltersManager) InitWithFilters(filters []*Filter) ([]*Filter, error) { 196 var ( 197 filtersToInit []FiltersToInitialize 198 publicKeys []*ecdsa.PublicKey 199 ) 200 201 for _, filter := range filters { 202 if filter.Identity != "" && filter.OneToOne { 203 publicKey, err := StrToPublicKey(filter.Identity) 204 if err != nil { 205 return nil, err 206 } 207 publicKeys = append(publicKeys, publicKey) 208 } else if filter.ChatID != "" { 209 filtersToInit = append(filtersToInit, FiltersToInitialize{ChatID: filter.ChatID, PubsubTopic: filter.PubsubTopic}) 210 } 211 } 212 213 return f.Init(filtersToInit, publicKeys) 214 } 215 216 func (f *FiltersManager) Reset(ctx context.Context) error { 217 var filters []*Filter 218 219 f.mutex.Lock() 220 for _, f := range f.filters { 221 filters = append(filters, f) 222 } 223 f.mutex.Unlock() 224 225 return f.Remove(ctx, filters...) 226 } 227 228 func (f *FiltersManager) Filters() (result []*Filter) { 229 f.mutex.Lock() 230 defer f.mutex.Unlock() 231 232 for _, f := range f.filters { 233 result = append(result, f) 234 } 235 236 return 237 } 238 239 func (f *FiltersManager) Filter(chatID string) *Filter { 240 f.mutex.Lock() 241 defer f.mutex.Unlock() 242 return f.filters[chatID] 243 } 244 245 // FilterByFilterID returns a Filter with a given Whisper filter ID. 246 func (f *FiltersManager) FilterByFilterID(filterID string) *Filter { 247 f.mutex.Lock() 248 defer f.mutex.Unlock() 249 for _, f := range f.filters { 250 if f.FilterID == filterID { 251 return f 252 } 253 } 254 return nil 255 } 256 257 func (f *FiltersManager) FilterByTopic(topic []byte) *Filter { 258 f.mutex.Lock() 259 defer f.mutex.Unlock() 260 for _, f := range f.filters { 261 if bytes.Equal(types.TopicTypeToByteArray(f.ContentTopic), topic) { 262 return f 263 } 264 } 265 return nil 266 } 267 268 // FiltersByIdentities returns an array of filters for given list of public keys 269 func (f *FiltersManager) FiltersByIdentities(identities []string) []*Filter { 270 f.mutex.Lock() 271 defer f.mutex.Unlock() 272 273 identitiesMap := make(map[string]bool) 274 275 for _, identity := range identities { 276 identitiesMap[identity] = true 277 } 278 279 var filters []*Filter 280 281 for _, filter := range f.filters { 282 // Pre-pend 0x before comparing 283 if identitiesMap["0x"+filter.Identity] { 284 filters = append(filters, filter) 285 } 286 } 287 return filters 288 } 289 290 // FilterByChatID returns a Filter for given chat id 291 func (f *FiltersManager) FilterByChatID(chatID string) *Filter { 292 f.mutex.Lock() 293 defer f.mutex.Unlock() 294 295 return f.filters[chatID] 296 } 297 298 // Remove remove all the filters associated with a chat/identity 299 func (f *FiltersManager) Remove(ctx context.Context, filters ...*Filter) error { 300 f.mutex.Lock() 301 defer f.mutex.Unlock() 302 303 for _, filter := range filters { 304 if err := f.service.Unsubscribe(ctx, filter.FilterID); err != nil { 305 return err 306 } 307 if filter.SymKeyID != "" { 308 f.service.DeleteSymKey(filter.SymKeyID) 309 } 310 delete(f.filters, filter.ChatID) 311 } 312 313 return nil 314 } 315 316 // Remove remove all the filters associated with a chat/identity 317 func (f *FiltersManager) RemoveNoListenFilters() error { 318 f.mutex.Lock() 319 defer f.mutex.Unlock() 320 var filterIDs []string 321 var filters []*Filter 322 323 for _, f := range filters { 324 if !f.Listen { 325 filterIDs = append(filterIDs, f.FilterID) 326 filters = append(filters, f) 327 } 328 } 329 if err := f.service.UnsubscribeMany(filterIDs); err != nil { 330 return err 331 } 332 333 for _, filter := range filters { 334 if filter.SymKeyID != "" { 335 f.service.DeleteSymKey(filter.SymKeyID) 336 } 337 delete(f.filters, filter.ChatID) 338 } 339 340 return nil 341 } 342 343 // Remove remove all the filters associated with a chat/identity 344 func (f *FiltersManager) RemoveFilterByChatID(chatID string) (*Filter, error) { 345 // TODO: remove subscriptions from waku2 if required. Might need to be implemented in transport 346 347 f.mutex.Lock() 348 filter, ok := f.filters[chatID] 349 f.mutex.Unlock() 350 351 if !ok { 352 return nil, nil 353 } 354 355 err := f.Remove(context.Background(), filter) 356 if err != nil { 357 return nil, err 358 } 359 360 return filter, nil 361 } 362 363 // LoadPartitioned creates a filter for a partitioned topic. 364 func (f *FiltersManager) LoadPartitioned(publicKey *ecdsa.PublicKey, identity *ecdsa.PrivateKey, listen bool) (*Filter, error) { 365 return f.loadPartitioned(publicKey, identity, listen, false) 366 } 367 368 // LoadEphemeral creates a filter for a partitioned/personal topic. 369 func (f *FiltersManager) LoadEphemeral(publicKey *ecdsa.PublicKey, identity *ecdsa.PrivateKey, listen bool) (*Filter, error) { 370 return f.loadPartitioned(publicKey, identity, listen, true) 371 } 372 373 // LoadPersonal creates a filter for a personal topic. 374 func (f *FiltersManager) LoadPersonal(publicKey *ecdsa.PublicKey, identity *ecdsa.PrivateKey, listen bool) (*Filter, error) { 375 f.mutex.Lock() 376 defer f.mutex.Unlock() 377 378 chatID := PersonalDiscoveryTopic(publicKey) 379 if _, ok := f.filters[chatID]; ok { 380 return f.filters[chatID], nil 381 } 382 383 // We set up a filter so we can publish, 384 // but we discard envelopes if listen is false. 385 filter, err := f.addAsymmetric(chatID, "", identity, listen) 386 if err != nil { 387 f.logger.Debug("could not register personal topic filter", zap.Error(err)) 388 return nil, err 389 } 390 391 chat := &Filter{ 392 ChatID: chatID, 393 FilterID: filter.FilterID, 394 ContentTopic: filter.Topic, 395 Identity: PublicKeyToStr(publicKey), 396 Listen: listen, 397 OneToOne: true, 398 } 399 400 f.filters[chatID] = chat 401 402 f.logger.Debug("registering filter for", zap.String("chatID", chatID), zap.String("type", "personal"), zap.String("topic", filter.Topic.String())) 403 404 return chat, nil 405 406 } 407 408 func (f *FiltersManager) loadMyPartitioned() (*Filter, error) { 409 return f.loadPartitioned(&f.privateKey.PublicKey, f.privateKey, true, false) 410 } 411 412 func (f *FiltersManager) loadPartitioned(publicKey *ecdsa.PublicKey, identity *ecdsa.PrivateKey, listen, ephemeral bool) (*Filter, error) { 413 f.mutex.Lock() 414 defer f.mutex.Unlock() 415 416 chatID := PartitionedTopic(publicKey) 417 if _, ok := f.filters[chatID]; ok { 418 return f.filters[chatID], nil 419 } 420 421 // We set up a filter so we can publish, 422 // but we discard envelopes if listen is false. 423 filter, err := f.addAsymmetric(chatID, "", identity, listen) 424 if err != nil { 425 f.logger.Debug("could not register partitioned topic", zap.String("chatID", chatID), zap.Error(err)) 426 return nil, err 427 } 428 429 chat := &Filter{ 430 ChatID: chatID, 431 FilterID: filter.FilterID, 432 ContentTopic: filter.Topic, 433 Identity: PublicKeyToStr(publicKey), 434 Listen: listen, 435 Ephemeral: ephemeral, 436 OneToOne: true, 437 } 438 439 f.filters[chatID] = chat 440 441 f.logger.Debug("registering filter for", zap.String("chatID", chatID), zap.String("type", "partitioned"), zap.String("topic", filter.Topic.String())) 442 443 return chat, nil 444 } 445 446 // LoadNegotiated loads a negotiated secret as a filter. 447 func (f *FiltersManager) LoadNegotiated(secret types.NegotiatedSecret) (*Filter, error) { 448 f.mutex.Lock() 449 defer f.mutex.Unlock() 450 451 chatID := NegotiatedTopic(secret.PublicKey) 452 453 if _, ok := f.filters[chatID]; ok { 454 return f.filters[chatID], nil 455 } 456 457 keyString := hex.EncodeToString(secret.Key) 458 filter, err := f.addSymmetric(keyString, "") 459 if err != nil { 460 f.logger.Debug("could not register negotiated topic", zap.Error(err)) 461 return nil, err 462 } 463 464 chat := &Filter{ 465 ChatID: chatID, 466 ContentTopic: filter.Topic, 467 SymKeyID: filter.SymKeyID, 468 FilterID: filter.FilterID, 469 Identity: PublicKeyToStr(secret.PublicKey), 470 Negotiated: true, 471 Listen: true, 472 OneToOne: true, 473 } 474 475 f.filters[chat.ChatID] = chat 476 477 f.logger.Debug("registering filter for", zap.String("chatID", chatID), zap.String("type", "negotiated"), zap.String("topic", filter.Topic.String())) 478 479 return chat, nil 480 } 481 482 // LoadDiscovery adds 1 discovery filter 483 // for the personal discovery topic. 484 func (f *FiltersManager) LoadDiscovery() ([]*Filter, error) { 485 f.mutex.Lock() 486 defer f.mutex.Unlock() 487 488 personalDiscoveryTopic := PersonalDiscoveryTopic(&f.privateKey.PublicKey) 489 490 // Check if filters are already loaded. 491 var result []*Filter 492 493 expectedTopicCount := 1 494 495 if chat, ok := f.filters[personalDiscoveryTopic]; ok { 496 result = append(result, chat) 497 } 498 499 if len(result) == expectedTopicCount { 500 return result, nil 501 } 502 503 identityStr := PublicKeyToStr(&f.privateKey.PublicKey) 504 505 // Load personal discovery 506 personalDiscoveryChat := &Filter{ 507 ChatID: personalDiscoveryTopic, 508 Identity: identityStr, 509 Discovery: true, 510 Listen: true, 511 OneToOne: true, 512 } 513 514 discoveryResponse, err := f.addAsymmetric(personalDiscoveryChat.ChatID, personalDiscoveryChat.PubsubTopic, f.privateKey, true) 515 if err != nil { 516 f.logger.Debug("could not register discovery topic", zap.String("chatID", personalDiscoveryChat.ChatID), zap.Error(err)) 517 return nil, err 518 } 519 520 personalDiscoveryChat.ContentTopic = discoveryResponse.Topic 521 personalDiscoveryChat.FilterID = discoveryResponse.FilterID 522 523 f.filters[personalDiscoveryChat.ChatID] = personalDiscoveryChat 524 525 f.logger.Debug("registering filter for", zap.String("chatID", personalDiscoveryChat.ChatID), zap.String("type", "discovery"), zap.String("topic", personalDiscoveryChat.ContentTopic.String())) 526 527 return []*Filter{personalDiscoveryChat}, nil 528 } 529 530 func (f *FiltersManager) PersonalTopicFilter() *Filter { 531 personalDiscoveryTopic := PersonalDiscoveryTopic(&f.privateKey.PublicKey) 532 533 return f.filters[personalDiscoveryTopic] 534 } 535 536 // LoadPublic adds a filter for a public chat. 537 func (f *FiltersManager) LoadPublic(chatID string, pubsubTopic string) (*Filter, error) { 538 f.mutex.Lock() 539 defer f.mutex.Unlock() 540 541 if chat, ok := f.filters[chatID]; ok { 542 if chat.PubsubTopic != pubsubTopic { 543 f.logger.Debug("updating pubsub topic for filter", 544 zap.String("chatID", chatID), 545 zap.String("type", "public"), 546 zap.String("oldTopic", chat.PubsubTopic), 547 zap.String("newTopic", pubsubTopic), 548 ) 549 chat.PubsubTopic = pubsubTopic 550 f.filters[chatID] = chat 551 } 552 553 return chat, nil 554 } 555 556 filterAndTopic, err := f.addSymmetric(chatID, pubsubTopic) 557 if err != nil { 558 f.logger.Debug("could not register public chat topic", zap.String("chatID", chatID), zap.Error(err)) 559 return nil, err 560 } 561 562 chat := &Filter{ 563 ChatID: chatID, 564 FilterID: filterAndTopic.FilterID, 565 SymKeyID: filterAndTopic.SymKeyID, 566 ContentTopic: filterAndTopic.Topic, 567 PubsubTopic: pubsubTopic, 568 Listen: true, 569 OneToOne: false, 570 } 571 572 f.filters[chatID] = chat 573 574 f.logger.Debug("registering filter for", 575 zap.String("chatID", chatID), 576 zap.String("type", "public"), 577 zap.String("ContentTopic", filterAndTopic.Topic.String()), 578 zap.String("PubsubTopic", pubsubTopic), 579 ) 580 581 return chat, nil 582 } 583 584 // LoadContactCode creates a filter for the advertise topic for a given public key. 585 func (f *FiltersManager) LoadContactCode(pubKey *ecdsa.PublicKey) (*Filter, error) { 586 f.mutex.Lock() 587 defer f.mutex.Unlock() 588 589 chatID := ContactCodeTopic(pubKey) 590 591 if _, ok := f.filters[chatID]; ok { 592 return f.filters[chatID], nil 593 } 594 595 contactCodeFilter, err := f.addSymmetric(chatID, "") 596 if err != nil { 597 f.logger.Debug("could not register contact code topic", zap.String("chatID", chatID), zap.Error(err)) 598 return nil, err 599 } 600 601 chat := &Filter{ 602 ChatID: chatID, 603 FilterID: contactCodeFilter.FilterID, 604 ContentTopic: contactCodeFilter.Topic, 605 SymKeyID: contactCodeFilter.SymKeyID, 606 Identity: PublicKeyToStr(pubKey), 607 Listen: true, 608 } 609 610 f.filters[chatID] = chat 611 612 f.logger.Debug("registering filter for", zap.String("chatID", chatID), zap.String("type", "contact-code"), zap.String("topic", contactCodeFilter.Topic.String())) 613 614 return chat, nil 615 } 616 617 // addSymmetric adds a symmetric key filter 618 func (f *FiltersManager) addSymmetric(chatID string, pubsubTopic string) (*RawFilter, error) { 619 var symKeyID string 620 var err error 621 622 topic := ToTopic(chatID) 623 topics := [][]byte{topic} 624 625 symKey, ok := f.keys[chatID] 626 if ok { 627 symKeyID, err = f.service.AddSymKeyDirect(symKey) 628 if err != nil { 629 return nil, err 630 } 631 } else { 632 symKeyID, err = f.service.AddSymKeyFromPassword(chatID) 633 if err != nil { 634 return nil, err 635 } 636 if symKey, err = f.service.GetSymKey(symKeyID); err != nil { 637 return nil, err 638 } 639 f.keys[chatID] = symKey 640 641 err = f.persistence.Add(chatID, symKey) 642 if err != nil { 643 return nil, err 644 } 645 } 646 647 id, err := f.service.Subscribe(&types.SubscriptionOptions{ 648 SymKeyID: symKeyID, 649 PoW: minPow, 650 Topics: topics, 651 PubsubTopic: pubsubTopic, 652 }) 653 if err != nil { 654 return nil, err 655 } 656 657 return &RawFilter{ 658 FilterID: id, 659 SymKeyID: symKeyID, 660 Topic: types.BytesToTopic(topic), 661 }, nil 662 } 663 664 // addAsymmetricFilter adds a filter with our private key 665 // and set minPow according to the listen parameter. 666 func (f *FiltersManager) addAsymmetric(chatID string, pubsubTopic string, identity *ecdsa.PrivateKey, listen bool) (*RawFilter, error) { 667 var ( 668 err error 669 pow = 1.0 // use PoW high enough to discard all messages for the filter 670 ) 671 672 if listen { 673 pow = minPow 674 } 675 676 topic := ToTopic(chatID) 677 topics := [][]byte{topic} 678 679 privateKeyID, err := f.service.AddKeyPair(identity) 680 if err != nil { 681 return nil, err 682 } 683 684 id, err := f.service.Subscribe(&types.SubscriptionOptions{ 685 PrivateKeyID: privateKeyID, 686 PoW: pow, 687 Topics: topics, 688 PubsubTopic: pubsubTopic, 689 }) 690 if err != nil { 691 return nil, err 692 } 693 return &RawFilter{FilterID: id, Topic: types.BytesToTopic(topic)}, nil 694 } 695 696 // GetNegotiated returns a negotiated chat given an identity 697 func (f *FiltersManager) GetNegotiated(identity *ecdsa.PublicKey) *Filter { 698 f.mutex.Lock() 699 defer f.mutex.Unlock() 700 701 return f.filters[NegotiatedTopic(identity)] 702 }