From 44a8c34042246ee839544c06d4e56ac1585c0742 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Thu, 23 May 2019 13:24:49 +0200 Subject: [PATCH] Addl library functions --- api/backend.go | 31 ++ lib/library.go | 74 +++++ mobile/status.go | 71 +++++ services/shhext/api.go | 6 +- services/shhext/filter/service.go | 396 +++++++++++++++---------- services/shhext/filter/service_test.go | 17 +- services/shhext/service.go | 24 +- signal/events_shhext.go | 17 +- 8 files changed, 460 insertions(+), 176 deletions(-) diff --git a/api/backend.go b/api/backend.go index 32cda0cc69..2ab6fdb10e 100644 --- a/api/backend.go +++ b/api/backend.go @@ -27,6 +27,7 @@ import ( "github.com/status-im/status-go/services/rpcfilters" "github.com/status-im/status-go/services/shhext/chat" "github.com/status-im/status-go/services/shhext/chat/crypto" + "github.com/status-im/status-go/services/shhext/filter" "github.com/status-im/status-go/services/subscriptions" "github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/signal" @@ -715,6 +716,36 @@ func (b *StatusBackend) SignGroupMembership(content string) (string, error) { return crypto.Sign(content, selectedChatAccount.AccountKey.PrivateKey) } +// LoadFilters loads filter on sshext +func (b *StatusBackend) LoadFilters(chats []*filter.Chat) ([]*filter.Chat, error) { + st, err := b.statusNode.ShhExtService() + if err != nil { + return nil, err + } + + return st.LoadFilters(chats) +} + +// LoadFilter loads filter on sshext +func (b *StatusBackend) LoadFilter(chat *filter.Chat) ([]*filter.Chat, error) { + st, err := b.statusNode.ShhExtService() + if err != nil { + return nil, err + } + + return st.LoadFilter(chat) +} + +// RemoveFilter remove a filter +func (b *StatusBackend) RemoveFilter(chat *filter.Chat) error { + st, err := b.statusNode.ShhExtService() + if err != nil { + return err + } + + return st.RemoveFilter(chat) +} + // EnableInstallation enables an installation for multi-device sync. func (b *StatusBackend) EnableInstallation(installationID string) error { selectedChatAccount, err := b.AccountManager().SelectedChatAccount() diff --git a/lib/library.go b/lib/library.go index e52aa55a92..e5051d2e2a 100644 --- a/lib/library.go +++ b/lib/library.go @@ -18,6 +18,7 @@ import ( "github.com/status-im/status-go/params" "github.com/status-im/status-go/profiling" "github.com/status-im/status-go/services/personal" + "github.com/status-im/status-go/services/shhext/filter" "github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" @@ -115,6 +116,79 @@ func ExtractIdentityFromContactCode(bundleString *C.char) *C.char { return C.CString(string(data)) } +// LoadFilters load all whisper filters +//export LoadFilters +func LoadFilters(chatsStr *C.char) *C.char { + var chats []*filter.Chat + + if err := json.Unmarshal([]byte(C.GoString(chatsStr)), &chats); err != nil { + return makeJSONResponse(err) + } + + response, err := statusBackend.LoadFilters(chats) + if err != nil { + return makeJSONResponse(err) + } + + data, err := json.Marshal(struct { + Chats []*filter.Chat `json:"result"` + }{Chats: response}) + if err != nil { + return makeJSONResponse(err) + } + + return C.CString(string(data)) +} + +// LoadFilter load a whisper filter +//export LoadFilter +func LoadFilter(chatStr *C.char) *C.char { + var chat *filter.Chat + + if err := json.Unmarshal([]byte(C.GoString(chatStr)), &chat); err != nil { + return makeJSONResponse(err) + } + + response, err := statusBackend.LoadFilter(chat) + if err != nil { + return makeJSONResponse(err) + } + + data, err := json.Marshal(struct { + Chats []*filter.Chat `json:"result"` + }{Chats: response}) + + if err != nil { + return makeJSONResponse(err) + } + + return C.CString(string(data)) +} + +// RemoveFilter load a whisper filter +//export RemoveFilter +func RemoveFilter(chatStr *C.char) *C.char { + var chat *filter.Chat + + if err := json.Unmarshal([]byte(C.GoString(chatStr)), &chat); err != nil { + return makeJSONResponse(err) + } + + err := statusBackend.RemoveFilter(chat) + if err != nil { + return makeJSONResponse(err) + } + + data, err := json.Marshal(struct { + Response string `json:"response"` + }{Response: "ok"}) + if err != nil { + return makeJSONResponse(err) + } + + return C.CString(string(data)) +} + // ExtractGroupMembershipSignatures extract public keys from tuples of content/signature //export ExtractGroupMembershipSignatures func ExtractGroupMembershipSignatures(signaturePairsStr *C.char) *C.char { diff --git a/mobile/status.go b/mobile/status.go index d450206680..e33f98845e 100644 --- a/mobile/status.go +++ b/mobile/status.go @@ -16,6 +16,7 @@ import ( "github.com/status-im/status-go/params" "github.com/status-im/status-go/profiling" "github.com/status-im/status-go/services/personal" + "github.com/status-im/status-go/services/shhext/filter" "github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" @@ -673,3 +674,73 @@ func SignHash(hexEncodedHash string) string { return hexEncodedSignature } + +// LoadFilters load all whisper filters +func LoadFilters(chatsStr string) string { + var chats []*filter.Chat + + if err := json.Unmarshal([]byte(chatsStr), &chats); err != nil { + return makeJSONResponse(err) + } + + response, err := statusBackend.LoadFilters(chats) + if err != nil { + return makeJSONResponse(err) + } + + data, err := json.Marshal(struct { + Chats []*filter.Chat `json:"result"` + }{Chats: response}) + if err != nil { + return makeJSONResponse(err) + } + + return string(data) +} + +// LoadFilter load a whisper filter +func LoadFilter(chatStr string) string { + var chat *filter.Chat + + if err := json.Unmarshal([]byte(chatStr), &chat); err != nil { + return makeJSONResponse(err) + } + + response, err := statusBackend.LoadFilter(chat) + if err != nil { + return makeJSONResponse(err) + } + + data, err := json.Marshal(struct { + Chats []*filter.Chat `json:"result"` + }{Chats: response}) + if err != nil { + return makeJSONResponse(err) + } + + return string(data) +} + +// RemoveFilter load a whisper filter +//export RemoveFilter +func RemoveFilter(chatStr string) string { + var chat *filter.Chat + + if err := json.Unmarshal([]byte(chatStr), &chat); err != nil { + return makeJSONResponse(err) + } + + err := statusBackend.RemoveFilter(chat) + if err != nil { + return makeJSONResponse(err) + } + + data, err := json.Marshal(struct { + Response string `json:"response"` + }{Response: "ok"}) + if err != nil { + return makeJSONResponse(err) + } + + return string(data) +} diff --git a/services/shhext/api.go b/services/shhext/api.go index 95fc9ed7f0..8406a61570 100644 --- a/services/shhext/api.go +++ b/services/shhext/api.go @@ -560,10 +560,14 @@ func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirect if chat != nil { whisperMessage.SymKeyID = chat.SymKeyID - whisperMessage.Topic = whisper.BytesToTopic(chat.Topic) + whisperMessage.Topic = chat.Topic whisperMessage.PublicKey = nil } } else if partitionedTopicSupported { + // Create filter on demand + if _, err := api.service.filter.LoadPartitioned(privateKey, publicKey, false); err != nil { + return nil, err + } t := filter.PublicKeyToPartitionedTopicBytes(publicKey) whisperMessage.Topic = whisper.BytesToTopic(t) diff --git a/services/shhext/filter/service.go b/services/shhext/filter/service.go index 75800b2448..d916b0719b 100644 --- a/services/shhext/filter/service.go +++ b/services/shhext/filter/service.go @@ -3,6 +3,7 @@ package filter import ( "crypto/ecdsa" "encoding/hex" + "errors" "fmt" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -18,56 +19,29 @@ const ( // The number of partitions var nPartitions = big.NewInt(5000) - -func ToTopic(s string) []byte { - return crypto.Keccak256([]byte(s))[:whisper.TopicLength] -} - -func PublicKeyToPartitionedTopic(publicKey *ecdsa.PublicKey) string { - partition := big.NewInt(0) - partition.Mod(publicKey.X, nPartitions) - return fmt.Sprintf("contact-discovery-%d", partition.Int64()) -} - -func PublicKeyToPartitionedTopicBytes(publicKey *ecdsa.PublicKey) []byte { - return ToTopic(PublicKeyToPartitionedTopic(publicKey)) -} - -func chatIDToPartitionedTopic(identity string) (string, error) { - publicKeyBytes, err := hex.DecodeString(identity) - if err != nil { - return "", err - } - - publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes) - if err != nil { - return "", err - } - - return PublicKeyToPartitionedTopic(publicKey), nil -} +var minPow = 0.001 type Filter struct { FilterID string - Topic []byte + Topic whisper.TopicType SymKeyID string } type Chat struct { // ChatID is the identifier of the chat - ChatID string + ChatID string `json:"chatId"` // SymKeyID is the symmetric key id used for symmetric chats - SymKeyID string + SymKeyID string `json:"symKeyId"` // OneToOne tells us if we need to use asymmetric encryption for this chat - OneToOne bool + OneToOne bool `json:"oneToOne"` // Listen is whether we are actually listening for messages on this chat, or the filter is only created in order to be able to post on the topic - Listen bool + Listen bool `json:"listen"` // FilterID the whisper filter id generated - FilterID string + FilterID string `json:"filterId"` // Identity is the public key of the other recipient for non-public chats - Identity string + Identity string `json:"identity"` // Topic is the whisper topic - Topic []byte + Topic whisper.TopicType `json:"topic"` } type Service struct { @@ -78,6 +52,7 @@ type Service struct { mutex sync.Mutex } +// New returns a new filter service func New(k string, w *whisper.Whisper, t *topic.Service) *Service { return &Service{ keyID: k, @@ -88,57 +63,44 @@ func New(k string, w *whisper.Whisper, t *topic.Service) *Service { } } -// LoadDiscovery adds the discovery filter -func (s *Service) LoadDiscovery(myKey *ecdsa.PrivateKey) error { - s.mutex.Lock() - defer s.mutex.Unlock() - - discoveryChat := &Chat{ - ChatID: discoveryTopic, - } - - discoveryResponse, err := s.AddAsymmetricFilter(myKey, discoveryChat.ChatID, true) - if err != nil { - return err +// TODO: Dont add duplicated filters +// TODO: Create partitioned topic only when actually sending to a user +// TODO: format response so that it can be parsed by the user +// LoadChat should return a list of newly chats loaded +func (s *Service) Init(chats []*Chat) ([]*Chat, error) { + log.Debug("Initializing filter service", "chats", chats) + keyID := s.whisper.SelectedKeyPairID() + if keyID == "" { + return nil, errors.New("no key selected") } - - discoveryChat.Topic = discoveryResponse.Topic - discoveryChat.FilterID = discoveryResponse.FilterID - - s.chats[discoveryChat.ChatID] = discoveryChat - return nil -} - -func (s *Service) Init(chats []*Chat) error { - log.Debug("Initializing filter service") - myKey, err := s.whisper.GetPrivateKey(s.keyID) + myKey, err := s.whisper.GetPrivateKey(keyID) if err != nil { - return err + return nil, err } // Add our own topic log.Debug("Loading one to one chats") identityStr := fmt.Sprintf("%x", crypto.FromECDSAPub(&myKey.PublicKey)) - err = s.LoadOneToOne(myKey, identityStr, true) + _, err = s.loadOneToOne(myKey, identityStr, true) if err != nil { log.Error("Error loading one to one chats", "err", err) - return err + return nil, err } // Add discovery topic log.Debug("Loading discovery topics") - err = s.LoadDiscovery(myKey) + err = s.loadDiscovery(myKey) if err != nil { - return err + return nil, err } // Add the various one to one and public chats log.Debug("Loading chats") for _, chat := range chats { - err = s.Load(myKey, chat) + _, err = s.load(myKey, chat) if err != nil { - return err + return nil, err } } @@ -146,18 +108,26 @@ func (s *Service) Init(chats []*Chat) error { log.Debug("Loading negotiated topics") secrets, err := s.topic.All() if err != nil { - return err + return nil, err } for _, secret := range secrets { - if err := s.ProcessNegotiatedSecret(secret); err != nil { - return err + if _, err := s.ProcessNegotiatedSecret(secret); err != nil { + return nil, err } } - return nil + s.mutex.Lock() + defer s.mutex.Unlock() + + var allChats []*Chat + for _, chat := range s.chats { + allChats = append(allChats, chat) + } + return allChats, nil } +// Stop removes all the filters func (s *Service) Stop() error { for _, chat := range s.chats { if err := s.Remove(chat); err != nil { @@ -167,6 +137,7 @@ func (s *Service) Stop() error { return nil } +// Remove remove all the filters associated with a chat/identity func (s *Service) Remove(chat *Chat) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -180,51 +151,215 @@ func (s *Service) Remove(chat *Chat) error { delete(s.chats, chat.ChatID) return nil +} + +// LoadPartitioned creates a filter for a partitioned topic +func (s *Service) LoadPartitioned(myKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, listen bool) (*Chat, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + chatID := PublicKeyToPartitionedTopic(theirPublicKey) + + if _, ok := s.chats[chatID]; ok { + return s.chats[chatID], nil + } + + // We set up a filter so we can publish, but we discard envelopes if listen is false + filter, err := s.addAsymmetricFilter(myKey, chatID, listen) + if err != nil { + return nil, err + } + + chat := &Chat{ + ChatID: chatID, + FilterID: filter.FilterID, + Topic: filter.Topic, + Listen: listen, + } + + s.chats[chatID] = chat + + return chat, nil +} + +// Load creates filters for a given chat, and returns all the created filters +func (s *Service) Load(chat *Chat) ([]*Chat, error) { + myKey, err := s.whisper.GetPrivateKey(s.keyID) + if err != nil { + return nil, err + } + return s.load(myKey, chat) } -// LoadOneToOne creates two filters for a given chat, one listening to the contact codes -// and another on the partitioned topic. We pass a listen parameter to indicated whether -// we are listening to messages on the partitioned topic -func (s *Service) LoadOneToOne(myKey *ecdsa.PrivateKey, identity string, listen bool) error { +// Get returns a negotiated filter given an identity +func (s *Service) Get(identity *ecdsa.PublicKey) *Chat { s.mutex.Lock() defer s.mutex.Unlock() - contactCodeChatID := identity + "-contact-code" - contactCodeFilter, err := s.AddSymmetric(contactCodeChatID) + return s.chats[negotiatedID(identity)] +} + +// ProcessNegotiatedSecret adds a filter based on the agreed secret +func (s *Service) ProcessNegotiatedSecret(secret *topic.Secret) (*Chat, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + chatID := negotiatedID(secret.Identity) + // If we already have a filter do nothing + if _, ok := s.chats[chatID]; ok { + return s.chats[chatID], nil + } + + keyString := fmt.Sprintf("%x", secret.Key) + filter, err := s.addSymmetric(keyString) if err != nil { - return err + return nil, err } - s.chats[contactCodeChatID] = &Chat{ - ChatID: contactCodeChatID, - FilterID: contactCodeFilter.FilterID, - Topic: contactCodeFilter.Topic, - SymKeyID: contactCodeFilter.SymKeyID, - Identity: identity, + identityStr := fmt.Sprintf("%x", crypto.FromECDSAPub(secret.Identity)) + + chat := &Chat{ + ChatID: chatID, + Topic: filter.Topic, + SymKeyID: filter.SymKeyID, + FilterID: filter.FilterID, + Identity: identityStr, + Listen: true, + } + + s.chats[chat.ChatID] = chat + return chat, nil +} + +// ToTopic converts a string to a whisper topic +func ToTopic(s string) []byte { + return crypto.Keccak256([]byte(s))[:whisper.TopicLength] +} + +// PublicKeyToPartitionedTopic returns the associated partitioned topic string +// with the given public key +func PublicKeyToPartitionedTopic(publicKey *ecdsa.PublicKey) string { + partition := big.NewInt(0) + partition.Mod(publicKey.X, nPartitions) + return fmt.Sprintf("contact-discovery-%d", partition.Int64()) +} + +// PublicKeyToPartitionedTopicBytes returns the bytes of the partitioned topic +// associated with the given public key +func PublicKeyToPartitionedTopicBytes(publicKey *ecdsa.PublicKey) []byte { + return ToTopic(PublicKeyToPartitionedTopic(publicKey)) +} + +// loadDiscovery adds the discovery filter +func (s *Service) loadDiscovery(myKey *ecdsa.PrivateKey) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + if _, ok := s.chats[discoveryTopic]; ok { + return nil } - partitionedTopicChatID, err := chatIDToPartitionedTopic(identity) + discoveryChat := &Chat{ + ChatID: discoveryTopic, + Listen: true, + } + + discoveryResponse, err := s.addAsymmetricFilter(myKey, discoveryChat.ChatID, true) if err != nil { return err } - // We set up a filter so we can publish, but we discard envelopes if listen is false - partitionedTopicFilter, err := s.AddAsymmetricFilter(myKey, partitionedTopicChatID, listen) + + discoveryChat.Topic = discoveryResponse.Topic + discoveryChat.FilterID = discoveryResponse.FilterID + + s.chats[discoveryChat.ChatID] = discoveryChat + return nil +} + +// loadPublic adds a filter for a public chat +func (s *Service) loadPublic(chat *Chat) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + if _, ok := s.chats[chat.ChatID]; ok { + return nil + } + + filterAndTopic, err := s.addSymmetric(chat.ChatID) if err != nil { return err } - s.chats[partitionedTopicChatID] = &Chat{ - ChatID: partitionedTopicChatID, - FilterID: partitionedTopicFilter.FilterID, - Topic: partitionedTopicFilter.Topic, + + chat.FilterID = filterAndTopic.FilterID + chat.SymKeyID = filterAndTopic.SymKeyID + chat.Topic = filterAndTopic.Topic + chat.Listen = true + + s.chats[chat.ChatID] = chat + return nil +} + +// loadOneToOne creates two filters for a given chat, one listening to the contact codes +// and another on the partitioned topic, if listen is specified. +func (s *Service) loadOneToOne(myKey *ecdsa.PrivateKey, identity string, listen bool) ([]*Chat, error) { + var chats []*Chat + contactCodeChat, err := s.loadContactCode(identity) + if err != nil { + return nil, err + } + + chats = append(chats, contactCodeChat) + + if listen { + publicKeyBytes, err := hex.DecodeString(identity) + if err != nil { + return nil, err + } + + publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes) + if err != nil { + return nil, err + } + + partitionedChat, err := s.LoadPartitioned(myKey, publicKey, listen) + if err != nil { + return nil, err + } + + chats = append(chats, partitionedChat) + } + return chats, nil +} + +// loadContactCode creates a filter for the topic are advertised for a given identity +func (s *Service) loadContactCode(identity string) (*Chat, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + chatID := identity + "-contact-code" + if _, ok := s.chats[chatID]; ok { + return s.chats[chatID], nil + } + + contactCodeFilter, err := s.addSymmetric(chatID) + if err != nil { + return nil, err + } + chat := &Chat{ + ChatID: chatID, + FilterID: contactCodeFilter.FilterID, + Topic: contactCodeFilter.Topic, + SymKeyID: contactCodeFilter.SymKeyID, Identity: identity, - Listen: listen, + Listen: true, } - return nil + s.chats[chatID] = chat + return chat, nil } -func (s *Service) AddSymmetric(chatID string) (*Filter, error) { +// addSymmetric adds a symmetric key filter +func (s *Service) addSymmetric(chatID string) (*Filter, error) { var symKey []byte topic := ToTopic(chatID) @@ -242,7 +377,7 @@ func (s *Service) AddSymmetric(chatID string) (*Filter, error) { f := &whisper.Filter{ KeySym: symKey, - PoW: 0.002, + PoW: minPow, AllowP2P: true, Topics: topics, Messages: s.whisper.NewMessageStore(), @@ -256,16 +391,17 @@ func (s *Service) AddSymmetric(chatID string) (*Filter, error) { return &Filter{ FilterID: id, SymKeyID: symKeyID, - Topic: topic, + Topic: whisper.BytesToTopic(topic), }, nil } -func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, listen bool) (*Filter, error) { +// addAsymmetricFilter adds a filter with our privatekey, and set minPow according to the listen parameter +func (s *Service) addAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, listen bool) (*Filter, error) { var err error var pow float64 if listen { - pow = 0.002 + pow = minPow } else { // Set high pow so we discard messages pow = 1 @@ -287,73 +423,19 @@ func (s *Service) AddAsymmetricFilter(keyAsym *ecdsa.PrivateKey, chatID string, return nil, err } - return &Filter{FilterID: id, Topic: topic}, nil -} - -func (s *Service) LoadPublic(chat *Chat) error { - s.mutex.Lock() - defer s.mutex.Unlock() - - filterAndTopic, err := s.AddSymmetric(chat.ChatID) - if err != nil { - return err - } - - // Add mutex - chat.FilterID = filterAndTopic.FilterID - chat.SymKeyID = filterAndTopic.SymKeyID - chat.Topic = filterAndTopic.Topic - s.chats[chat.ChatID] = chat - return nil -} - -func (s *Service) Load(myKey *ecdsa.PrivateKey, chat *Chat) error { - var err error - log.Debug("Loading chat", "chatID", chat.ChatID) - - // Check we haven't already loaded the chat - if _, ok := s.chats[chat.ChatID]; !ok { - if chat.OneToOne { - err = s.LoadOneToOne(myKey, chat.Identity, false) - - } else { - err = s.LoadPublic(chat) - } - if err != nil { - return err - } - - } - return nil + return &Filter{FilterID: id, Topic: whisper.BytesToTopic(topic)}, nil } func negotiatedID(identity *ecdsa.PublicKey) string { return fmt.Sprintf("%x-negotiated", crypto.FromECDSAPub(identity)) } -func (s *Service) Get(identity *ecdsa.PublicKey) *Chat { - return s.chats[negotiatedID(identity)] -} - -func (s *Service) ProcessNegotiatedSecret(secret *topic.Secret) error { - s.mutex.Lock() - defer s.mutex.Unlock() - - keyString := fmt.Sprintf("%x", secret.Key) - filter, err := s.AddSymmetric(keyString) - if err != nil { - return err - } +func (s *Service) load(myKey *ecdsa.PrivateKey, chat *Chat) ([]*Chat, error) { + log.Debug("Loading chat", "chatID", chat.ChatID) - identityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(secret.Identity)) + if chat.OneToOne { + return s.loadOneToOne(myKey, chat.Identity, false) - chat := &Chat{ - ChatID: negotiatedID(secret.Identity), - Topic: filter.Topic, - SymKeyID: filter.SymKeyID, - Identity: identityStr, } - - s.chats[chat.ChatID] = chat - return nil + return []*Chat{chat}, s.loadPublic(chat) } diff --git a/services/shhext/filter/service_test.go b/services/shhext/filter/service_test.go index 55968933cf..f840cd8bbf 100644 --- a/services/shhext/filter/service_test.go +++ b/services/shhext/filter/service_test.go @@ -84,19 +84,22 @@ func (s *ServiceTestSuite) TestDiscoveryAndPartitionedTopic() { partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[0].partitionedTopic) contactCodeTopic := s.keys[0].PublicKeyString() + "-contact-code" - err := s.service.Init(chats) + _, err := s.service.Init(chats) s.Require().NoError(err) s.Require().Equal(3, len(s.service.chats), "It creates two filters") discoveryFilter := s.service.chats[discoveryTopic] s.Require().NotNil(discoveryFilter, "It adds the discovery filter") + s.Require().True(discoveryFilter.Listen) contactCodeFilter := s.service.chats[contactCodeTopic] s.Require().NotNil(contactCodeFilter, "It adds the contact code filter") + s.Require().True(contactCodeFilter.Listen) partitionedFilter := s.service.chats[partitionedTopic] s.Require().NotNil(partitionedFilter, "It adds the partitioned filter") + s.Require().True(partitionedFilter.Listen) } func (s *ServiceTestSuite) TestPublicAndOneToOneChats() { @@ -110,23 +113,21 @@ func (s *ServiceTestSuite) TestPublicAndOneToOneChats() { OneToOne: true, }, } - partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[1].partitionedTopic) contactCodeTopic := s.keys[1].PublicKeyString() + "-contact-code" - err := s.service.Init(chats) + _, err := s.service.Init(chats) s.Require().NoError(err) - s.Require().Equal(6, len(s.service.chats), "It creates two additional filters for the one to one and one for the public chat") + s.Require().Equal(5, len(s.service.chats), "It creates two additional filters for the one to one and one for the public chat") statusFilter := s.service.chats["status"] s.Require().NotNil(statusFilter, "It creates a filter for the public chat") s.Require().NotNil(statusFilter.SymKeyID, "It returns a sym key id") + s.Require().True(statusFilter.Listen) contactCodeFilter := s.service.chats[contactCodeTopic] s.Require().NotNil(contactCodeFilter, "It adds the contact code filter") - - partitionedFilter := s.service.chats[partitionedTopic] - s.Require().NotNil(partitionedFilter, "It adds the partitioned filter") + s.Require().True(contactCodeFilter.Listen) } func (s *ServiceTestSuite) TestNegotiatedTopic() { @@ -143,7 +144,7 @@ func (s *ServiceTestSuite) TestNegotiatedTopic() { _, _, err = s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[1].privateKey.PublicKey, []string{"0-2"}) s.Require().NoError(err) - err = s.service.Init(chats) + _, err = s.service.Init(chats) s.Require().NoError(err) s.Require().Equal(5, len(s.service.chats), "It creates two additional filters for the negotiated topics") diff --git a/services/shhext/service.go b/services/shhext/service.go index 84d1f44696..eecd7bbb41 100644 --- a/services/shhext/service.go +++ b/services/shhext/service.go @@ -321,26 +321,40 @@ func (s *Service) GetNegotiatedChat(identity *ecdsa.PublicKey) *filter.Chat { return s.filter.Get(identity) } -func (s *Service) LoadFilters(chats []*filter.Chat) error { +func (s *Service) LoadFilters(chats []*filter.Chat) ([]*filter.Chat, error) { return s.filter.Init(chats) } -func (s *Service) RemoveFilter(chat *filter.Chat) { - // remove filter +func (s *Service) LoadFilter(chat *filter.Chat) ([]*filter.Chat, error) { + return s.filter.Load(chat) +} + +func (s *Service) RemoveFilter(chat *filter.Chat) error { + return s.filter.Remove(chat) } func (s *Service) onNewTopicHandler(sharedSecrets []*topic.Secret) { var filters []*signal.Filter log.Info("NEW TOPIC HANDLER", "secrets", sharedSecrets) for _, sharedSecret := range sharedSecrets { - err := s.filter.ProcessNegotiatedSecret(sharedSecret) + chat, err := s.filter.ProcessNegotiatedSecret(sharedSecret) if err != nil { log.Error("Failed to process negotiated secret", "err", err) return } + filter := &signal.Filter{ + ChatID: chat.ChatID, + SymKeyID: chat.SymKeyID, + Listen: chat.Listen, + FilterID: chat.FilterID, + Identity: chat.Identity, + Topic: chat.Topic, + } + + filters = append(filters, filter) + } - // TODO: send back chat filter log.Info("FILTER IDS", "filter", filters) if len(filters) != 0 { log.Info("SENDING FILTERS") diff --git a/signal/events_shhext.go b/signal/events_shhext.go index 62c21acfef..8e4f93897d 100644 --- a/signal/events_shhext.go +++ b/signal/events_shhext.go @@ -63,11 +63,18 @@ type BundleAddedSignal struct { } type Filter struct { - Identity string `json:"identity"` - FilterID string `json:"filterId"` - SymKeyID string `json:"symKeyId"` - ChatID string `json:"chatId"` - Topic whisper.TopicType `json:"topic"` + // ChatID is the identifier of the chat + ChatID string `json:"chatId"` + // SymKeyID is the symmetric key id used for symmetric chats + SymKeyID string `json:"symKeyId"` + // OneToOne tells us if we need to use asymmetric encryption for this chat + Listen bool `json:"listen"` + // FilterID the whisper filter id generated + FilterID string `json:"filterId"` + // Identity is the public key of the other recipient for non-public chats + Identity string `json:"identity"` + // Topic is the whisper topic + Topic whisper.TopicType `json:"topic"` } type WhisperFilterAddedSignal struct {