Add support for partitioned topic
This commit is contained in:
parent
78ed35d2fe
commit
1aa3e2812a
2
Makefile
2
Makefile
|
@ -172,7 +172,7 @@ setup: setup-build setup-dev tidy ##@other Prepare project for development and b
|
|||
|
||||
generate: ##@other Regenerate assets and other auto-generated stuff
|
||||
go generate ./static ./static/chat_db_migrations ./static/mailserver_db_migrations ./t
|
||||
$(shell cd ./services/shhext/chat && exec protoc --go_out=. ./*.proto)
|
||||
$(shell cd ./services/shhext/chat/protobuf && exec protoc --go_out=. ./*.proto)
|
||||
|
||||
prepare-release: clean-release
|
||||
mkdir -p $(RELEASE_DIR)
|
||||
|
|
118
api/backend.go
118
api/backend.go
|
@ -2,7 +2,6 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -26,8 +25,8 @@ import (
|
|||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"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"
|
||||
|
@ -645,91 +644,6 @@ func appendIf(condition bool, services []gethnode.ServiceConstructor, service ge
|
|||
return append(services, service)
|
||||
}
|
||||
|
||||
// CreateContactCode create or return the latest contact code
|
||||
func (b *StatusBackend) CreateContactCode() (string, error) {
|
||||
selectedChatAccount, err := b.AccountManager().SelectedChatAccount()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
st, err := b.statusNode.ShhExtService()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bundle, err := st.GetBundle(selectedChatAccount.AccountKey.PrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bundle.ToBase64()
|
||||
}
|
||||
|
||||
// GetContactCode return the latest contact code
|
||||
func (b *StatusBackend) GetContactCode(identity string) (string, error) {
|
||||
st, err := b.statusNode.ShhExtService()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicKeyBytes, err := hex.DecodeString(identity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicKey, err := ethcrypto.UnmarshalPubkey(publicKeyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bundle, err := st.GetPublicBundle(publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bundle == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return bundle.ToBase64()
|
||||
}
|
||||
|
||||
// ProcessContactCode process and adds the someone else's bundle
|
||||
func (b *StatusBackend) ProcessContactCode(contactCode string) error {
|
||||
selectedChatAccount, err := b.AccountManager().SelectedChatAccount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
st, err := b.statusNode.ShhExtService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bundle, err := chat.FromBase64(contactCode)
|
||||
if err != nil {
|
||||
b.log.Error("error decoding base64", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := st.ProcessPublicBundle(selectedChatAccount.AccountKey.PrivateKey, bundle); err != nil {
|
||||
b.log.Error("error adding bundle", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractIdentityFromContactCode extract the identity of the user generating the contact code
|
||||
func (b *StatusBackend) ExtractIdentityFromContactCode(contactCode string) (string, error) {
|
||||
bundle, err := chat.FromBase64(contactCode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return chat.ExtractIdentity(bundle)
|
||||
}
|
||||
|
||||
// ExtractGroupMembershipSignatures extract signatures from tuples of content/signature
|
||||
func (b *StatusBackend) ExtractGroupMembershipSignatures(signaturePairs [][2]string) ([]string, error) {
|
||||
return crypto.ExtractSignatures(signaturePairs)
|
||||
|
@ -745,6 +659,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()
|
||||
|
|
|
@ -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"
|
||||
|
@ -51,40 +52,23 @@ func StopNode() *C.char {
|
|||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
// Create an X3DH bundle
|
||||
//export CreateContactCode
|
||||
func CreateContactCode() *C.char {
|
||||
bundle, err := statusBackend.CreateContactCode()
|
||||
if err != nil {
|
||||
// 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)
|
||||
}
|
||||
|
||||
cstr := C.CString(bundle)
|
||||
|
||||
return cstr
|
||||
}
|
||||
|
||||
//export ProcessContactCode
|
||||
func ProcessContactCode(bundleString *C.char) *C.char {
|
||||
err := statusBackend.ProcessContactCode(C.GoString(bundleString))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get an X3DH bundle
|
||||
//export GetContactCode
|
||||
func GetContactCode(identityString *C.char) *C.char {
|
||||
bundle, err := statusBackend.GetContactCode(C.GoString(identityString))
|
||||
response, err := statusBackend.LoadFilters(chats)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(struct {
|
||||
ContactCode string `json:"code"`
|
||||
}{ContactCode: bundle})
|
||||
Chats []*filter.Chat `json:"result"`
|
||||
}{Chats: response})
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
@ -92,22 +76,48 @@ func GetContactCode(identityString *C.char) *C.char {
|
|||
return C.CString(string(data))
|
||||
}
|
||||
|
||||
//export ExtractIdentityFromContactCode
|
||||
func ExtractIdentityFromContactCode(bundleString *C.char) *C.char {
|
||||
bundle := C.GoString(bundleString)
|
||||
// LoadFilter load a whisper filter
|
||||
//export LoadFilter
|
||||
func LoadFilter(chatStr *C.char) *C.char {
|
||||
var chat *filter.Chat
|
||||
|
||||
identity, err := statusBackend.ExtractIdentityFromContactCode(bundle)
|
||||
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)
|
||||
}
|
||||
|
||||
if err := statusBackend.ProcessContactCode(bundle); err != nil {
|
||||
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 {
|
||||
Identity string `json:"identity"`
|
||||
}{Identity: identity})
|
||||
Response string `json:"response"`
|
||||
}{Response: "ok"})
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
|
131
mobile/status.go
131
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"
|
||||
|
@ -64,48 +65,6 @@ func StopNode() string {
|
|||
return makeJSONResponse(nil)
|
||||
}
|
||||
|
||||
// CreateContactCode creates an X3DH bundle.
|
||||
func CreateContactCode() string {
|
||||
bundle, err := statusBackend.CreateContactCode()
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
// ProcessContactCode processes an X3DH bundle.
|
||||
// TODO(adam): it looks like the return should be error.
|
||||
func ProcessContactCode(bundle string) string {
|
||||
err := statusBackend.ProcessContactCode(bundle)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtractIdentityFromContactCode extracts an identity from an X3DH bundle.
|
||||
func ExtractIdentityFromContactCode(bundle string) string {
|
||||
identity, err := statusBackend.ExtractIdentityFromContactCode(bundle)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
if err := statusBackend.ProcessContactCode(bundle); err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(struct {
|
||||
Identity string `json:"identity"`
|
||||
}{Identity: identity})
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// ExtractGroupMembershipSignatures extract public keys from tuples of content/signature.
|
||||
func ExtractGroupMembershipSignatures(signaturePairsStr string) string {
|
||||
var signaturePairs [][2]string
|
||||
|
@ -617,24 +576,6 @@ func SetSignalEventCallback(cb unsafe.Pointer) {
|
|||
signal.SetSignalEventCallback(cb)
|
||||
}
|
||||
|
||||
// Get an X3DH bundle
|
||||
//export GetContactCode
|
||||
func GetContactCode(identity string) string {
|
||||
bundle, err := statusBackend.GetContactCode(identity)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(struct {
|
||||
ContactCode string `json:"code"`
|
||||
}{ContactCode: bundle})
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// ExportNodeLogs reads current node log and returns content to a caller.
|
||||
//export ExportNodeLogs
|
||||
func ExportNodeLogs() string {
|
||||
|
@ -673,3 +614,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)
|
||||
}
|
||||
|
|
|
@ -368,8 +368,6 @@ type WalletConfig struct {
|
|||
|
||||
// ShhextConfig defines options used by shhext service.
|
||||
type ShhextConfig struct {
|
||||
// AsymKeyID the key id of the selected account
|
||||
AsymKeyID string
|
||||
PFSEnabled bool
|
||||
// BackupDisabledDataDir is the file system folder the node should use for any data storage needs that it doesn't want backed up.
|
||||
BackupDisabledDataDir string
|
||||
|
|
|
@ -9,15 +9,12 @@ import (
|
|||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
||||
"github.com/status-im/status-go/db"
|
||||
"github.com/status-im/status-go/mailserver"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
|
@ -424,15 +421,13 @@ func (api *PublicAPI) GetNewFilterMessages(filterID string) ([]dedup.Deduplicate
|
|||
|
||||
dedupMessages := api.service.deduplicator.Deduplicate(msgs)
|
||||
|
||||
if api.service.pfsEnabled {
|
||||
// Attempt to decrypt message, otherwise leave unchanged
|
||||
for _, dedupMessage := range dedupMessages {
|
||||
|
||||
if err := api.processPFSMessage(dedupMessage); err != nil {
|
||||
if err := api.service.ProcessMessage(dedupMessage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dedupMessages, nil
|
||||
}
|
||||
|
@ -462,7 +457,7 @@ func (api *PublicAPI) ConfirmMessagesProcessed(messages []*whisper.Message) (err
|
|||
// ConfirmMessagesProcessedByID is a method to confirm that messages was consumed by
|
||||
// the client side.
|
||||
func (api *PublicAPI) ConfirmMessagesProcessedByID(messageIDs [][]byte) error {
|
||||
if err := api.service.protocol.ConfirmMessagesProcessed(messageIDs); err != nil {
|
||||
if err := api.service.ConfirmMessagesProcessed(messageIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -471,97 +466,12 @@ func (api *PublicAPI) ConfirmMessagesProcessedByID(messageIDs [][]byte) error {
|
|||
|
||||
// SendPublicMessage sends a public chat message to the underlying transport
|
||||
func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg chat.SendPublicMessageRPC) (hexutil.Bytes, error) {
|
||||
privateKey, err := api.service.w.GetPrivateKey(msg.Sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is transport layer agnostic
|
||||
protocolMessage, err := api.service.protocol.BuildPublicMessage(privateKey, msg.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
symKeyID, err := api.service.w.AddSymKeyFromPassword(msg.Chat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// marshal for sending to wire
|
||||
marshaledMessage, err := proto.Marshal(protocolMessage)
|
||||
if err != nil {
|
||||
api.log.Error("encryption-service", "error marshaling message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enrich with transport layer info
|
||||
whisperMessage := chat.PublicMessageToWhisper(msg, marshaledMessage)
|
||||
whisperMessage.SymKeyID = symKeyID
|
||||
|
||||
// And dispatch
|
||||
return api.Post(ctx, whisperMessage)
|
||||
return api.service.SendPublicMessage(ctx, msg)
|
||||
}
|
||||
|
||||
// SendDirectMessage sends a 1:1 chat message to the underlying transport
|
||||
func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg chat.SendDirectMessageRPC) (hexutil.Bytes, error) {
|
||||
if !api.service.pfsEnabled {
|
||||
return nil, ErrPFSNotEnabled
|
||||
}
|
||||
// To be completely agnostic from whisper we should not be using whisper to store the key
|
||||
privateKey, err := api.service.w.GetPrivateKey(msg.Sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(msg.PubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is transport layer-agnostic
|
||||
var protocolMessage *chat.ProtocolMessage
|
||||
// The negotiated secret
|
||||
var topic []byte
|
||||
|
||||
api.log.Info("BUILDING MESSAGE")
|
||||
if msg.DH {
|
||||
protocolMessage, topic, err = api.service.protocol.BuildDHMessage(privateKey, &privateKey.PublicKey, msg.Payload)
|
||||
} else {
|
||||
protocolMessage, topic, err = api.service.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload)
|
||||
}
|
||||
|
||||
api.log.Info("BUILT MESSAGE", "topic", topic)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// marshal for sending to wire
|
||||
marshaledMessage, err := proto.Marshal(protocolMessage)
|
||||
if err != nil {
|
||||
api.log.Error("encryption-service", "error marshaling message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Refactor this as it's not quite the right abstraction anymore
|
||||
whisperMessage := chat.DirectMessageToWhisper(msg, marshaledMessage, topic)
|
||||
// Enrich with transport layer info
|
||||
if topic != nil {
|
||||
api.log.Info("GETTING SYM KEY", "symkey", api.service.GetNegotiatedChat(publicKey))
|
||||
|
||||
chat := api.service.GetNegotiatedChat(publicKey)
|
||||
|
||||
if chat != nil {
|
||||
whisperMessage.SymKeyID = chat.SymKeyID
|
||||
whisperMessage.Topic = whisper.BytesToTopic(chat.Topic)
|
||||
whisperMessage.PublicKey = nil
|
||||
}
|
||||
}
|
||||
|
||||
api.log.Info("WHISPER MESSAGE", "message", whisperMessage)
|
||||
|
||||
// And dispatch
|
||||
return api.Post(ctx, whisperMessage)
|
||||
return api.service.SendDirectMessage(ctx, msg)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) requestMessagesUsingPayload(request db.HistoryRequest, peer, symkeyID string, payload []byte, force bool, timeout time.Duration, topics []whisper.TopicType) (hash common.Hash, err error) {
|
||||
|
@ -672,54 +582,6 @@ func (api *PublicAPI) CompleteRequest(parent context.Context, hex string) (err e
|
|||
return err
|
||||
}
|
||||
|
||||
func (api *PublicAPI) processPFSMessage(dedupMessage dedup.DeduplicateMessage) error {
|
||||
msg := dedupMessage.Message
|
||||
|
||||
privateKeyID := api.service.w.SelectedKeyPairID()
|
||||
if privateKeyID == "" {
|
||||
return errors.New("no key selected")
|
||||
}
|
||||
|
||||
privateKey, err := api.service.w.GetPrivateKey(privateKeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(msg.Sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshal message
|
||||
protocolMessage := &chat.ProtocolMessage{}
|
||||
|
||||
if err := proto.Unmarshal(msg.Payload, protocolMessage); err != nil {
|
||||
api.log.Debug("Not a protocol message", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := api.service.protocol.HandleMessage(privateKey, publicKey, protocolMessage, dedupMessage.DedupID)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
// Set the decrypted payload
|
||||
msg.Payload = response
|
||||
case chat.ErrDeviceNotFound:
|
||||
// Notify that someone tried to contact us using an invalid bundle
|
||||
if privateKey.PublicKey != *publicKey {
|
||||
api.log.Warn("Device not found, sending signal", "err", err)
|
||||
keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey))
|
||||
handler := EnvelopeSignalHandler{}
|
||||
handler.DecryptMessageFailed(keyString)
|
||||
}
|
||||
default:
|
||||
// Log and pass to the client, even if failed to decrypt
|
||||
api.log.Error("Failed handling message with error", "err", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -----
|
||||
// HELPER
|
||||
// -----
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
// 1540715431_add_version.up.sql
|
||||
// 1541164797_add_installations.down.sql
|
||||
// 1541164797_add_installations.up.sql
|
||||
// 1558084410_add_topic.down.sql
|
||||
// 1558084410_add_topic.up.sql
|
||||
// 1558084410_add_secret.down.sql
|
||||
// 1558084410_add_secret.up.sql
|
||||
// 1558588866_add_version.up.sql
|
||||
// static.go
|
||||
// DO NOT EDIT!
|
||||
|
@ -239,42 +239,42 @@ func _1541164797_add_installationsUpSql() (*asset, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
var __1558084410_add_topicDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xc9\x2f\xc8\x4c\x8e\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x8b\xcf\x4c\x29\xb6\xe6\x42\x57\x52\x6c\xcd\x05\x08\x00\x00\xff\xff\xf0\xe3\x8a\xc7\x36\x00\x00\x00")
|
||||
var __1558084410_add_secretDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x4e\x4d\x2e\x4a\x2d\x89\xcf\xcc\x2b\x2e\x49\xcc\xc9\x49\x2c\xc9\xcc\xcf\x8b\xcf\x4c\x29\xb6\xe6\xc2\x50\x53\x6c\xcd\x05\x08\x00\x00\xff\xff\xd3\xcd\x41\x83\x38\x00\x00\x00")
|
||||
|
||||
func _1558084410_add_topicDownSqlBytes() ([]byte, error) {
|
||||
func _1558084410_add_secretDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558084410_add_topicDownSql,
|
||||
"1558084410_add_topic.down.sql",
|
||||
__1558084410_add_secretDownSql,
|
||||
"1558084410_add_secret.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558084410_add_topicDownSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_topicDownSqlBytes()
|
||||
func _1558084410_add_secretDownSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_secretDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558084410_add_topic.down.sql", size: 54, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
info := bindataFileInfo{name: "1558084410_add_secret.down.sql", size: 56, mode: os.FileMode(420), modTime: time.Unix(1560418252, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __1558084410_add_topicUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x90\x41\x6b\x85\x30\x10\x84\xef\xf9\x15\x73\x54\xf0\x1f\xf4\xa4\x61\x95\xd0\x74\xd3\xa6\x11\xea\x49\xc4\x78\x58\x10\x2d\x35\x97\xfe\xfb\x62\x79\x4f\x94\xc7\x3b\xcf\xcc\xce\x7c\xab\x3d\x95\x81\x10\xca\xca\x12\xd2\xfa\x2d\xe3\x86\x4c\x01\x12\xa7\x25\x49\xfa\x45\x65\x5d\x05\x76\x01\xdc\x5a\x8b\x77\x6f\xde\x4a\xdf\xe1\x95\x3a\x38\x86\x76\x5c\x5b\xa3\x03\x4c\xc3\xce\x53\xa1\x80\x6d\x1a\x7f\xa6\x74\x8d\xa9\xfc\x45\xa9\xc7\xa6\x5e\x96\x2d\x0d\xf3\x3c\x24\x59\x97\x5e\xe2\xbd\x19\x81\xbe\xc2\x11\x2e\x4e\x6b\x7a\x89\xd7\xcb\xbb\xd8\xb2\xf9\x68\x29\x93\x58\x9c\x7d\xf9\x93\x7d\xb5\xf3\x64\x1a\xfe\x27\xc8\x2e\x7e\x4f\x35\x79\x62\x4d\x9f\xb7\x47\x1c\x72\xbe\x03\xfc\x05\x00\x00\xff\xff\xf3\xa6\x3d\xc3\x2a\x01\x00\x00")
|
||||
var __1558084410_add_secretUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x50\xcf\x0a\x82\x30\x1c\xbe\xef\x29\xbe\xa3\x82\x6f\xd0\x49\xc7\x4f\x19\xad\xdf\x6a\x4d\xc8\x93\x48\xf3\x30\x10\x83\xdc\xa5\xb7\x0f\x23\x45\xa1\xce\xdf\xff\x4f\x5a\xca\x1d\xc1\xe5\x85\x26\x4c\xfd\xfd\xd9\xc7\x09\x89\x00\x82\xef\xc7\x18\xe2\x0b\x85\x36\x05\xd8\x38\x70\xad\x35\xce\x56\x9d\x72\xdb\xe0\x48\x0d\x0c\x43\x1a\x2e\xb5\x92\x0e\xaa\x62\x63\x29\x13\xf8\x9a\xec\x65\x22\x3d\x08\xf1\x23\xaa\x0d\xe3\x14\xbb\x61\xe8\x62\x78\x8c\x6d\xf0\x4b\x34\x1c\xdd\xdc\xaa\xce\x36\x75\xda\xe0\xf7\xd6\x33\x58\xb3\xba\xd4\x94\x04\x9f\x6d\x79\xe9\x9f\x82\xa5\xb1\xa4\x2a\xfe\x4c\x48\x76\x7c\x4b\x25\x59\x62\x49\xd7\xe5\x8a\x15\x4f\xe7\x09\xef\x00\x00\x00\xff\xff\xa6\xbb\x2c\x23\x2d\x01\x00\x00")
|
||||
|
||||
func _1558084410_add_topicUpSqlBytes() ([]byte, error) {
|
||||
func _1558084410_add_secretUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__1558084410_add_topicUpSql,
|
||||
"1558084410_add_topic.up.sql",
|
||||
__1558084410_add_secretUpSql,
|
||||
"1558084410_add_secret.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _1558084410_add_topicUpSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_topicUpSqlBytes()
|
||||
func _1558084410_add_secretUpSql() (*asset, error) {
|
||||
bytes, err := _1558084410_add_secretUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558084410_add_topic.up.sql", size: 298, mode: os.FileMode(420), modTime: time.Unix(1560418030, 0)}
|
||||
info := bindataFileInfo{name: "1558084410_add_secret.up.sql", size: 301, mode: os.FileMode(420), modTime: time.Unix(1560418252, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ func _1558588866_add_versionUpSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "1558588866_add_version.up.sql", size: 57, mode: os.FileMode(420), modTime: time.Unix(1558588995, 0)}
|
||||
info := bindataFileInfo{name: "1558588866_add_version.up.sql", size: 57, mode: os.FileMode(420), modTime: time.Unix(1560418251, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -379,8 +379,8 @@ var _bindata = map[string]func() (*asset, error){
|
|||
"1540715431_add_version.up.sql": _1540715431_add_versionUpSql,
|
||||
"1541164797_add_installations.down.sql": _1541164797_add_installationsDownSql,
|
||||
"1541164797_add_installations.up.sql": _1541164797_add_installationsUpSql,
|
||||
"1558084410_add_topic.down.sql": _1558084410_add_topicDownSql,
|
||||
"1558084410_add_topic.up.sql": _1558084410_add_topicUpSql,
|
||||
"1558084410_add_secret.down.sql": _1558084410_add_secretDownSql,
|
||||
"1558084410_add_secret.up.sql": _1558084410_add_secretUpSql,
|
||||
"1558588866_add_version.up.sql": _1558588866_add_versionUpSql,
|
||||
"static.go": staticGo,
|
||||
}
|
||||
|
@ -433,8 +433,8 @@ var _bintree = &bintree{nil, map[string]*bintree{
|
|||
"1540715431_add_version.up.sql": &bintree{_1540715431_add_versionUpSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.down.sql": &bintree{_1541164797_add_installationsDownSql, map[string]*bintree{}},
|
||||
"1541164797_add_installations.up.sql": &bintree{_1541164797_add_installationsUpSql, map[string]*bintree{}},
|
||||
"1558084410_add_topic.down.sql": &bintree{_1558084410_add_topicDownSql, map[string]*bintree{}},
|
||||
"1558084410_add_topic.up.sql": &bintree{_1558084410_add_topicUpSql, map[string]*bintree{}},
|
||||
"1558084410_add_secret.down.sql": &bintree{_1558084410_add_secretDownSql, map[string]*bintree{}},
|
||||
"1558084410_add_secret.up.sql": &bintree{_1558084410_add_secretUpSql, map[string]*bintree{}},
|
||||
"1558588866_add_version.up.sql": &bintree{_1558588866_add_versionUpSql, map[string]*bintree{}},
|
||||
"static.go": &bintree{staticGo, map[string]*bintree{}},
|
||||
}}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -15,6 +13,8 @@ import (
|
|||
dr "github.com/status-im/doubleratchet"
|
||||
|
||||
"github.com/status-im/status-go/services/shhext/chat/crypto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
)
|
||||
|
||||
var ErrSessionNotFound = errors.New("session not found")
|
||||
|
@ -51,8 +51,6 @@ type EncryptionServiceConfig struct {
|
|||
BundleRefreshInterval int64
|
||||
}
|
||||
|
||||
type IdentityAndIDPair [2]string
|
||||
|
||||
// DefaultEncryptionServiceConfig returns the default values used by the encryption service
|
||||
func DefaultEncryptionServiceConfig(installationID string) EncryptionServiceConfig {
|
||||
return EncryptionServiceConfig{
|
||||
|
@ -132,19 +130,9 @@ func (s *EncryptionService) ConfirmMessagesProcessed(messageIDs [][]byte) error
|
|||
}
|
||||
|
||||
// CreateBundle retrieves or creates an X3DH bundle given a private key
|
||||
func (s *EncryptionService) CreateBundle(privateKey *ecdsa.PrivateKey) (*Bundle, error) {
|
||||
func (s *EncryptionService) CreateBundle(privateKey *ecdsa.PrivateKey, installations []*multidevice.Installation) (*protobuf.Bundle, error) {
|
||||
ourIdentityKeyC := ecrypto.CompressPubkey(&privateKey.PublicKey)
|
||||
|
||||
installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations-1, ourIdentityKeyC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
installations = append(installations, &Installation{
|
||||
ID: s.config.InstallationID,
|
||||
Version: protocolCurrentVersion,
|
||||
})
|
||||
|
||||
bundleContainer, err := s.persistence.GetAnyPrivateBundle(ourIdentityKeyC, installations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -176,7 +164,7 @@ func (s *EncryptionService) CreateBundle(privateKey *ecdsa.PrivateKey) (*Bundle,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return s.CreateBundle(privateKey)
|
||||
return s.CreateBundle(privateKey, installations)
|
||||
}
|
||||
|
||||
// DecryptWithDH decrypts message sent with a DH key exchange, and throws away the key after decryption
|
||||
|
@ -224,55 +212,13 @@ func (s *EncryptionService) keyFromPassiveX3DH(myIdentityKey *ecdsa.PrivateKey,
|
|||
return key, nil
|
||||
}
|
||||
|
||||
func (s *EncryptionService) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
myIdentityKeyC := ecrypto.CompressPubkey(myIdentityKey)
|
||||
return s.persistence.EnableInstallation(myIdentityKeyC, installationID)
|
||||
}
|
||||
|
||||
func (s *EncryptionService) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
myIdentityKeyC := ecrypto.CompressPubkey(myIdentityKey)
|
||||
return s.persistence.DisableInstallation(myIdentityKeyC, installationID)
|
||||
}
|
||||
|
||||
// ProcessPublicBundle persists a bundle and returns a list of tuples identity/installationID
|
||||
func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *Bundle) ([]IdentityAndIDPair, error) {
|
||||
// Make sure the bundle belongs to who signed it
|
||||
identity, err := ExtractIdentity(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedPreKeys := b.GetSignedPreKeys()
|
||||
var response []IdentityAndIDPair
|
||||
var installations []*Installation
|
||||
myIdentityStr := fmt.Sprintf("0x%x", ecrypto.FromECDSAPub(&myIdentityKey.PublicKey))
|
||||
|
||||
// Any device from other peers will be considered enabled, ours needs to
|
||||
// be explicitly enabled
|
||||
fromOurIdentity := identity != myIdentityStr
|
||||
|
||||
for installationID, signedPreKey := range signedPreKeys {
|
||||
if installationID != s.config.InstallationID {
|
||||
installations = append(installations, &Installation{
|
||||
ID: installationID,
|
||||
Version: signedPreKey.GetProtocolVersion(),
|
||||
})
|
||||
response = append(response, IdentityAndIDPair{identity, installationID})
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installations, fromOurIdentity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = s.persistence.AddPublicBundle(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
// ProcessPublicBundle persists a bundle
|
||||
func (s *EncryptionService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, b *protobuf.Bundle) error {
|
||||
return s.persistence.AddPublicBundle(b)
|
||||
}
|
||||
|
||||
// DecryptPayload decrypts the payload of a DirectMessageProtocol, given an identity private key and the sender's public key
|
||||
func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey, theirInstallationID string, msgs map[string]*DirectMessageProtocol, messageID []byte) ([]byte, error) {
|
||||
func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentityKey *ecdsa.PublicKey, theirInstallationID string, msgs map[string]*protobuf.DirectMessageProtocol, messageID []byte) ([]byte, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
|
@ -328,11 +274,6 @@ func (s *EncryptionService) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, thei
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Add installations with a timestamp of 0, as we don't have bundle informations
|
||||
if err = s.persistence.AddInstallations(theirIdentityKeyC, 0, []*Installation{{ID: theirInstallationID, Version: 0}}, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We mark the exchange as successful so we stop sending x3dh header
|
||||
if err = s.persistence.RatchetInfoConfirmed(drHeader.GetId(), theirIdentityKeyC, theirInstallationID); err != nil {
|
||||
s.log.Error("Could not confirm ratchet info", "err", err)
|
||||
|
@ -396,7 +337,7 @@ func (s *EncryptionService) createNewSession(drInfo *RatchetInfo, sk [32]byte, k
|
|||
return session, err
|
||||
}
|
||||
|
||||
func (s *EncryptionService) encryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *RatchetInfo, payload []byte) ([]byte, *DRHeader, error) {
|
||||
func (s *EncryptionService) encryptUsingDR(theirIdentityKey *ecdsa.PublicKey, drInfo *RatchetInfo, payload []byte) ([]byte, *protobuf.DRHeader, error) {
|
||||
var err error
|
||||
|
||||
var session dr.Session
|
||||
|
@ -430,7 +371,7 @@ func (s *EncryptionService) encryptUsingDR(theirIdentityKey *ecdsa.PublicKey, dr
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
header := &DRHeader{
|
||||
header := &protobuf.DRHeader{
|
||||
Id: drInfo.BundleID,
|
||||
Key: response.Header.DH[:],
|
||||
N: response.Header.N,
|
||||
|
@ -474,7 +415,7 @@ func (s *EncryptionService) decryptUsingDR(theirIdentityKey *ecdsa.PublicKey, dr
|
|||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (s *EncryptionService) encryptWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (*DirectMessageProtocol, error) {
|
||||
func (s *EncryptionService) encryptWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (*protobuf.DirectMessageProtocol, error) {
|
||||
symmetricKey, ourEphemeralKey, err := PerformActiveDH(theirIdentityKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -485,16 +426,16 @@ func (s *EncryptionService) encryptWithDH(theirIdentityKey *ecdsa.PublicKey, pay
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &DirectMessageProtocol{
|
||||
DHHeader: &DHHeader{
|
||||
return &protobuf.DirectMessageProtocol{
|
||||
DHHeader: &protobuf.DHHeader{
|
||||
Key: ecrypto.CompressPubkey(ourEphemeralKey),
|
||||
},
|
||||
Payload: encryptedPayload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *EncryptionService) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (map[string]*DirectMessageProtocol, error) {
|
||||
response := make(map[string]*DirectMessageProtocol)
|
||||
func (s *EncryptionService) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicKey, payload []byte) (map[string]*protobuf.DirectMessageProtocol, error) {
|
||||
response := make(map[string]*protobuf.DirectMessageProtocol)
|
||||
dmp, err := s.encryptWithDH(theirIdentityKey, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -505,21 +446,15 @@ func (s *EncryptionService) EncryptPayloadWithDH(theirIdentityKey *ecdsa.PublicK
|
|||
}
|
||||
|
||||
// GetPublicBundle returns the active installations bundles for a given user
|
||||
func (s *EncryptionService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*Bundle, error) {
|
||||
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
|
||||
|
||||
installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *EncryptionService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey, installations []*multidevice.Installation) (*protobuf.Bundle, error) {
|
||||
return s.persistence.GetPublicBundle(theirIdentityKey, installations)
|
||||
}
|
||||
|
||||
// EncryptPayload returns a new DirectMessageProtocol with a given payload encrypted, given a recipient's public key and the sender private identity key
|
||||
// TODO: refactor this
|
||||
// nolint: gocyclo
|
||||
func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, myIdentityKey *ecdsa.PrivateKey, payload []byte) (map[string]*DirectMessageProtocol, error) {
|
||||
func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, myIdentityKey *ecdsa.PrivateKey, installations []*multidevice.Installation, payload []byte) (map[string]*protobuf.DirectMessageProtocol, []*multidevice.Installation, error) {
|
||||
// Which installations we are sending the message to
|
||||
var targetedInstallations []*multidevice.Installation
|
||||
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
|
@ -527,18 +462,13 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
|||
|
||||
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
|
||||
|
||||
// Get their installationIds
|
||||
installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations, theirIdentityKeyC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We don't have any, send a message with DH
|
||||
if installations == nil && !bytes.Equal(theirIdentityKeyC, ecrypto.CompressPubkey(&myIdentityKey.PublicKey)) {
|
||||
return s.EncryptPayloadWithDH(theirIdentityKey, payload)
|
||||
if len(installations) == 0 {
|
||||
encryptedPayload, err := s.EncryptPayloadWithDH(theirIdentityKey, payload)
|
||||
return encryptedPayload, targetedInstallations, err
|
||||
}
|
||||
|
||||
response := make(map[string]*DirectMessageProtocol)
|
||||
response := make(map[string]*protobuf.DirectMessageProtocol)
|
||||
|
||||
for _, installation := range installations {
|
||||
installationID := installation.ID
|
||||
|
@ -546,31 +476,33 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
|||
if s.config.InstallationID == installationID {
|
||||
continue
|
||||
}
|
||||
bundle, err := s.persistence.GetPublicBundle(theirIdentityKey, []*Installation{installation})
|
||||
bundle, err := s.persistence.GetPublicBundle(theirIdentityKey, []*multidevice.Installation{installation})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// See if a session is there already
|
||||
drInfo, err := s.persistence.GetAnyRatchetInfo(theirIdentityKeyC, installationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
targetedInstallations = append(targetedInstallations, installation)
|
||||
|
||||
if drInfo != nil {
|
||||
s.log.Debug("Found DR info", "installationID", installationID)
|
||||
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dmp := DirectMessageProtocol{
|
||||
dmp := protobuf.DirectMessageProtocol{
|
||||
Payload: encryptedPayload,
|
||||
DRHeader: drHeader,
|
||||
}
|
||||
|
||||
if drInfo.EphemeralKey != nil {
|
||||
dmp.X3DHHeader = &X3DHHeader{
|
||||
dmp.X3DHHeader = &protobuf.X3DHHeader{
|
||||
Key: drInfo.EphemeralKey,
|
||||
Id: drInfo.BundleID,
|
||||
}
|
||||
|
@ -594,33 +526,33 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
|||
|
||||
sharedKey, ourEphemeralKey, err := s.keyFromActiveX3DH(theirIdentityKeyC, theirSignedPreKey, myIdentityKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
theirIdentityKeyC := ecrypto.CompressPubkey(theirIdentityKey)
|
||||
ourEphemeralKeyC := ecrypto.CompressPubkey(ourEphemeralKey)
|
||||
|
||||
err = s.persistence.AddRatchetInfo(sharedKey, theirIdentityKeyC, theirSignedPreKey, ourEphemeralKeyC, installationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
x3dhHeader := &X3DHHeader{
|
||||
x3dhHeader := &protobuf.X3DHHeader{
|
||||
Key: ourEphemeralKeyC,
|
||||
Id: theirSignedPreKey,
|
||||
}
|
||||
|
||||
drInfo, err = s.persistence.GetRatchetInfo(theirSignedPreKey, theirIdentityKeyC, installationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if drInfo != nil {
|
||||
encryptedPayload, drHeader, err := s.encryptUsingDR(theirIdentityKey, drInfo, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dmp := &DirectMessageProtocol{
|
||||
dmp := &protobuf.DirectMessageProtocol{
|
||||
Payload: encryptedPayload,
|
||||
X3DHHeader: x3dhHeader,
|
||||
DRHeader: drHeader,
|
||||
|
@ -632,5 +564,5 @@ func (s *EncryptionService) EncryptPayload(theirIdentityKey *ecdsa.PublicKey, my
|
|||
|
||||
s.log.Debug("Built message", "theirKey", theirIdentityKey)
|
||||
|
||||
return response, nil
|
||||
return response, targetedInstallations, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -20,7 +23,7 @@ func TestEncryptionServiceMultiDeviceSuite(t *testing.T) {
|
|||
}
|
||||
|
||||
type serviceAndKey struct {
|
||||
encryptionServices []*EncryptionService
|
||||
services []*ProtocolService
|
||||
key *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
|
@ -37,7 +40,7 @@ func setupUser(user string, s *EncryptionServiceMultiDeviceSuite, n int) error {
|
|||
|
||||
s.services[user] = &serviceAndKey{
|
||||
key: key,
|
||||
encryptionServices: make([]*EncryptionService, n),
|
||||
services: make([]*ProtocolService, n),
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
|
@ -50,11 +53,27 @@ func setupUser(user string, s *EncryptionServiceMultiDeviceSuite, n int) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Initialize sharedsecret
|
||||
multideviceConfig := &multidevice.Config{
|
||||
MaxInstallations: n - 1,
|
||||
InstallationID: installationID,
|
||||
ProtocolVersion: 1,
|
||||
}
|
||||
|
||||
config := DefaultEncryptionServiceConfig(installationID)
|
||||
config.MaxInstallations = n - 1
|
||||
sharedSecretService := sharedsecret.NewService(persistence.GetSharedSecretStorage())
|
||||
multideviceService := multidevice.New(multideviceConfig, persistence.GetMultideviceStorage())
|
||||
|
||||
s.services[user].encryptionServices[i] = NewEncryptionService(persistence, config)
|
||||
protocol := NewProtocolService(
|
||||
NewEncryptionService(
|
||||
persistence,
|
||||
DefaultEncryptionServiceConfig(installationID)),
|
||||
sharedSecretService,
|
||||
multideviceService,
|
||||
func(s []multidevice.IdentityAndIDPair) {},
|
||||
func(s []*sharedsecret.Secret) {},
|
||||
)
|
||||
|
||||
s.services[user].services[i] = protocol
|
||||
|
||||
}
|
||||
|
||||
|
@ -73,43 +92,47 @@ func (s *EncryptionServiceMultiDeviceSuite) SetupTest() {
|
|||
func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
|
||||
aliceKey := s.services[aliceUser].key
|
||||
|
||||
alice2Bundle, err := s.services[aliceUser].encryptionServices[1].CreateBundle(aliceKey)
|
||||
alice2Bundle, err := s.services[aliceUser].services[1].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice2Identity, err := ExtractIdentity(alice2Bundle)
|
||||
alice2IdentityPK, err := ExtractIdentity(alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice3Bundle, err := s.services[aliceUser].encryptionServices[2].CreateBundle(aliceKey)
|
||||
alice2Identity := fmt.Sprintf("0x%x", crypto.FromECDSAPub(alice2IdentityPK))
|
||||
|
||||
alice3Bundle, err := s.services[aliceUser].services[2].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice3Identity, err := ExtractIdentity(alice2Bundle)
|
||||
alice3IdentityPK, err := ExtractIdentity(alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice3Identity := fmt.Sprintf("0x%x", crypto.FromECDSAPub(alice3IdentityPK))
|
||||
|
||||
// Add alice2 bundle
|
||||
response, err := s.services[aliceUser].encryptionServices[0].ProcessPublicBundle(aliceKey, alice2Bundle)
|
||||
response, err := s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice2Bundle)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
|
||||
s.Require().Equal(multidevice.IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
|
||||
|
||||
// Add alice3 bundle
|
||||
response, err = s.services[aliceUser].encryptionServices[0].ProcessPublicBundle(aliceKey, alice3Bundle)
|
||||
response, err = s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice3Bundle)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(IdentityAndIDPair{alice3Identity, "alice3"}, response[0])
|
||||
s.Require().Equal(multidevice.IdentityAndIDPair{alice3Identity, "alice3"}, response[0])
|
||||
|
||||
// No installation is enabled
|
||||
alice1MergedBundle1, err := s.services[aliceUser].encryptionServices[0].CreateBundle(aliceKey)
|
||||
alice1MergedBundle1, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(1, len(alice1MergedBundle1.GetSignedPreKeys()))
|
||||
s.Require().NotNil(alice1MergedBundle1.GetSignedPreKeys()["alice1"])
|
||||
|
||||
// We enable the installations
|
||||
err = s.services[aliceUser].encryptionServices[0].EnableInstallation(&aliceKey.PublicKey, "alice2")
|
||||
err = s.services[aliceUser].services[0].EnableInstallation(&aliceKey.PublicKey, "alice2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.services[aliceUser].encryptionServices[0].EnableInstallation(&aliceKey.PublicKey, "alice3")
|
||||
err = s.services[aliceUser].services[0].EnableInstallation(&aliceKey.PublicKey, "alice3")
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice1MergedBundle2, err := s.services[aliceUser].encryptionServices[0].CreateBundle(aliceKey)
|
||||
alice1MergedBundle2, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We get back a bundle with all the installations
|
||||
|
@ -118,21 +141,21 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundle() {
|
|||
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice2"])
|
||||
s.Require().NotNil(alice1MergedBundle2.GetSignedPreKeys()["alice3"])
|
||||
|
||||
response, err = s.services[aliceUser].encryptionServices[0].ProcessPublicBundle(aliceKey, alice1MergedBundle2)
|
||||
response, err = s.services[aliceUser].services[0].ProcessPublicBundle(aliceKey, alice1MergedBundle2)
|
||||
s.Require().NoError(err)
|
||||
sort.Slice(response, func(i, j int) bool {
|
||||
return response[i][1] < response[j][1]
|
||||
})
|
||||
// We only get back installationIDs not equal to us
|
||||
s.Require().Equal(2, len(response))
|
||||
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
|
||||
s.Require().Equal(IdentityAndIDPair{alice2Identity, "alice3"}, response[1])
|
||||
s.Require().Equal(multidevice.IdentityAndIDPair{alice2Identity, "alice2"}, response[0])
|
||||
s.Require().Equal(multidevice.IdentityAndIDPair{alice2Identity, "alice3"}, response[1])
|
||||
|
||||
// We disable the installations
|
||||
err = s.services[aliceUser].encryptionServices[0].DisableInstallation(&aliceKey.PublicKey, "alice2")
|
||||
err = s.services[aliceUser].services[0].DisableInstallation(&aliceKey.PublicKey, "alice2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
alice1MergedBundle3, err := s.services[aliceUser].encryptionServices[0].CreateBundle(aliceKey)
|
||||
alice1MergedBundle3, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We get back a bundle with all the installations
|
||||
|
@ -146,23 +169,23 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundleOutOfOrder()
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice1 creates a bundle
|
||||
alice1Bundle, err := s.services[aliceUser].encryptionServices[0].CreateBundle(aliceKey)
|
||||
alice1Bundle, err := s.services[aliceUser].services[0].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice2 Receives the bundle
|
||||
_, err = s.services[aliceUser].encryptionServices[1].ProcessPublicBundle(aliceKey, alice1Bundle)
|
||||
_, err = s.services[aliceUser].services[1].ProcessPublicBundle(aliceKey, alice1Bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice2 Creates a Bundle
|
||||
_, err = s.services[aliceUser].encryptionServices[1].CreateBundle(aliceKey)
|
||||
_, err = s.services[aliceUser].services[1].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We enable the installation
|
||||
err = s.services[aliceUser].encryptionServices[1].EnableInstallation(&aliceKey.PublicKey, "alice1")
|
||||
err = s.services[aliceUser].services[1].EnableInstallation(&aliceKey.PublicKey, "alice1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// It should contain both bundles
|
||||
alice2MergedBundle1, err := s.services[aliceUser].encryptionServices[1].CreateBundle(aliceKey)
|
||||
alice2MergedBundle1, err := s.services[aliceUser].services[1].GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().NotNil(alice2MergedBundle1.GetSignedPreKeys()["alice1"])
|
||||
|
@ -170,9 +193,9 @@ func (s *EncryptionServiceMultiDeviceSuite) TestProcessPublicBundleOutOfOrder()
|
|||
}
|
||||
|
||||
func pairDevices(s *serviceAndKey, target int) error {
|
||||
device := s.encryptionServices[target]
|
||||
for i := 0; i < len(s.encryptionServices); i++ {
|
||||
b, err := s.encryptionServices[i].CreateBundle(s.key)
|
||||
device := s.services[target]
|
||||
for i := 0; i < len(s.services); i++ {
|
||||
b, err := s.services[i].GetBundle(s.key)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -183,7 +206,7 @@ func pairDevices(s *serviceAndKey, target int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = device.EnableInstallation(&s.key.PublicKey, s.encryptionServices[i].config.InstallationID)
|
||||
err = device.EnableInstallation(&s.key.PublicKey, s.services[i].encryption.config.InstallationID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -194,14 +217,14 @@ func pairDevices(s *serviceAndKey, target int) error {
|
|||
func (s *EncryptionServiceMultiDeviceSuite) TestMaxDevices() {
|
||||
err := pairDevices(s.services[aliceUser], 0)
|
||||
s.Require().NoError(err)
|
||||
alice1 := s.services[aliceUser].encryptionServices[0]
|
||||
bob1 := s.services[bobUser].encryptionServices[0]
|
||||
alice1 := s.services[aliceUser].services[0]
|
||||
bob1 := s.services[bobUser].services[0]
|
||||
aliceKey := s.services[aliceUser].key
|
||||
bobKey := s.services[bobUser].key
|
||||
|
||||
// Check bundle is ok
|
||||
// No installation is enabled
|
||||
aliceBundle, err := alice1.CreateBundle(aliceKey)
|
||||
aliceBundle, err := alice1.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check all installations are correctly working, and that the oldest device is not there
|
||||
|
@ -218,19 +241,20 @@ func (s *EncryptionServiceMultiDeviceSuite) TestMaxDevices() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message to alice
|
||||
payload, err := bob1.EncryptPayload(&aliceKey.PublicKey, bobKey, []byte("test"))
|
||||
msg, err := bob1.BuildDirectMessage(bobKey, &aliceKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
payload := msg.Message.GetDirectMessage()
|
||||
s.Require().Equal(3, len(payload))
|
||||
s.Require().NotNil(payload["alice1"])
|
||||
s.Require().NotNil(payload["alice3"])
|
||||
s.Require().NotNil(payload["alice4"])
|
||||
|
||||
// We disable the last installation
|
||||
err = s.services[aliceUser].encryptionServices[0].DisableInstallation(&aliceKey.PublicKey, "alice4")
|
||||
err = s.services[aliceUser].services[0].DisableInstallation(&aliceKey.PublicKey, "alice4")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We check the bundle is updated
|
||||
aliceBundle, err = alice1.CreateBundle(aliceKey)
|
||||
aliceBundle, err = alice1.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check all installations are there
|
||||
|
@ -247,8 +271,9 @@ func (s *EncryptionServiceMultiDeviceSuite) TestMaxDevices() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message to alice
|
||||
payload, err = bob1.EncryptPayload(&aliceKey.PublicKey, bobKey, []byte("test"))
|
||||
msg, err = bob1.BuildDirectMessage(bobKey, &aliceKey.PublicKey, []byte("test"))
|
||||
s.Require().NoError(err)
|
||||
payload = msg.Message.GetDirectMessage()
|
||||
s.Require().Equal(3, len(payload))
|
||||
s.Require().NotNil(payload["alice1"])
|
||||
s.Require().NotNil(payload["alice2"])
|
||||
|
|
|
@ -13,6 +13,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
@ -27,8 +30,8 @@ func TestEncryptionServiceTestSuite(t *testing.T) {
|
|||
|
||||
type EncryptionServiceTestSuite struct {
|
||||
suite.Suite
|
||||
alice *EncryptionService
|
||||
bob *EncryptionService
|
||||
alice *ProtocolService
|
||||
bob *ProtocolService
|
||||
aliceDBPath string
|
||||
bobDBPath string
|
||||
}
|
||||
|
@ -56,21 +59,57 @@ func (s *EncryptionServiceTestSuite) initDatabases(baseConfig *EncryptionService
|
|||
bobDBKey = "bob"
|
||||
)
|
||||
|
||||
aliceMultideviceConfig := &multidevice.Config{
|
||||
MaxInstallations: 3,
|
||||
InstallationID: aliceInstallationID,
|
||||
ProtocolVersion: 1,
|
||||
}
|
||||
|
||||
alicePersistence, err := NewSQLLitePersistence(aliceDBPath, aliceDBKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
baseConfig.InstallationID = aliceInstallationID
|
||||
aliceEncryptionService := NewEncryptionService(alicePersistence, *baseConfig)
|
||||
|
||||
aliceSharedSecretService := sharedsecret.NewService(alicePersistence.GetSharedSecretStorage())
|
||||
aliceMultideviceService := multidevice.New(aliceMultideviceConfig, alicePersistence.GetMultideviceStorage())
|
||||
|
||||
s.alice = NewProtocolService(
|
||||
aliceEncryptionService,
|
||||
aliceSharedSecretService,
|
||||
aliceMultideviceService,
|
||||
func(s []multidevice.IdentityAndIDPair) {},
|
||||
func(s []*sharedsecret.Secret) {},
|
||||
)
|
||||
|
||||
bobPersistence, err := NewSQLLitePersistence(bobDBPath, bobDBKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
baseConfig.InstallationID = aliceInstallationID
|
||||
s.alice = NewEncryptionService(alicePersistence, *baseConfig)
|
||||
bobMultideviceConfig := &multidevice.Config{
|
||||
MaxInstallations: 3,
|
||||
InstallationID: bobInstallationID,
|
||||
ProtocolVersion: 1,
|
||||
}
|
||||
|
||||
bobMultideviceService := multidevice.New(bobMultideviceConfig, bobPersistence.GetMultideviceStorage())
|
||||
|
||||
bobSharedSecretService := sharedsecret.NewService(bobPersistence.GetSharedSecretStorage())
|
||||
|
||||
baseConfig.InstallationID = bobInstallationID
|
||||
s.bob = NewEncryptionService(bobPersistence, *baseConfig)
|
||||
bobEncryptionService := NewEncryptionService(bobPersistence, *baseConfig)
|
||||
|
||||
s.bob = NewProtocolService(
|
||||
bobEncryptionService,
|
||||
bobSharedSecretService,
|
||||
bobMultideviceService,
|
||||
func(s []multidevice.IdentityAndIDPair) {},
|
||||
func(s []*sharedsecret.Secret) {},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) SetupTest() {
|
||||
|
@ -82,14 +121,14 @@ func (s *EncryptionServiceTestSuite) TearDownTest() {
|
|||
os.Remove(s.bobDBPath)
|
||||
}
|
||||
|
||||
func (s *EncryptionServiceTestSuite) TestCreateBundle() {
|
||||
func (s *EncryptionServiceTestSuite) TestGetBundle() {
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
aliceBundle1, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle1, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
s.NotNil(aliceBundle1, "It creates a bundle")
|
||||
|
||||
aliceBundle2, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle2, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(aliceBundle1, aliceBundle2, "It returns the same bundle")
|
||||
}
|
||||
|
@ -105,9 +144,11 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() {
|
|||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse1, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext)
|
||||
response1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext)
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse1 := response1.Message.GetDirectMessage()
|
||||
|
||||
installationResponse1 := encryptionResponse1["none"]
|
||||
// That's for any device
|
||||
s.Require().NotNil(installationResponse1)
|
||||
|
@ -119,14 +160,16 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() {
|
|||
s.NotEqual(cyphertext1, cleartext, "It encrypts the payload correctly")
|
||||
|
||||
// On the receiver side, we should be able to decrypt using our private key and the ephemeral just sent
|
||||
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse1, defaultMessageID)
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using DH")
|
||||
|
||||
// The next message will not be re-using the same key
|
||||
encryptionResponse2, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext)
|
||||
response2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext)
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse2 := response2.Message.GetDirectMessage()
|
||||
|
||||
installationResponse2 := encryptionResponse2[aliceInstallationID]
|
||||
|
||||
cyphertext2 := installationResponse2.GetPayload()
|
||||
|
@ -134,7 +177,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadNoBundle() {
|
|||
s.NotEqual(cyphertext1, cyphertext2, "It does not re-use the symmetric key")
|
||||
s.NotEqual(ephemeralKey1, ephemeralKey2, "It does not re-use the ephemeral key")
|
||||
|
||||
decryptedPayload2, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse2, defaultMessageID)
|
||||
decryptedPayload2, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(cleartext, decryptedPayload2, "It correctly decrypts the payload using DH")
|
||||
}
|
||||
|
@ -150,7 +193,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -158,9 +201,11 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// We send a message using the bundle
|
||||
encryptionResponse1, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext)
|
||||
response1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext)
|
||||
s.Require().NoError(err)
|
||||
|
||||
encryptionResponse1 := response1.Message.GetDirectMessage()
|
||||
|
||||
installationResponse1 := encryptionResponse1[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse1)
|
||||
|
||||
|
@ -186,7 +231,7 @@ func (s *EncryptionServiceTestSuite) TestEncryptPayloadBundle() {
|
|||
s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain")
|
||||
|
||||
// Bob is able to decrypt it using the bundle
|
||||
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse1, defaultMessageID)
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
s.Equal(cleartext, decryptedPayload1, "It correctly decrypts the payload using X3DH")
|
||||
}
|
||||
|
@ -209,7 +254,7 @@ func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -217,12 +262,13 @@ func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// We send a message using the bundle
|
||||
_, err = s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext1)
|
||||
_, err = s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send another message using the bundle
|
||||
encryptionResponse, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext2)
|
||||
response, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext2)
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse := response.Message.GetDirectMessage()
|
||||
|
||||
installationResponse := encryptionResponse[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse)
|
||||
|
@ -250,7 +296,7 @@ func (s *EncryptionServiceTestSuite) TestConsequentMessagesBundle() {
|
|||
s.Equal(uint32(0), drHeader.GetPn(), "It adds the correct length of the message chain")
|
||||
|
||||
// Bob is able to decrypt it using the bundle
|
||||
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse, defaultMessageID)
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH")
|
||||
|
@ -274,11 +320,11 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -290,24 +336,25 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
encryptionResponse, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext1)
|
||||
response, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob replies to the message
|
||||
encryptionResponse, err = s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, cleartext1)
|
||||
response, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, cleartext1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, encryptionResponse, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send another message using the bundle
|
||||
encryptionResponse, err = s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, cleartext2)
|
||||
response, err = s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, cleartext2)
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse := response.Message.GetDirectMessage()
|
||||
|
||||
installationResponse := encryptionResponse[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse)
|
||||
|
@ -333,7 +380,7 @@ func (s *EncryptionServiceTestSuite) TestConversation() {
|
|||
s.Equal(uint32(1), drHeader.GetPn(), "It adds the correct length of the message chain")
|
||||
|
||||
// Bob is able to decrypt it using the bundle
|
||||
decryptedPayload1, err := s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse, defaultMessageID)
|
||||
decryptedPayload1, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(cleartext2, decryptedPayload1, "It correctly decrypts the payload using X3DH")
|
||||
|
@ -354,7 +401,7 @@ func (s *EncryptionServiceTestSuite) TestMaxSkipKeys() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -362,7 +409,7 @@ func (s *EncryptionServiceTestSuite) TestMaxSkipKeys() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
|
@ -371,30 +418,30 @@ func (s *EncryptionServiceTestSuite) TestMaxSkipKeys() {
|
|||
|
||||
// Bob sends a message
|
||||
|
||||
for i := 0; i < s.alice.config.MaxSkip; i++ {
|
||||
_, err = s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
for i := 0; i < s.alice.encryption.config.MaxSkip; i++ {
|
||||
_, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
_, err = s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
_, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage2, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
bobMessage2, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message, we should have maxSkip + 1 keys in the db, but
|
||||
// we should not throw an error
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage2, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
|
@ -409,7 +456,7 @@ func (s *EncryptionServiceTestSuite) TestMaxSkipKeysError() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -417,7 +464,7 @@ func (s *EncryptionServiceTestSuite) TestMaxSkipKeysError() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
|
@ -426,17 +473,17 @@ func (s *EncryptionServiceTestSuite) TestMaxSkipKeysError() {
|
|||
|
||||
// Bob sends a message
|
||||
|
||||
for i := 0; i < s.alice.config.MaxSkip+1; i++ {
|
||||
_, err = s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
for i := 0; i < s.alice.encryption.config.MaxSkip+1; i++ {
|
||||
_, err = s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, defaultMessageID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: too many messages"), err)
|
||||
}
|
||||
|
||||
|
@ -457,7 +504,7 @@ func (s *EncryptionServiceTestSuite) TestMaxMessageKeysPerSession() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -465,7 +512,7 @@ func (s *EncryptionServiceTestSuite) TestMaxMessageKeysPerSession() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
|
@ -474,27 +521,27 @@ func (s *EncryptionServiceTestSuite) TestMaxMessageKeysPerSession() {
|
|||
|
||||
// We create just enough messages so that the first key should be deleted
|
||||
|
||||
nMessages := s.alice.config.MaxMessageKeysPerSession
|
||||
messages := make([]map[string]*DirectMessageProtocol, nMessages)
|
||||
nMessages := s.alice.encryption.config.MaxMessageKeysPerSession
|
||||
messages := make([]*protobuf.ProtocolMessage, nMessages)
|
||||
for i := 0; i < nMessages; i++ {
|
||||
m, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
m, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
messages[i] = m
|
||||
messages[i] = m.Message
|
||||
}
|
||||
|
||||
// Another message to trigger the deletion
|
||||
m, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
m, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
s.Require().NoError(err)
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, m, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, m.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We decrypt the first message, and it should fail
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, messages[0], defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[0], defaultMessageID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// We decrypt the second message, and it should be decrypted
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, messages[1], defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[1], defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
|
@ -514,7 +561,7 @@ func (s *EncryptionServiceTestSuite) TestMaxKeep() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -522,7 +569,7 @@ func (s *EncryptionServiceTestSuite) TestMaxKeep() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
|
@ -530,15 +577,15 @@ func (s *EncryptionServiceTestSuite) TestMaxKeep() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// We decrypt all messages but 1 & 2
|
||||
messages := make([]map[string]*DirectMessageProtocol, s.alice.config.MaxKeep)
|
||||
for i := 0; i < s.alice.config.MaxKeep; i++ {
|
||||
m, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText)
|
||||
messages[i] = m
|
||||
messages := make([]*protobuf.ProtocolMessage, s.alice.encryption.config.MaxKeep)
|
||||
for i := 0; i < s.alice.encryption.config.MaxKeep; i++ {
|
||||
m, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText)
|
||||
messages[i] = m.Message
|
||||
s.Require().NoError(err)
|
||||
|
||||
if i != 0 && i != 1 {
|
||||
messageID := []byte(fmt.Sprintf("%d", i))
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, m, messageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, m.Message, messageID)
|
||||
s.Require().NoError(err)
|
||||
err = s.alice.ConfirmMessagesProcessed([][]byte{messageID})
|
||||
s.Require().NoError(err)
|
||||
|
@ -547,11 +594,11 @@ func (s *EncryptionServiceTestSuite) TestMaxKeep() {
|
|||
}
|
||||
|
||||
// We decrypt the first message, and it should fail, as it should have been removed
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, messages[0], defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[0], defaultMessageID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// We decrypt the second message, and it should be decrypted
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, messages[1], defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, messages[1], defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
|
@ -576,7 +623,7 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -584,7 +631,7 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
|
@ -592,44 +639,44 @@ func (s *EncryptionServiceTestSuite) TestConcurrentBundles() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage1, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, aliceText1)
|
||||
aliceMessage1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, aliceText1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText1)
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage1, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob replies to the message
|
||||
bobMessage2, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText2)
|
||||
bobMessage2, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage2, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, aliceText2)
|
||||
aliceMessage2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, aliceText2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage2, defaultMessageID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage2, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func publisher(
|
||||
e *EncryptionService,
|
||||
e *ProtocolService,
|
||||
privateKey *ecdsa.PrivateKey,
|
||||
publicKey *ecdsa.PublicKey,
|
||||
errChan chan error,
|
||||
output chan map[string]*DirectMessageProtocol,
|
||||
output chan *protobuf.ProtocolMessage,
|
||||
) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
@ -642,13 +689,13 @@ func publisher(
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
|
||||
response, err := e.EncryptPayload(publicKey, privateKey, cleartext)
|
||||
response, err := e.BuildDirectMessage(privateKey, publicKey, cleartext)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
output <- response
|
||||
output <- response.Message
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
@ -658,17 +705,16 @@ func publisher(
|
|||
}
|
||||
|
||||
func receiver(
|
||||
s *EncryptionService,
|
||||
s *ProtocolService,
|
||||
privateKey *ecdsa.PrivateKey,
|
||||
publicKey *ecdsa.PublicKey,
|
||||
installationID string,
|
||||
errChan chan error,
|
||||
input chan map[string]*DirectMessageProtocol,
|
||||
input chan *protobuf.ProtocolMessage,
|
||||
) {
|
||||
i := 0
|
||||
|
||||
for payload := range input {
|
||||
actualCleartext, err := s.DecryptPayload(privateKey, publicKey, installationID, payload, defaultMessageID)
|
||||
actualCleartext, err := s.HandleMessage(privateKey, publicKey, payload, defaultMessageID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
|
@ -697,7 +743,7 @@ func (s *EncryptionServiceTestSuite) TestRandomised() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -705,15 +751,15 @@ func (s *EncryptionServiceTestSuite) TestRandomised() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
_, err = s.bob.ProcessPublicBundle(bobKey, aliceBundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
aliceChan := make(chan map[string]*DirectMessageProtocol, 100)
|
||||
bobChan := make(chan map[string]*DirectMessageProtocol, 100)
|
||||
aliceChan := make(chan *protobuf.ProtocolMessage, 100)
|
||||
bobChan := make(chan *protobuf.ProtocolMessage, 100)
|
||||
|
||||
alicePublisherErrChan := make(chan error, 1)
|
||||
bobPublisherErrChan := make(chan error, 1)
|
||||
|
@ -727,10 +773,10 @@ func (s *EncryptionServiceTestSuite) TestRandomised() {
|
|||
go publisher(s.bob, bobKey, &aliceKey.PublicKey, bobPublisherErrChan, aliceChan)
|
||||
|
||||
// Set up bob receiver
|
||||
go receiver(s.bob, bobKey, &aliceKey.PublicKey, aliceInstallationID, bobReceiverErrChan, bobChan)
|
||||
go receiver(s.bob, bobKey, &aliceKey.PublicKey, bobReceiverErrChan, bobChan)
|
||||
|
||||
// Set up alice receiver
|
||||
go receiver(s.alice, aliceKey, &bobKey.PublicKey, bobInstallationID, aliceReceiverErrChan, aliceChan)
|
||||
go receiver(s.alice, aliceKey, &bobKey.PublicKey, aliceReceiverErrChan, aliceChan)
|
||||
|
||||
aliceErr := <-alicePublisherErrChan
|
||||
s.Require().NoError(aliceErr)
|
||||
|
@ -771,11 +817,11 @@ func (s *EncryptionServiceTestSuite) TestBundleNotExisting() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, aliceText)
|
||||
aliceMessage, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, aliceText)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message, and returns a bundlenotfound error
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage.Message, defaultMessageID)
|
||||
s.Require().Error(err)
|
||||
s.Equal(ErrSessionNotFound, err)
|
||||
}
|
||||
|
@ -804,11 +850,11 @@ func (s *EncryptionServiceTestSuite) TestDeviceNotIncluded() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
aliceMessage, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, []byte("does not matter"))
|
||||
aliceMessage, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("does not matter"))
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Bob receives the message, and returns a bundlenotfound error
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, aliceMessage, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, aliceMessage.Message, defaultMessageID)
|
||||
s.Require().Error(err)
|
||||
s.Equal(ErrDeviceNotFound, err)
|
||||
}
|
||||
|
@ -829,7 +875,7 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create bundles
|
||||
bobBundle1, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle1, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(1), bobBundle1.GetSignedPreKeys()[bobInstallationID].GetVersion())
|
||||
|
||||
|
@ -837,7 +883,7 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
|||
time.Sleep(time.Duration(config.BundleRefreshInterval) * time.Millisecond)
|
||||
|
||||
// Create bundles
|
||||
bobBundle2, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle2, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(2), bobBundle2.GetSignedPreKeys()[bobInstallationID].GetVersion())
|
||||
|
||||
|
@ -846,8 +892,9 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
encryptionResponse1, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, []byte("anything"))
|
||||
response1, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("anything"))
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse1 := response1.Message.GetDirectMessage()
|
||||
|
||||
installationResponse1 := encryptionResponse1[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse1)
|
||||
|
@ -859,7 +906,7 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
|||
s.Equal(bobBundle1.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey(), x3dhHeader1.GetId())
|
||||
|
||||
// Bob decrypts the message
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse1, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response1.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add the second bob bundle
|
||||
|
@ -867,8 +914,9 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice sends a message
|
||||
encryptionResponse2, err := s.alice.EncryptPayload(&bobKey.PublicKey, aliceKey, []byte("anything"))
|
||||
response2, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, []byte("anything"))
|
||||
s.Require().NoError(err)
|
||||
encryptionResponse2 := response2.Message.GetDirectMessage()
|
||||
|
||||
installationResponse2 := encryptionResponse2[bobInstallationID]
|
||||
s.Require().NotNil(installationResponse2)
|
||||
|
@ -880,7 +928,7 @@ func (s *EncryptionServiceTestSuite) TestRefreshedBundle() {
|
|||
s.Equal(bobBundle2.GetSignedPreKeys()[bobInstallationID].GetSignedPreKey(), x3dhHeader2.GetId())
|
||||
|
||||
// Bob decrypts the message
|
||||
_, err = s.bob.DecryptPayload(bobKey, &aliceKey.PublicKey, aliceInstallationID, encryptionResponse2, defaultMessageID)
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, response2.Message, defaultMessageID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
|
@ -894,7 +942,7 @@ func (s *EncryptionServiceTestSuite) TestMessageConfirmation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
bobBundle, err := s.bob.CreateBundle(bobKey)
|
||||
bobBundle, err := s.bob.GetBundle(bobKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add bob bundle
|
||||
|
@ -902,7 +950,7 @@ func (s *EncryptionServiceTestSuite) TestMessageConfirmation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Create a bundle
|
||||
aliceBundle, err := s.alice.CreateBundle(aliceKey)
|
||||
aliceBundle, err := s.alice.GetBundle(aliceKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add alice bundle
|
||||
|
@ -910,16 +958,16 @@ func (s *EncryptionServiceTestSuite) TestMessageConfirmation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage1, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText1)
|
||||
bobMessage1, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
bobMessage1ID := []byte("bob-message-1-id")
|
||||
|
||||
// Alice receives the message once
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1, bobMessage1ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, bobMessage1ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives the message twice
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1, bobMessage1ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, bobMessage1ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice confirms the message
|
||||
|
@ -927,33 +975,33 @@ func (s *EncryptionServiceTestSuite) TestMessageConfirmation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice decrypts it again, it should fail
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage1, bobMessage1ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage1.Message, bobMessage1ID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage2, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText1)
|
||||
bobMessage2, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
bobMessage2ID := []byte("bob-message-2-id")
|
||||
|
||||
// Bob sends a message
|
||||
bobMessage3, err := s.bob.EncryptPayload(&aliceKey.PublicKey, bobKey, bobText1)
|
||||
bobMessage3, err := s.bob.BuildDirectMessage(bobKey, &aliceKey.PublicKey, bobText1)
|
||||
s.Require().NoError(err)
|
||||
bobMessage3ID := []byte("bob-message-3-id")
|
||||
|
||||
// Alice receives message 3 once
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage3, bobMessage3ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage3.Message, bobMessage3ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives message 3 twice
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage3, bobMessage3ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage3.Message, bobMessage3ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives message 2 once
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage2, bobMessage2ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, bobMessage2ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice receives message 2 twice
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage2, bobMessage2ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, bobMessage2ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Alice confirms the messages
|
||||
|
@ -961,10 +1009,10 @@ func (s *EncryptionServiceTestSuite) TestMessageConfirmation() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Alice decrypts it again, it should fail
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage3, bobMessage3ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage3.Message, bobMessage3ID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
|
||||
// Alice decrypts it again, it should fail
|
||||
_, err = s.alice.DecryptPayload(aliceKey, &bobKey.PublicKey, bobInstallationID, bobMessage2, bobMessage2ID)
|
||||
_, err = s.alice.HandleMessage(aliceKey, &bobKey.PublicKey, bobMessage2.Message, bobMessage2ID)
|
||||
s.Require().Equal(errors.New("can't skip current chain message keys: bad until: probably an out-of-order message that was deleted"), err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package multidevice
|
||||
|
||||
type Persistence interface {
|
||||
// GetActiveInstallations returns the active installations for a given identity.
|
||||
GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error)
|
||||
// EnableInstallation enables the installation.
|
||||
EnableInstallation(identity []byte, installationID string) error
|
||||
// DisableInstallation disable the installation.
|
||||
DisableInstallation(identity []byte, installationID string) error
|
||||
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
|
||||
AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) error
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package multidevice
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
)
|
||||
|
||||
type Installation struct {
|
||||
ID string
|
||||
Version uint32
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
MaxInstallations int
|
||||
ProtocolVersion uint32
|
||||
InstallationID string
|
||||
}
|
||||
|
||||
func New(config *Config, persistence Persistence) *Service {
|
||||
return &Service{
|
||||
config: config,
|
||||
persistence: persistence,
|
||||
}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
persistence Persistence
|
||||
config *Config
|
||||
}
|
||||
|
||||
type IdentityAndIDPair [2]string
|
||||
|
||||
func (s *Service) GetActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
return s.persistence.GetActiveInstallations(s.config.MaxInstallations, identityC)
|
||||
}
|
||||
|
||||
func (s *Service) GetOurActiveInstallations(identity *ecdsa.PublicKey) ([]*Installation, error) {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
installations, err := s.persistence.GetActiveInstallations(s.config.MaxInstallations-1, identityC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Move to layer above
|
||||
installations = append(installations, &Installation{
|
||||
ID: s.config.InstallationID,
|
||||
Version: s.config.ProtocolVersion,
|
||||
})
|
||||
|
||||
return installations, nil
|
||||
}
|
||||
|
||||
func (s *Service) EnableInstallation(identity *ecdsa.PublicKey, installationID string) error {
|
||||
identityC := crypto.CompressPubkey(identity)
|
||||
return s.persistence.EnableInstallation(identityC, installationID)
|
||||
}
|
||||
|
||||
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
myIdentityKeyC := crypto.CompressPubkey(myIdentityKey)
|
||||
return s.persistence.DisableInstallation(myIdentityKeyC, installationID)
|
||||
}
|
||||
|
||||
// ProcessPublicBundle persists a bundle and returns a list of tuples identity/installationID
|
||||
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, theirIdentity *ecdsa.PublicKey, b *protobuf.Bundle) ([]IdentityAndIDPair, error) {
|
||||
signedPreKeys := b.GetSignedPreKeys()
|
||||
var response []IdentityAndIDPair
|
||||
var installations []*Installation
|
||||
|
||||
myIdentityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(&myIdentityKey.PublicKey))
|
||||
theirIdentityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(theirIdentity))
|
||||
|
||||
// Any device from other peers will be considered enabled, ours needs to
|
||||
// be explicitly enabled
|
||||
fromOurIdentity := theirIdentityStr != myIdentityStr
|
||||
|
||||
for installationID, signedPreKey := range signedPreKeys {
|
||||
if installationID != s.config.InstallationID {
|
||||
installations = append(installations, &Installation{
|
||||
ID: installationID,
|
||||
Version: signedPreKey.GetProtocolVersion(),
|
||||
})
|
||||
response = append(response, IdentityAndIDPair{theirIdentityStr, installationID})
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.persistence.AddInstallations(b.GetIdentity(), b.GetTimestamp(), installations, fromOurIdentity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package multidevice
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// SQLLitePersistence represents a persistence service tied to an SQLite database
|
||||
type SQLLitePersistence struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewSQLLitePersistence creates a new SQLLitePersistence instance, given a path and a key
|
||||
func NewSQLLitePersistence(db *sql.DB) *SQLLitePersistence {
|
||||
return &SQLLitePersistence{db: db}
|
||||
}
|
||||
|
||||
// GetActiveInstallations returns the active installations for a given identity
|
||||
func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) {
|
||||
stmt, err := s.db.Prepare(`SELECT installation_id, version
|
||||
FROM installations
|
||||
WHERE enabled = 1 AND identity = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var installations []*Installation
|
||||
rows, err := stmt.Query(identity, maxInstallations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var installationID string
|
||||
var version uint32
|
||||
err = rows.Scan(
|
||||
&installationID,
|
||||
&version,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installations = append(installations, &Installation{
|
||||
ID: installationID,
|
||||
Version: version,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return installations, nil
|
||||
|
||||
}
|
||||
|
||||
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
|
||||
func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, installation := range installations {
|
||||
stmt, err := tx.Prepare(`SELECT enabled, version
|
||||
FROM installations
|
||||
WHERE identity = ? AND installation_id = ?
|
||||
LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var oldEnabled bool
|
||||
// We don't override version once we saw one
|
||||
var oldVersion uint32
|
||||
latestVersion := installation.Version
|
||||
|
||||
err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
// We update timestamp if present without changing enabled, only if this is a new bundle
|
||||
// and we set the version to the latest we ever saw
|
||||
if err != sql.ErrNoRows {
|
||||
if oldVersion > installation.Version {
|
||||
latestVersion = oldVersion
|
||||
}
|
||||
|
||||
stmt, err = tx.Prepare(`UPDATE installations
|
||||
SET timestamp = ?, enabled = ?, version = ?
|
||||
WHERE identity = ?
|
||||
AND installation_id = ?
|
||||
AND timestamp < ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(
|
||||
timestamp,
|
||||
oldEnabled,
|
||||
latestVersion,
|
||||
identity,
|
||||
installation.ID,
|
||||
timestamp,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
} else {
|
||||
stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version)
|
||||
VALUES (?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(
|
||||
identity,
|
||||
installation.ID,
|
||||
timestamp,
|
||||
defaultEnabled,
|
||||
latestVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// EnableInstallation enables the installation
|
||||
func (s *SQLLitePersistence) EnableInstallation(identity []byte, installationID string) error {
|
||||
stmt, err := s.db.Prepare(`UPDATE installations
|
||||
SET enabled = 1
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(identity, installationID)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// DisableInstallation disable the installation
|
||||
func (s *SQLLitePersistence) DisableInstallation(identity []byte, installationID string) error {
|
||||
|
||||
stmt, err := s.db.Prepare(`UPDATE installations
|
||||
SET enabled = 0
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(identity, installationID)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
package multidevice
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
dbPath = "/tmp/status-key-store.db"
|
||||
)
|
||||
|
||||
func TestSQLLitePersistenceTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SQLLitePersistenceTestSuite))
|
||||
}
|
||||
|
||||
type SQLLitePersistenceTestSuite struct {
|
||||
suite.Suite
|
||||
// nolint: structcheck, megacheck
|
||||
db *sql.DB
|
||||
service Persistence
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) SetupTest() {
|
||||
os.Remove(dbPath)
|
||||
|
||||
db, err := appDB.Open(dbPath, "", 0)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = NewSQLLitePersistence(db)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallations() {
|
||||
identity := []byte("alice")
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationVersions() {
|
||||
identity := []byte("alice")
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
}
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
|
||||
installationsWithDowngradedVersion := []*Installation{
|
||||
{ID: "alice-1", Version: 0},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
3,
|
||||
installationsWithDowngradedVersion,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err = s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
installations = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-3", Version: 3},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
2,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
installations = []*Installation{
|
||||
{ID: "alice-2", Version: 2},
|
||||
{ID: "alice-3", Version: 3},
|
||||
{ID: "alice-4", Version: 4},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
3,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
false,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Nil(actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add the installations again
|
||||
installations = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{{ID: "alice-2", Version: 2}}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{{ID: "alice-2", Version: 2}}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
|
||||
err = s.service.EnableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err = s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
|
||||
}
|
|
@ -4,13 +4,10 @@ import (
|
|||
"crypto/ecdsa"
|
||||
|
||||
dr "github.com/status-im/doubleratchet"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
)
|
||||
|
||||
type Installation struct {
|
||||
ID string
|
||||
Version uint32
|
||||
}
|
||||
|
||||
// RatchetInfo holds the current ratchet state
|
||||
type RatchetInfo struct {
|
||||
ID []byte
|
||||
|
@ -30,17 +27,17 @@ type PersistenceService interface {
|
|||
// GetSessionStorage returns the associated double ratchet SessionStorage object.
|
||||
GetSessionStorage() dr.SessionStorage
|
||||
|
||||
// GetPublicBundle retrieves an existing Bundle for the specified public key & installationIDs.
|
||||
GetPublicBundle(*ecdsa.PublicKey, []*Installation) (*Bundle, error)
|
||||
// GetPublicBundle retrieves an existing Bundle for the specified public key & installations
|
||||
GetPublicBundle(*ecdsa.PublicKey, []*multidevice.Installation) (*protobuf.Bundle, error)
|
||||
// AddPublicBundle persists a specified Bundle
|
||||
AddPublicBundle(*Bundle) error
|
||||
AddPublicBundle(*protobuf.Bundle) error
|
||||
|
||||
// GetAnyPrivateBundle retrieves any bundle for our identity & installationIDs
|
||||
GetAnyPrivateBundle([]byte, []*Installation) (*BundleContainer, error)
|
||||
// GetAnyPrivateBundle retrieves any bundle for our identity & installations
|
||||
GetAnyPrivateBundle([]byte, []*multidevice.Installation) (*protobuf.BundleContainer, error)
|
||||
// GetPrivateKeyBundle retrieves a BundleContainer with the specified signed prekey.
|
||||
GetPrivateKeyBundle([]byte) ([]byte, error)
|
||||
// AddPrivateBundle persists a BundleContainer.
|
||||
AddPrivateBundle(*BundleContainer) error
|
||||
AddPrivateBundle(*protobuf.BundleContainer) error
|
||||
// MarkBundleExpired marks a private bundle as expired, not to be used for encryption anymore.
|
||||
MarkBundleExpired([]byte) error
|
||||
|
||||
|
@ -53,13 +50,4 @@ type PersistenceService interface {
|
|||
// RatchetInfoConfirmed clears the ephemeral key in the RatchetInfo
|
||||
// associated with the specified bundle ID and interlocutor identity public key.
|
||||
RatchetInfoConfirmed([]byte, []byte, string) error
|
||||
|
||||
// GetActiveInstallations returns the active installations for a given identity.
|
||||
GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error)
|
||||
// AddInstallations adds the installations for a given identity.
|
||||
AddInstallations(identity []byte, timestamp int64, installations []*Installation, enabled bool) error
|
||||
// EnableInstallation enables the installation.
|
||||
EnableInstallation(identity []byte, installationID string) error
|
||||
// DisableInstallation disable the installation.
|
||||
DisableInstallation(identity []byte, installationID string) error
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: encryption.proto
|
||||
|
||||
package chat
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
|
@ -491,56 +491,56 @@ func (m *ProtocolMessage) GetPublicMessage() []byte {
|
|||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*SignedPreKey)(nil), "chat.SignedPreKey")
|
||||
proto.RegisterType((*Bundle)(nil), "chat.Bundle")
|
||||
proto.RegisterMapType((map[string]*SignedPreKey)(nil), "chat.Bundle.SignedPreKeysEntry")
|
||||
proto.RegisterType((*BundleContainer)(nil), "chat.BundleContainer")
|
||||
proto.RegisterType((*DRHeader)(nil), "chat.DRHeader")
|
||||
proto.RegisterType((*DHHeader)(nil), "chat.DHHeader")
|
||||
proto.RegisterType((*X3DHHeader)(nil), "chat.X3DHHeader")
|
||||
proto.RegisterType((*DirectMessageProtocol)(nil), "chat.DirectMessageProtocol")
|
||||
proto.RegisterType((*ProtocolMessage)(nil), "chat.ProtocolMessage")
|
||||
proto.RegisterMapType((map[string]*DirectMessageProtocol)(nil), "chat.ProtocolMessage.DirectMessageEntry")
|
||||
proto.RegisterType((*SignedPreKey)(nil), "protobuf.SignedPreKey")
|
||||
proto.RegisterType((*Bundle)(nil), "protobuf.Bundle")
|
||||
proto.RegisterMapType((map[string]*SignedPreKey)(nil), "protobuf.Bundle.SignedPreKeysEntry")
|
||||
proto.RegisterType((*BundleContainer)(nil), "protobuf.BundleContainer")
|
||||
proto.RegisterType((*DRHeader)(nil), "protobuf.DRHeader")
|
||||
proto.RegisterType((*DHHeader)(nil), "protobuf.DHHeader")
|
||||
proto.RegisterType((*X3DHHeader)(nil), "protobuf.X3DHHeader")
|
||||
proto.RegisterType((*DirectMessageProtocol)(nil), "protobuf.DirectMessageProtocol")
|
||||
proto.RegisterType((*ProtocolMessage)(nil), "protobuf.ProtocolMessage")
|
||||
proto.RegisterMapType((map[string]*DirectMessageProtocol)(nil), "protobuf.ProtocolMessage.DirectMessageEntry")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("encryption.proto", fileDescriptor_8293a649ce9418c6) }
|
||||
|
||||
var fileDescriptor_8293a649ce9418c6 = []byte{
|
||||
// 562 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x61, 0x8b, 0xd3, 0x4c,
|
||||
0x10, 0x26, 0x49, 0xef, 0xda, 0x4e, 0xd3, 0xb4, 0xec, 0xcb, 0x2b, 0xa1, 0x1e, 0x58, 0xc2, 0xa9,
|
||||
0x11, 0xa1, 0x70, 0xad, 0x1f, 0xc4, 0x8f, 0x5a, 0xb1, 0x9e, 0x88, 0xc7, 0x2a, 0xe2, 0x17, 0x09,
|
||||
0xdb, 0x66, 0xbd, 0x5b, 0x4c, 0x93, 0xb0, 0xbb, 0x2d, 0xe4, 0xcf, 0xf9, 0x57, 0xfc, 0x29, 0x4a,
|
||||
0x76, 0xb3, 0xed, 0xb6, 0x77, 0x07, 0x7e, 0xeb, 0xcc, 0x3c, 0xfb, 0xcc, 0x33, 0xcf, 0x74, 0x02,
|
||||
0x43, 0x9a, 0xaf, 0x78, 0x55, 0x4a, 0x56, 0xe4, 0x93, 0x92, 0x17, 0xb2, 0x40, 0xad, 0xd5, 0x0d,
|
||||
0x91, 0x51, 0x05, 0xfe, 0x67, 0x76, 0x9d, 0xd3, 0xf4, 0x8a, 0xd3, 0x0f, 0xb4, 0x42, 0xe7, 0x10,
|
||||
0x08, 0x15, 0x27, 0x25, 0xa7, 0xc9, 0x4f, 0x5a, 0x85, 0xce, 0xd8, 0x89, 0x7d, 0xec, 0x0b, 0x1b,
|
||||
0x15, 0x42, 0x7b, 0x4b, 0xb9, 0x60, 0x45, 0x1e, 0xba, 0x63, 0x27, 0xee, 0x63, 0x13, 0xa2, 0x67,
|
||||
0x30, 0x54, 0xf4, 0xab, 0x22, 0x4b, 0x0c, 0xc4, 0x53, 0x90, 0x81, 0xc9, 0x7f, 0xd5, 0xe9, 0xe8,
|
||||
0x8f, 0x03, 0xa7, 0xaf, 0x37, 0x79, 0x9a, 0x51, 0x34, 0x82, 0x0e, 0x4b, 0x69, 0x2e, 0x99, 0x34,
|
||||
0xfd, 0x76, 0x31, 0x7a, 0x07, 0x83, 0x43, 0x45, 0x22, 0x74, 0xc7, 0x5e, 0xdc, 0x9b, 0x3e, 0x9a,
|
||||
0xd4, 0x13, 0x4c, 0x34, 0xc5, 0xc4, 0x9e, 0x42, 0xbc, 0xcd, 0x25, 0xaf, 0x70, 0xdf, 0xd6, 0x2c,
|
||||
0xd0, 0x19, 0x74, 0xeb, 0x04, 0x91, 0x1b, 0x4e, 0xc3, 0x96, 0xea, 0xb2, 0x4f, 0xd4, 0x55, 0xc9,
|
||||
0xd6, 0x54, 0x48, 0xb2, 0x2e, 0xc3, 0x93, 0xb1, 0x13, 0x7b, 0x78, 0x9f, 0x18, 0x7d, 0x01, 0x74,
|
||||
0xbb, 0x01, 0x1a, 0x82, 0x67, 0x1c, 0xea, 0xe2, 0xfa, 0x27, 0x8a, 0xe1, 0x64, 0x4b, 0xb2, 0x0d,
|
||||
0x55, 0xb6, 0xf4, 0xa6, 0x48, 0x4b, 0xb4, 0x9f, 0x62, 0x0d, 0x78, 0xe5, 0xbe, 0x74, 0x22, 0x0e,
|
||||
0x03, 0xad, 0xfe, 0x4d, 0x91, 0x4b, 0xc2, 0x72, 0xca, 0xd1, 0x39, 0x9c, 0x2e, 0x55, 0x4a, 0xb1,
|
||||
0xf6, 0xa6, 0xbe, 0x3d, 0x24, 0x6e, 0x6a, 0x68, 0x06, 0x0f, 0x4a, 0xce, 0xb6, 0x44, 0xd2, 0xe4,
|
||||
0x68, 0x5b, 0xae, 0x9a, 0xeb, 0xbf, 0xa6, 0x6a, 0x37, 0xbe, 0x6c, 0x75, 0xbc, 0x61, 0x2b, 0xba,
|
||||
0x84, 0xce, 0x1c, 0x2f, 0x28, 0x49, 0x29, 0xb7, 0xf5, 0xfb, 0x5a, 0xbf, 0x0f, 0x8e, 0x59, 0xa9,
|
||||
0x93, 0xa3, 0x00, 0xdc, 0xd2, 0xac, 0xcf, 0x2d, 0x55, 0xcc, 0xd2, 0xc6, 0x3a, 0x97, 0xa5, 0xd1,
|
||||
0x19, 0x74, 0xe6, 0x8b, 0xfb, 0xb8, 0xa2, 0x17, 0x00, 0xdf, 0x66, 0xf7, 0xd7, 0x8f, 0xd9, 0x1a,
|
||||
0x7d, 0xbf, 0x1c, 0xf8, 0x7f, 0xce, 0x38, 0x5d, 0xc9, 0x8f, 0x54, 0x08, 0x72, 0x4d, 0xaf, 0x9a,
|
||||
0xbf, 0x0d, 0xba, 0x80, 0x5e, 0xcd, 0x97, 0xdc, 0x28, 0xc2, 0xc6, 0x9f, 0xa1, 0xf6, 0x67, 0xdf,
|
||||
0x08, 0xdb, 0x4d, 0x9f, 0x43, 0x77, 0x8e, 0xcd, 0x03, 0xbd, 0x92, 0x40, 0x3f, 0x30, 0x1e, 0xe0,
|
||||
0xbd, 0x1b, 0x35, 0x78, 0xc7, 0x4e, 0x0f, 0xc0, 0x8b, 0x1d, 0xd8, 0x30, 0x87, 0xd0, 0x2e, 0x49,
|
||||
0x95, 0x15, 0x24, 0x55, 0xfe, 0xf8, 0xd8, 0x84, 0xd1, 0x6f, 0x17, 0x06, 0x46, 0x73, 0x33, 0xc2,
|
||||
0x3f, 0x6e, 0xf5, 0x29, 0x0c, 0x58, 0x2e, 0x24, 0xc9, 0x32, 0x52, 0xdf, 0x69, 0xc2, 0x52, 0xa5,
|
||||
0xb9, 0x8b, 0x03, 0x3b, 0xfd, 0x3e, 0x45, 0x4f, 0xa0, 0xad, 0x9f, 0x88, 0xd0, 0x53, 0xa7, 0x70,
|
||||
0xc8, 0x67, 0x8a, 0xe8, 0x13, 0x04, 0xa9, 0xb2, 0x32, 0x59, 0x6b, 0x21, 0x21, 0x55, 0xf0, 0x58,
|
||||
0xc3, 0x8f, 0x54, 0x4e, 0x0e, 0x6c, 0x6f, 0x4e, 0x28, 0xb5, 0x73, 0xe8, 0x31, 0x04, 0xe5, 0x66,
|
||||
0x99, 0xb1, 0xd5, 0x8e, 0xf0, 0x87, 0x1a, 0xbe, 0xaf, 0xb3, 0x0d, 0x6c, 0xf4, 0x1d, 0xd0, 0x6d,
|
||||
0xae, 0x3b, 0xae, 0xe5, 0xe2, 0xf0, 0x5a, 0x1e, 0x36, 0x6e, 0xdf, 0xb5, 0x7d, 0xeb, 0x6c, 0x96,
|
||||
0xa7, 0xea, 0x4b, 0x32, 0xfb, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x75, 0x6d, 0x59, 0xd4, 0x04,
|
||||
0x00, 0x00,
|
||||
// 566 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0xdd, 0x6a, 0xdb, 0x4c,
|
||||
0x10, 0x45, 0x52, 0xe2, 0x9f, 0xb1, 0xfc, 0xc3, 0x7e, 0x5f, 0x83, 0x30, 0x81, 0x1a, 0xb5, 0xa5,
|
||||
0x6e, 0x09, 0x2e, 0xd8, 0x0d, 0x94, 0x5e, 0xb6, 0x2e, 0xb8, 0x09, 0x85, 0xb0, 0x81, 0x92, 0x3b,
|
||||
0xb1, 0xb6, 0x36, 0xe9, 0x52, 0x79, 0x25, 0x76, 0xd7, 0x06, 0x3d, 0x41, 0xdf, 0xad, 0x2f, 0xd3,
|
||||
0x57, 0x28, 0x5a, 0x69, 0xad, 0xb5, 0x9d, 0x5c, 0xf4, 0xca, 0x9e, 0xb3, 0x73, 0xce, 0xcc, 0x9c,
|
||||
0xd1, 0xc0, 0x80, 0xf2, 0x95, 0xc8, 0x33, 0xc5, 0x52, 0x3e, 0xc9, 0x44, 0xaa, 0x52, 0xd4, 0xd2,
|
||||
0x3f, 0xcb, 0xcd, 0x7d, 0x98, 0x83, 0x7f, 0xcb, 0x1e, 0x38, 0x8d, 0x6f, 0x04, 0xbd, 0xa6, 0x39,
|
||||
0x7a, 0x09, 0x3d, 0xa9, 0xe3, 0x28, 0x13, 0x34, 0xfa, 0x49, 0xf3, 0xc0, 0x19, 0x39, 0x63, 0x1f,
|
||||
0xfb, 0xd2, 0xce, 0x0a, 0xa0, 0xb9, 0xa5, 0x42, 0xb2, 0x94, 0x07, 0xee, 0xc8, 0x19, 0x77, 0xb1,
|
||||
0x09, 0xd1, 0x1b, 0x18, 0x68, 0xed, 0x55, 0x9a, 0x44, 0x26, 0xc5, 0xd3, 0x29, 0x7d, 0x83, 0x7f,
|
||||
0x2f, 0xe1, 0xf0, 0x97, 0x0b, 0x8d, 0x4f, 0x1b, 0x1e, 0x27, 0x14, 0x0d, 0xa1, 0xc5, 0x62, 0xca,
|
||||
0x15, 0x53, 0xa6, 0xde, 0x2e, 0x46, 0xd7, 0xd0, 0xdf, 0xef, 0x48, 0x06, 0xee, 0xc8, 0x1b, 0x77,
|
||||
0xa6, 0x2f, 0x26, 0x66, 0x8a, 0x49, 0x29, 0x33, 0xb1, 0x27, 0x91, 0x5f, 0xb8, 0x12, 0x39, 0xee,
|
||||
0xda, 0x7d, 0x4b, 0x74, 0x0e, 0xed, 0x02, 0x20, 0x6a, 0x23, 0x68, 0x70, 0xa2, 0x2b, 0xd5, 0x40,
|
||||
0xf1, 0xaa, 0xd8, 0x9a, 0x4a, 0x45, 0xd6, 0x59, 0x70, 0x3a, 0x72, 0xc6, 0x1e, 0xae, 0x81, 0xe1,
|
||||
0x1d, 0xa0, 0xe3, 0x02, 0x68, 0x00, 0x9e, 0x71, 0xa9, 0x8d, 0x8b, 0xbf, 0xe8, 0x02, 0x4e, 0xb7,
|
||||
0x24, 0xd9, 0x50, 0x6d, 0x4d, 0x67, 0x7a, 0x56, 0xb7, 0x69, 0xd3, 0x71, 0x99, 0xf4, 0xd1, 0xfd,
|
||||
0xe0, 0x84, 0x5b, 0xe8, 0x97, 0x13, 0x7c, 0x4e, 0xb9, 0x22, 0x8c, 0x53, 0x81, 0xc6, 0xd0, 0x58,
|
||||
0x6a, 0x48, 0x2b, 0x77, 0xa6, 0x83, 0xc3, 0x61, 0x71, 0xf5, 0x8e, 0x66, 0x70, 0x96, 0x09, 0xb6,
|
||||
0x25, 0x8a, 0x46, 0x07, 0x9b, 0x73, 0xf5, 0x7c, 0xff, 0x55, 0xaf, 0x76, 0xf1, 0xab, 0x93, 0x96,
|
||||
0x37, 0x38, 0x09, 0xaf, 0xa0, 0x35, 0xc7, 0x0b, 0x4a, 0x62, 0x2a, 0xec, 0x39, 0xfc, 0x72, 0x0e,
|
||||
0x1f, 0x1c, 0xb3, 0x5e, 0x87, 0xa3, 0x1e, 0xb8, 0x99, 0x59, 0xa5, 0x9b, 0xe9, 0x98, 0xc5, 0x95,
|
||||
0x85, 0x2e, 0x8b, 0xc3, 0x73, 0x68, 0xcd, 0x17, 0x4f, 0x69, 0x85, 0xef, 0x01, 0xee, 0x66, 0x4f,
|
||||
0xbf, 0x1f, 0xaa, 0x55, 0xfd, 0xfd, 0x76, 0xe0, 0xd9, 0x9c, 0x09, 0xba, 0x52, 0xdf, 0xa8, 0x94,
|
||||
0xe4, 0x81, 0xde, 0x54, 0x9f, 0x10, 0xba, 0x84, 0x4e, 0xa1, 0x17, 0xfd, 0xd0, 0x82, 0x95, 0x47,
|
||||
0xff, 0xd7, 0x1e, 0xd5, 0xc5, 0xb0, 0x5d, 0xf8, 0x1d, 0xb4, 0xe7, 0xd8, 0x90, 0xca, 0xf5, 0xa0,
|
||||
0x9a, 0x64, 0xbc, 0xc0, 0xb5, 0x2b, 0x05, 0x61, 0x57, 0x85, 0x1e, 0x11, 0x16, 0x3b, 0x82, 0xa9,
|
||||
0x10, 0x40, 0x33, 0x23, 0x79, 0x92, 0x92, 0x58, 0x7b, 0xe5, 0x63, 0x13, 0x86, 0x7f, 0x5c, 0xe8,
|
||||
0x9b, 0xfe, 0xab, 0x71, 0xfe, 0x61, 0xcb, 0xaf, 0xa1, 0xcf, 0xb8, 0x54, 0x24, 0x49, 0x48, 0x71,
|
||||
0xc7, 0x11, 0x8b, 0x75, 0xff, 0x6d, 0xdc, 0xb3, 0xe1, 0xaf, 0x31, 0x7a, 0x0b, 0xcd, 0x92, 0x22,
|
||||
0x03, 0x4f, 0x9f, 0xc9, 0xb1, 0xa6, 0x49, 0x40, 0xb7, 0xd0, 0x8b, 0xb5, 0xbd, 0xd1, 0xba, 0x6c,
|
||||
0x28, 0xa0, 0x9a, 0x72, 0x51, 0x53, 0x0e, 0x3a, 0x9e, 0xec, 0xad, 0xa3, 0x3a, 0xb1, 0xd8, 0xc6,
|
||||
0xd0, 0x2b, 0xe8, 0x65, 0x9b, 0x65, 0xc2, 0x56, 0x3b, 0xd1, 0x7b, 0x6d, 0x44, 0xb7, 0x44, 0xab,
|
||||
0xb4, 0x21, 0x01, 0x74, 0xac, 0xf5, 0xc8, 0x35, 0x5d, 0xee, 0x5f, 0xd3, 0x73, 0xcb, 0xfd, 0xc7,
|
||||
0xbe, 0x0c, 0xeb, 0xac, 0x96, 0x0d, 0x9d, 0x3a, 0xfb, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x2d,
|
||||
0x0e, 0xc8, 0x00, 0x05, 0x00, 0x00,
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package chat;
|
||||
package protobuf;
|
||||
|
||||
message SignedPreKey {
|
||||
bytes signed_pre_key = 1;
|
|
@ -5,37 +5,48 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
)
|
||||
|
||||
const protocolCurrentVersion = 1
|
||||
const topicNegotiationVersion = 1
|
||||
const ProtocolVersion = 1
|
||||
const sharedSecretNegotiationVersion = 1
|
||||
const partitionedTopicMinVersion = 1
|
||||
|
||||
type ProtocolService struct {
|
||||
log log.Logger
|
||||
encryption *EncryptionService
|
||||
topic *topic.Service
|
||||
addedBundlesHandler func([]IdentityAndIDPair)
|
||||
onNewTopicHandler func([]*topic.Secret)
|
||||
secret *sharedsecret.Service
|
||||
multidevice *multidevice.Service
|
||||
addedBundlesHandler func([]multidevice.IdentityAndIDPair)
|
||||
onNewSharedSecretHandler func([]*sharedsecret.Secret)
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var ErrNotProtocolMessage = errors.New("Not a protocol message")
|
||||
|
||||
// NewProtocolService creates a new ProtocolService instance
|
||||
func NewProtocolService(encryption *EncryptionService, topic *topic.Service, addedBundlesHandler func([]IdentityAndIDPair), onNewTopicHandler func([]*topic.Secret)) *ProtocolService {
|
||||
func NewProtocolService(encryption *EncryptionService, secret *sharedsecret.Service, multidevice *multidevice.Service, addedBundlesHandler func([]multidevice.IdentityAndIDPair), onNewSharedSecretHandler func([]*sharedsecret.Secret)) *ProtocolService {
|
||||
return &ProtocolService{
|
||||
log: log.New("package", "status-go/services/sshext.chat"),
|
||||
encryption: encryption,
|
||||
topic: topic,
|
||||
secret: secret,
|
||||
multidevice: multidevice,
|
||||
addedBundlesHandler: addedBundlesHandler,
|
||||
onNewTopicHandler: onNewTopicHandler,
|
||||
onNewSharedSecretHandler: onNewSharedSecretHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProtocolService) addBundle(myIdentityKey *ecdsa.PrivateKey, msg *ProtocolMessage, sendSingle bool) (*ProtocolMessage, error) {
|
||||
func (p *ProtocolService) addBundle(myIdentityKey *ecdsa.PrivateKey, msg *protobuf.ProtocolMessage, sendSingle bool) (*protobuf.ProtocolMessage, error) {
|
||||
|
||||
// Get a bundle
|
||||
bundle, err := p.encryption.CreateBundle(myIdentityKey)
|
||||
installations, err := p.multidevice.GetOurActiveInstallations(&myIdentityKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundle, err := p.encryption.CreateBundle(myIdentityKey, installations)
|
||||
if err != nil {
|
||||
p.log.Error("encryption-service", "error creating bundle", err)
|
||||
return nil, err
|
||||
|
@ -46,16 +57,16 @@ func (p *ProtocolService) addBundle(myIdentityKey *ecdsa.PrivateKey, msg *Protoc
|
|||
// an issue anymore
|
||||
msg.Bundle = bundle
|
||||
} else {
|
||||
msg.Bundles = []*Bundle{bundle}
|
||||
msg.Bundles = []*protobuf.Bundle{bundle}
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// BuildPublicMessage marshals a public chat message given the user identity private key and a payload
|
||||
func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) (*ProtocolMessage, error) {
|
||||
func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, payload []byte) (*protobuf.ProtocolMessage, error) {
|
||||
// Build message not encrypted
|
||||
protocolMessage := &ProtocolMessage{
|
||||
protocolMessage := &protobuf.ProtocolMessage{
|
||||
InstallationId: p.encryption.config.InstallationID,
|
||||
PublicMessage: payload,
|
||||
}
|
||||
|
@ -63,100 +74,153 @@ func (p *ProtocolService) BuildPublicMessage(myIdentityKey *ecdsa.PrivateKey, pa
|
|||
return p.addBundle(myIdentityKey, protocolMessage, false)
|
||||
}
|
||||
|
||||
type ProtocolMessageSpec struct {
|
||||
Message *protobuf.ProtocolMessage
|
||||
// Installations is the targeted devices
|
||||
Installations []*multidevice.Installation
|
||||
// SharedSecret is a shared secret established among the installations
|
||||
SharedSecret []byte
|
||||
}
|
||||
|
||||
func (p *ProtocolMessageSpec) MinVersion() uint32 {
|
||||
|
||||
var version uint32
|
||||
|
||||
for _, installation := range p.Installations {
|
||||
if installation.Version < version {
|
||||
version = installation.Version
|
||||
}
|
||||
}
|
||||
return version
|
||||
|
||||
}
|
||||
|
||||
func (p *ProtocolMessageSpec) PartitionedTopic() bool {
|
||||
|
||||
return p.MinVersion() >= partitionedTopicMinVersion
|
||||
|
||||
}
|
||||
|
||||
// BuildDirectMessage returns a 1:1 chat message and optionally a negotiated topic given the user identity private key, the recipient's public key, and a payload
|
||||
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) (*ProtocolMessage, []byte, error) {
|
||||
func (p *ProtocolService) BuildDirectMessage(myIdentityKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, payload []byte) (*ProtocolMessageSpec, error) {
|
||||
activeInstallations, err := p.multidevice.GetActiveInstallations(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encrypt payload
|
||||
encryptionResponse, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, payload)
|
||||
encryptionResponse, installations, err := p.encryption.EncryptPayload(publicKey, myIdentityKey, activeInstallations, payload)
|
||||
if err != nil {
|
||||
p.log.Error("encryption-service", "error encrypting payload", err)
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build message
|
||||
protocolMessage := &ProtocolMessage{
|
||||
protocolMessage := &protobuf.ProtocolMessage{
|
||||
InstallationId: p.encryption.config.InstallationID,
|
||||
DirectMessage: encryptionResponse,
|
||||
}
|
||||
|
||||
msg, err := p.addBundle(myIdentityKey, protocolMessage, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check who we are sending the message to, and see if we have a shared secret
|
||||
// across devices
|
||||
var installationIDs []string
|
||||
var sharedSecret *topic.Secret
|
||||
var sharedSecret *sharedsecret.Secret
|
||||
var agreed bool
|
||||
for installationID := range protocolMessage.GetDirectMessage() {
|
||||
if installationID != noInstallationID {
|
||||
installationIDs = append(installationIDs, installationID)
|
||||
}
|
||||
}
|
||||
if len(installationIDs) != 0 {
|
||||
sharedSecret, agreed, err = p.topic.Send(myIdentityKey, p.encryption.config.InstallationID, publicKey, installationIDs)
|
||||
|
||||
sharedSecret, agreed, err = p.secret.Send(myIdentityKey, p.encryption.config.InstallationID, publicKey, installationIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call handler
|
||||
if sharedSecret != nil {
|
||||
p.onNewTopicHandler([]*topic.Secret{sharedSecret})
|
||||
p.onNewSharedSecretHandler([]*sharedsecret.Secret{sharedSecret})
|
||||
}
|
||||
response := &ProtocolMessageSpec{
|
||||
Message: msg,
|
||||
Installations: installations,
|
||||
}
|
||||
|
||||
if agreed {
|
||||
return msg, sharedSecret.Key, nil
|
||||
response.SharedSecret = sharedSecret.Key
|
||||
}
|
||||
return msg, nil, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// BuildDHMessage builds a message with DH encryption so that it can be decrypted by any other device.
|
||||
func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) (*ProtocolMessage, []byte, error) {
|
||||
func (p *ProtocolService) BuildDHMessage(myIdentityKey *ecdsa.PrivateKey, destination *ecdsa.PublicKey, payload []byte) (*ProtocolMessageSpec, error) {
|
||||
// Encrypt payload
|
||||
encryptionResponse, err := p.encryption.EncryptPayloadWithDH(destination, payload)
|
||||
if err != nil {
|
||||
p.log.Error("encryption-service", "error encrypting payload", err)
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build message
|
||||
protocolMessage := &ProtocolMessage{
|
||||
protocolMessage := &protobuf.ProtocolMessage{
|
||||
InstallationId: p.encryption.config.InstallationID,
|
||||
DirectMessage: encryptionResponse,
|
||||
}
|
||||
|
||||
msg, err := p.addBundle(myIdentityKey, protocolMessage, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msg, nil, nil
|
||||
return &ProtocolMessageSpec{Message: msg}, nil
|
||||
}
|
||||
|
||||
// ProcessPublicBundle processes a received X3DH bundle.
|
||||
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *Bundle) ([]IdentityAndIDPair, error) {
|
||||
return p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
func (p *ProtocolService) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *protobuf.Bundle) ([]multidevice.IdentityAndIDPair, error) {
|
||||
if err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
theirIdentityKey, err := ExtractIdentity(bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.multidevice.ProcessPublicBundle(myIdentityKey, theirIdentityKey, bundle)
|
||||
}
|
||||
|
||||
// GetBundle retrieves or creates a X3DH bundle, given a private identity key.
|
||||
func (p *ProtocolService) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*Bundle, error) {
|
||||
return p.encryption.CreateBundle(myIdentityKey)
|
||||
func (p *ProtocolService) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*protobuf.Bundle, error) {
|
||||
installations, err := p.multidevice.GetOurActiveInstallations(&myIdentityKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.encryption.CreateBundle(myIdentityKey, installations)
|
||||
}
|
||||
|
||||
// EnableInstallation enables an installation for multi-device sync.
|
||||
func (p *ProtocolService) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
return p.encryption.EnableInstallation(myIdentityKey, installationID)
|
||||
return p.multidevice.EnableInstallation(myIdentityKey, installationID)
|
||||
}
|
||||
|
||||
// DisableInstallation disables an installation for multi-device sync.
|
||||
func (p *ProtocolService) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
return p.encryption.DisableInstallation(myIdentityKey, installationID)
|
||||
return p.multidevice.DisableInstallation(myIdentityKey, installationID)
|
||||
}
|
||||
|
||||
// GetPublicBundle retrieves a public bundle given an identity
|
||||
func (p *ProtocolService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*Bundle, error) {
|
||||
return p.encryption.GetPublicBundle(theirIdentityKey)
|
||||
func (p *ProtocolService) GetPublicBundle(theirIdentityKey *ecdsa.PublicKey) (*protobuf.Bundle, error) {
|
||||
installations, err := p.multidevice.GetActiveInstallations(theirIdentityKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.encryption.GetPublicBundle(theirIdentityKey, installations)
|
||||
}
|
||||
|
||||
// ConfirmMessagesProcessed confirms and deletes message keys for the given messages
|
||||
|
@ -165,7 +229,7 @@ func (p *ProtocolService) ConfirmMessagesProcessed(messageIDs [][]byte) error {
|
|||
}
|
||||
|
||||
// HandleMessage unmarshals a message and processes it, decrypting it if it is a 1:1 message.
|
||||
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, protocolMessage *ProtocolMessage, messageID []byte) ([]byte, error) {
|
||||
func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, protocolMessage *protobuf.ProtocolMessage, messageID []byte) ([]byte, error) {
|
||||
if p.encryption == nil {
|
||||
return nil, errors.New("encryption service not initialized")
|
||||
}
|
||||
|
@ -173,7 +237,7 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
|
|||
// Process bundle, deprecated, here for backward compatibility
|
||||
if bundle := protocolMessage.GetBundle(); bundle != nil {
|
||||
// Should we stop processing if the bundle cannot be verified?
|
||||
addedBundles, err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
addedBundles, err := p.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -184,7 +248,7 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
|
|||
// Process bundles
|
||||
for _, bundle := range protocolMessage.GetBundles() {
|
||||
// Should we stop processing if the bundle cannot be verified?
|
||||
addedBundles, err := p.encryption.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
addedBundles, err := p.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -205,17 +269,20 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var bundles []*protobuf.Bundle
|
||||
p.log.Info("Checking version")
|
||||
// Handle protocol negotiation for compatible clients
|
||||
version := getProtocolVersion(protocolMessage.GetBundles(), protocolMessage.GetInstallationId())
|
||||
if version >= topicNegotiationVersion {
|
||||
p.log.Info("bundle", "bundles", protocolMessage)
|
||||
bundles = append(protocolMessage.GetBundles(), protocolMessage.GetBundle())
|
||||
version := getProtocolVersion(bundles, protocolMessage.GetInstallationId())
|
||||
if version >= sharedSecretNegotiationVersion {
|
||||
p.log.Info("Version greater than 1 negotianting")
|
||||
sharedSecret, err := p.topic.Receive(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId())
|
||||
sharedSecret, err := p.secret.Receive(myIdentityKey, theirPublicKey, protocolMessage.GetInstallationId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.onNewTopicHandler([]*topic.Secret{sharedSecret})
|
||||
p.onNewSharedSecretHandler([]*sharedsecret.Secret{sharedSecret})
|
||||
|
||||
}
|
||||
return message, nil
|
||||
|
@ -225,12 +292,13 @@ func (p *ProtocolService) HandleMessage(myIdentityKey *ecdsa.PrivateKey, theirPu
|
|||
return nil, errors.New("no payload")
|
||||
}
|
||||
|
||||
func getProtocolVersion(bundles []*Bundle, installationID string) uint32 {
|
||||
func getProtocolVersion(bundles []*protobuf.Bundle, installationID string) uint32 {
|
||||
if installationID == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
for _, bundle := range bundles {
|
||||
if bundle != nil {
|
||||
signedPreKeys := bundle.GetSignedPreKeys()
|
||||
if signedPreKeys == nil {
|
||||
continue
|
||||
|
@ -243,6 +311,7 @@ func getProtocolVersion(bundles []*Bundle, installationID string) uint32 {
|
|||
|
||||
return signedPreKey.GetProtocolVersion()
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
@ -38,21 +39,35 @@ func (s *ProtocolServiceTestSuite) SetupTest() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
addedBundlesHandler := func(addedBundles []IdentityAndIDPair) {}
|
||||
onNewTopicHandler := func(topic []*topic.Secret) {}
|
||||
addedBundlesHandler := func(addedBundles []multidevice.IdentityAndIDPair) {}
|
||||
onNewSharedSecretHandler := func(secret []*sharedsecret.Secret) {}
|
||||
|
||||
aliceMultideviceConfig := &multidevice.Config{
|
||||
MaxInstallations: 3,
|
||||
InstallationID: "1",
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
}
|
||||
|
||||
s.alice = NewProtocolService(
|
||||
NewEncryptionService(alicePersistence, DefaultEncryptionServiceConfig("1")),
|
||||
topic.NewService(alicePersistence.GetTopicStorage()),
|
||||
sharedsecret.NewService(alicePersistence.GetSharedSecretStorage()),
|
||||
multidevice.New(aliceMultideviceConfig, alicePersistence.GetMultideviceStorage()),
|
||||
addedBundlesHandler,
|
||||
onNewTopicHandler,
|
||||
onNewSharedSecretHandler,
|
||||
)
|
||||
|
||||
bobMultideviceConfig := &multidevice.Config{
|
||||
MaxInstallations: 3,
|
||||
InstallationID: "2",
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
}
|
||||
|
||||
s.bob = NewProtocolService(
|
||||
NewEncryptionService(bobPersistence, DefaultEncryptionServiceConfig("2")),
|
||||
topic.NewService(bobPersistence.GetTopicStorage()),
|
||||
sharedsecret.NewService(bobPersistence.GetSharedSecretStorage()),
|
||||
multidevice.New(bobMultideviceConfig, bobPersistence.GetMultideviceStorage()),
|
||||
addedBundlesHandler,
|
||||
onNewTopicHandler,
|
||||
onNewSharedSecretHandler,
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -79,9 +94,12 @@ func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() {
|
|||
|
||||
payload := []byte("test")
|
||||
|
||||
msg, _, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(msg, "It creates a message")
|
||||
s.NotNil(msgSpec, "It creates a message spec")
|
||||
|
||||
msg := msgSpec.Message
|
||||
s.NotNil(msg, "It creates a messages")
|
||||
|
||||
s.NotNilf(msg.GetBundle(), "It adds a bundle to the message")
|
||||
|
||||
|
@ -95,6 +113,32 @@ func (s *ProtocolServiceTestSuite) TestBuildDirectMessage() {
|
|||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
payload := []byte("test")
|
||||
|
||||
// Message is sent with DH
|
||||
msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(msgSpec)
|
||||
|
||||
msg := msgSpec.Message
|
||||
s.Require().NotNil(msg)
|
||||
|
||||
// Bob is able to decrypt the message
|
||||
unmarshaledMsg, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, msg, []byte("message-id"))
|
||||
s.NoError(err)
|
||||
s.NotNil(unmarshaledMsg)
|
||||
|
||||
recoveredPayload := []byte("test")
|
||||
s.Equalf(payload, recoveredPayload, "It successfully unmarshal the decrypted message")
|
||||
}
|
||||
|
||||
func (s *ProtocolServiceTestSuite) TestSecretNegotiation() {
|
||||
var secretResponse []*sharedsecret.Secret
|
||||
bobKey, err := crypto.GenerateKey()
|
||||
s.NoError(err)
|
||||
aliceKey, err := crypto.GenerateKey()
|
||||
|
@ -102,17 +146,26 @@ func (s *ProtocolServiceTestSuite) TestBuildAndReadDirectMessage() {
|
|||
|
||||
payload := []byte("test")
|
||||
|
||||
// Message is sent with DH
|
||||
marshaledMsg, _, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.bob.onNewSharedSecretHandler = func(secret []*sharedsecret.Secret) {
|
||||
secretResponse = secret
|
||||
}
|
||||
msgSpec, err := s.alice.BuildDirectMessage(aliceKey, &bobKey.PublicKey, payload)
|
||||
s.NoError(err)
|
||||
s.NotNil(msgSpec, "It creates a message spec")
|
||||
|
||||
bundle := msgSpec.Message.GetBundle()
|
||||
s.Require().NotNil(bundle)
|
||||
|
||||
signedPreKeys := bundle.GetSignedPreKeys()
|
||||
s.Require().NotNil(signedPreKeys)
|
||||
|
||||
signedPreKey := signedPreKeys["1"]
|
||||
s.Require().NotNil(signedPreKey)
|
||||
|
||||
s.Require().Equal(uint32(1), signedPreKey.GetProtocolVersion())
|
||||
|
||||
_, err = s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, msgSpec.Message, []byte("message-id"))
|
||||
s.NoError(err)
|
||||
|
||||
// Bob is able to decrypt the message
|
||||
unmarshaledMsg, err := s.bob.HandleMessage(bobKey, &aliceKey.PublicKey, marshaledMsg, []byte("message-id"))
|
||||
s.NoError(err)
|
||||
|
||||
s.NotNil(unmarshaledMsg)
|
||||
|
||||
recoveredPayload := []byte("test")
|
||||
s.Equalf(payload, recoveredPayload, "It successfully unmarshal the decrypted message")
|
||||
s.Require().NotNil(secretResponse)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package topic
|
||||
package sharedsecret
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
@ -30,20 +30,20 @@ func (s *SQLLitePersistence) Add(identity []byte, secret []byte, installationID
|
|||
return err
|
||||
}
|
||||
|
||||
insertTopicStmt, err := tx.Prepare("INSERT INTO topics(identity, secret) VALUES (?, ?)")
|
||||
insertSecretStmt, err := tx.Prepare("INSERT INTO secrets(identity, secret) VALUES (?, ?)")
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
defer insertTopicStmt.Close()
|
||||
defer insertSecretStmt.Close()
|
||||
|
||||
_, err = insertTopicStmt.Exec(identity, secret)
|
||||
_, err = insertSecretStmt.Exec(identity, secret)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
insertInstallationIDStmt, err := tx.Prepare("INSERT INTO topic_installation_ids(id, identity_id) VALUES (?, ?)")
|
||||
insertInstallationIDStmt, err := tx.Prepare("INSERT INTO secret_installation_ids(id, identity_id) VALUES (?, ?)")
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
|
@ -70,9 +70,9 @@ func (s *SQLLitePersistence) Get(identity []byte, installationIDs []string) (*Re
|
|||
|
||||
/* #nosec */
|
||||
query := `SELECT secret, id
|
||||
FROM topics t
|
||||
FROM secrets t
|
||||
JOIN
|
||||
topic_installation_ids tid
|
||||
secret_installation_ids tid
|
||||
ON t.identity = tid.identity_id
|
||||
WHERE
|
||||
t.identity = ?
|
||||
|
@ -101,7 +101,7 @@ func (s *SQLLitePersistence) Get(identity []byte, installationIDs []string) (*Re
|
|||
|
||||
func (s *SQLLitePersistence) All() ([][][]byte, error) {
|
||||
query := `SELECT identity, secret
|
||||
FROM topics`
|
||||
FROM secrets`
|
||||
|
||||
var secrets [][][]byte
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package topic
|
||||
package sharedsecret
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
@ -17,8 +17,8 @@ func NewService(persistence PersistenceService) *Service {
|
|||
return &Service{persistence: persistence}
|
||||
}
|
||||
|
||||
func (s *Service) setupTopic(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
|
||||
log.Info("Setup topic called for", "installationID", installationID)
|
||||
func (s *Service) setup(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
|
||||
log.Info("Setup called for", "installationID", installationID)
|
||||
sharedKey, err := ecies.ImportECDSA(myPrivateKey).GenerateShared(
|
||||
ecies.ImportECDSAPublic(theirPublicKey),
|
||||
sskLen,
|
||||
|
@ -38,16 +38,20 @@ func (s *Service) setupTopic(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecd
|
|||
|
||||
// Receive will generate a shared secret for a given identity, and return it
|
||||
func (s *Service) Receive(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
|
||||
return s.setupTopic(myPrivateKey, theirPublicKey, installationID)
|
||||
return s.setup(myPrivateKey, theirPublicKey, installationID)
|
||||
}
|
||||
|
||||
// Send returns a shared key and whether it has been acknowledged from all the installationIDs
|
||||
func (s *Service) Send(myPrivateKey *ecdsa.PrivateKey, myInstallationID string, theirPublicKey *ecdsa.PublicKey, theirInstallationIDs []string) (*Secret, bool, error) {
|
||||
sharedKey, err := s.setupTopic(myPrivateKey, theirPublicKey, myInstallationID)
|
||||
secret, err := s.setup(myPrivateKey, theirPublicKey, myInstallationID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if len(theirInstallationIDs) == 0 {
|
||||
return secret, false, nil
|
||||
}
|
||||
|
||||
theirIdentity := crypto.CompressPubkey(theirPublicKey)
|
||||
response, err := s.persistence.Get(theirIdentity, theirInstallationIDs)
|
||||
if err != nil {
|
||||
|
@ -56,14 +60,11 @@ func (s *Service) Send(myPrivateKey *ecdsa.PrivateKey, myInstallationID string,
|
|||
|
||||
for _, installationID := range theirInstallationIDs {
|
||||
if !response.installationIDs[installationID] {
|
||||
return sharedKey, false, nil
|
||||
return secret, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &Secret{
|
||||
Key: response.secret,
|
||||
Identity: theirPublicKey,
|
||||
}, true, nil
|
||||
return secret, true, nil
|
||||
}
|
||||
|
||||
type Secret struct {
|
|
@ -1,4 +1,4 @@
|
|||
package topic
|
||||
package sharedsecret
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
@ -21,7 +21,7 @@ type ServiceTestSuite struct {
|
|||
}
|
||||
|
||||
func (s *ServiceTestSuite) SetupTest() {
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "topic")
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "sharedsecret")
|
||||
s.Require().NoError(err)
|
||||
s.path = dbFile.Name()
|
||||
|
||||
|
@ -103,12 +103,12 @@ func (s *ServiceTestSuite) TestAll() {
|
|||
s.Require().NoError(err)
|
||||
s.Require().NotNil(sharedKey2, "it generates a shared key")
|
||||
|
||||
// All the topics are there
|
||||
topics, err := s.service.All()
|
||||
// All the secrets are there
|
||||
secrets, err := s.service.All()
|
||||
s.Require().NoError(err)
|
||||
expected := []*Secret{
|
||||
sharedKey1,
|
||||
sharedKey2,
|
||||
}
|
||||
s.Require().Equal(expected, topics)
|
||||
s.Require().Equal(expected, secrets)
|
||||
}
|
|
@ -13,7 +13,9 @@ import (
|
|||
"github.com/status-im/migrate/v4/source/go_bindata"
|
||||
ecrypto "github.com/status-im/status-go/services/shhext/chat/crypto"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
)
|
||||
|
||||
// A safe max number of rows
|
||||
|
@ -24,7 +26,8 @@ type SQLLitePersistence struct {
|
|||
db *sql.DB
|
||||
keysStorage dr.KeysStorage
|
||||
sessionStorage dr.SessionStorage
|
||||
topicStorage topic.PersistenceService
|
||||
secretStorage sharedsecret.PersistenceService
|
||||
multideviceStorage multidevice.Persistence
|
||||
}
|
||||
|
||||
// SQLLiteKeysStorage represents a keys persistence service tied to an SQLite database
|
||||
|
@ -49,7 +52,9 @@ func NewSQLLitePersistence(path string, key string) (*SQLLitePersistence, error)
|
|||
|
||||
s.sessionStorage = NewSQLLiteSessionStorage(s.db)
|
||||
|
||||
s.topicStorage = topic.NewSQLLitePersistence(s.db)
|
||||
s.secretStorage = sharedsecret.NewSQLLitePersistence(s.db)
|
||||
|
||||
s.multideviceStorage = multidevice.NewSQLLitePersistence(s.db)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
@ -78,9 +83,14 @@ func (s *SQLLitePersistence) GetSessionStorage() dr.SessionStorage {
|
|||
return s.sessionStorage
|
||||
}
|
||||
|
||||
// GetTopicStorage returns the associated topicStorageObject
|
||||
func (s *SQLLitePersistence) GetTopicStorage() topic.PersistenceService {
|
||||
return s.topicStorage
|
||||
// GetSharedSecretStorage returns the associated secretStorageObject
|
||||
func (s *SQLLitePersistence) GetSharedSecretStorage() sharedsecret.PersistenceService {
|
||||
return s.secretStorage
|
||||
}
|
||||
|
||||
// GetMultideviceStorage returns the associated multideviceStorage
|
||||
func (s *SQLLitePersistence) GetMultideviceStorage() multidevice.Persistence {
|
||||
return s.multideviceStorage
|
||||
}
|
||||
|
||||
// Open opens a file at the specified path
|
||||
|
@ -96,7 +106,7 @@ func (s *SQLLitePersistence) Open(path string, key string) error {
|
|||
}
|
||||
|
||||
// AddPrivateBundle adds the specified BundleContainer to the database
|
||||
func (s *SQLLitePersistence) AddPrivateBundle(bc *BundleContainer) error {
|
||||
func (s *SQLLitePersistence) AddPrivateBundle(bc *protobuf.BundleContainer) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -150,7 +160,7 @@ func (s *SQLLitePersistence) AddPrivateBundle(bc *BundleContainer) error {
|
|||
}
|
||||
|
||||
// AddPublicBundle adds the specified Bundle to the database
|
||||
func (s *SQLLitePersistence) AddPublicBundle(b *Bundle) error {
|
||||
func (s *SQLLitePersistence) AddPublicBundle(b *protobuf.Bundle) error {
|
||||
tx, err := s.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
|
@ -203,7 +213,7 @@ func (s *SQLLitePersistence) AddPublicBundle(b *Bundle) error {
|
|||
}
|
||||
|
||||
// GetAnyPrivateBundle retrieves any bundle from the database containing a private key
|
||||
func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installations []*Installation) (*BundleContainer, error) {
|
||||
func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installations []*multidevice.Installation) (*protobuf.BundleContainer, error) {
|
||||
|
||||
versions := make(map[string]uint32)
|
||||
/* #nosec */
|
||||
|
@ -239,11 +249,11 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installat
|
|||
|
||||
defer rows.Close()
|
||||
|
||||
bundle := &Bundle{
|
||||
SignedPreKeys: make(map[string]*SignedPreKey),
|
||||
bundle := &protobuf.Bundle{
|
||||
SignedPreKeys: make(map[string]*protobuf.SignedPreKey),
|
||||
}
|
||||
|
||||
bundleContainer := &BundleContainer{
|
||||
bundleContainer := &protobuf.BundleContainer{
|
||||
Bundle: bundle,
|
||||
}
|
||||
|
||||
|
@ -267,7 +277,7 @@ func (s *SQLLitePersistence) GetAnyPrivateBundle(myIdentityKey []byte, installat
|
|||
bundle.Timestamp = timestamp
|
||||
}
|
||||
|
||||
bundle.SignedPreKeys[installationID] = &SignedPreKey{
|
||||
bundle.SignedPreKeys[installationID] = &protobuf.SignedPreKey{
|
||||
SignedPreKey: signedPreKey,
|
||||
Version: version,
|
||||
ProtocolVersion: versions[installationID],
|
||||
|
@ -323,7 +333,7 @@ func (s *SQLLitePersistence) MarkBundleExpired(identity []byte) error {
|
|||
}
|
||||
|
||||
// GetPublicBundle retrieves an existing Bundle for the specified public key from the database
|
||||
func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, installations []*Installation) (*Bundle, error) {
|
||||
func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, installations []*multidevice.Installation) (*protobuf.Bundle, error) {
|
||||
|
||||
if len(installations) == 0 {
|
||||
return nil, nil
|
||||
|
@ -360,9 +370,9 @@ func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, install
|
|||
|
||||
defer rows.Close()
|
||||
|
||||
bundle := &Bundle{
|
||||
bundle := &protobuf.Bundle{
|
||||
Identity: identity,
|
||||
SignedPreKeys: make(map[string]*SignedPreKey),
|
||||
SignedPreKeys: make(map[string]*protobuf.SignedPreKey),
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
|
@ -379,7 +389,7 @@ func (s *SQLLitePersistence) GetPublicBundle(publicKey *ecdsa.PublicKey, install
|
|||
return nil, err
|
||||
}
|
||||
|
||||
bundle.SignedPreKeys[installationID] = &SignedPreKey{
|
||||
bundle.SignedPreKeys[installationID] = &protobuf.SignedPreKey{
|
||||
SignedPreKey: signedPreKey,
|
||||
Version: version,
|
||||
ProtocolVersion: versions[installationID],
|
||||
|
@ -754,159 +764,6 @@ func (s *SQLLiteSessionStorage) Load(id []byte) (*dr.State, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetActiveInstallations returns the active installations for a given identity
|
||||
func (s *SQLLitePersistence) GetActiveInstallations(maxInstallations int, identity []byte) ([]*Installation, error) {
|
||||
stmt, err := s.db.Prepare(`SELECT installation_id, version
|
||||
FROM installations
|
||||
WHERE enabled = 1 AND identity = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var installations []*Installation
|
||||
rows, err := stmt.Query(identity, maxInstallations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var installationID string
|
||||
var version uint32
|
||||
err = rows.Scan(
|
||||
&installationID,
|
||||
&version,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installations = append(installations, &Installation{
|
||||
ID: installationID,
|
||||
Version: version,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return installations, nil
|
||||
|
||||
}
|
||||
|
||||
// AddInstallations adds the installations for a given identity, maintaining the enabled flag
|
||||
func (s *SQLLitePersistence) AddInstallations(identity []byte, timestamp int64, installations []*Installation, defaultEnabled bool) error {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, installation := range installations {
|
||||
stmt, err := tx.Prepare(`SELECT enabled, version
|
||||
FROM installations
|
||||
WHERE identity = ? AND installation_id = ?
|
||||
LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var oldEnabled bool
|
||||
// We don't override version once we saw one
|
||||
var oldVersion uint32
|
||||
latestVersion := installation.Version
|
||||
|
||||
err = stmt.QueryRow(identity, installation.ID).Scan(&oldEnabled, &oldVersion)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
// We update timestamp if present without changing enabled, only if this is a new bundle
|
||||
// and we set the version to the latest we ever saw
|
||||
if err != sql.ErrNoRows {
|
||||
if oldVersion > installation.Version {
|
||||
latestVersion = oldVersion
|
||||
}
|
||||
|
||||
stmt, err = tx.Prepare(`UPDATE installations
|
||||
SET timestamp = ?, enabled = ?, version = ?
|
||||
WHERE identity = ?
|
||||
AND installation_id = ?
|
||||
AND timestamp < ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(
|
||||
timestamp,
|
||||
oldEnabled,
|
||||
latestVersion,
|
||||
identity,
|
||||
installation.ID,
|
||||
timestamp,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
} else {
|
||||
stmt, err = tx.Prepare(`INSERT INTO installations(identity, installation_id, timestamp, enabled, version)
|
||||
VALUES (?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(
|
||||
identity,
|
||||
installation.ID,
|
||||
timestamp,
|
||||
defaultEnabled,
|
||||
latestVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// EnableInstallation enables the installation
|
||||
func (s *SQLLitePersistence) EnableInstallation(identity []byte, installationID string) error {
|
||||
stmt, err := s.db.Prepare(`UPDATE installations
|
||||
SET enabled = 1
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(identity, installationID)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// DisableInstallation disable the installation
|
||||
func (s *SQLLitePersistence) DisableInstallation(identity []byte, installationID string) error {
|
||||
|
||||
stmt, err := s.db.Prepare(`UPDATE installations
|
||||
SET enabled = 0
|
||||
WHERE identity = ? AND installation_id = ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(identity, installationID)
|
||||
return err
|
||||
}
|
||||
|
||||
func toKey(a []byte) dr.Key {
|
||||
var k [32]byte
|
||||
copy(k[:], a)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
@ -53,7 +54,7 @@ func (s *SQLLitePersistenceTestSuite) TestPrivateBundle() {
|
|||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualKey)
|
||||
|
||||
anyPrivateBundle, err := s.service.GetAnyPrivateBundle([]byte("non-existing-id"), []*Installation{{ID: installationID, Version: 1}})
|
||||
anyPrivateBundle, err := s.service.GetAnyPrivateBundle([]byte("non-existing-id"), []*multidevice.Installation{{ID: installationID, Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Nil(anyPrivateBundle)
|
||||
|
||||
|
@ -70,7 +71,7 @@ func (s *SQLLitePersistenceTestSuite) TestPrivateBundle() {
|
|||
s.Equal(bundle.GetPrivateSignedPreKey(), actualKey, "It returns the same key")
|
||||
|
||||
identity := crypto.CompressPubkey(&key.PublicKey)
|
||||
anyPrivateBundle, err = s.service.GetAnyPrivateBundle(identity, []*Installation{{ID: installationID, Version: 1}})
|
||||
anyPrivateBundle, err = s.service.GetAnyPrivateBundle(identity, []*multidevice.Installation{{ID: installationID, Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.NotNil(anyPrivateBundle)
|
||||
s.Equal(bundle.GetBundle().GetSignedPreKeys()[installationID].SignedPreKey, anyPrivateBundle.GetBundle().GetSignedPreKeys()[installationID].SignedPreKey, "It returns the same bundle")
|
||||
|
@ -80,7 +81,7 @@ func (s *SQLLitePersistenceTestSuite) TestPublicBundle() {
|
|||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
|
@ -91,7 +92,7 @@ func (s *SQLLitePersistenceTestSuite) TestPublicBundle() {
|
|||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
|
||||
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the right prekeys")
|
||||
|
@ -101,7 +102,7 @@ func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() {
|
|||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
|
@ -123,7 +124,7 @@ func (s *SQLLitePersistenceTestSuite) TestUpdatedBundle() {
|
|||
err = s.service.AddPublicBundle(bundle)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
|
||||
s.Equal(bundle.GetSignedPreKeys(), actualBundle.GetSignedPreKeys(), "It sets the right prekeys")
|
||||
|
@ -133,7 +134,7 @@ func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() {
|
|||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
|
@ -160,7 +161,7 @@ func (s *SQLLitePersistenceTestSuite) TestOutOfOrderBundles() {
|
|||
err = s.service.AddPublicBundle(bundle1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
s.Equal(bundle2.GetIdentity(), actualBundle.GetIdentity(), "It sets the right identity")
|
||||
s.Equal(bundle2.GetSignedPreKeys()["1"].GetVersion(), uint32(1))
|
||||
|
@ -171,7 +172,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() {
|
|||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
|
@ -197,7 +198,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiplePublicBundle() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Returns the most recent bundle
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Equal(bundle.GetIdentity(), actualBundle.GetIdentity(), "It sets the identity")
|
||||
|
@ -209,7 +210,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() {
|
|||
key, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*Installation{{ID: "1", Version: 1}})
|
||||
actualBundle, err := s.service.GetPublicBundle(&key.PublicKey, []*multidevice.Installation{{ID: "1", Version: 1}})
|
||||
s.Require().NoError(err, "Error was not returned even though bundle is not there")
|
||||
s.Nil(actualBundle)
|
||||
|
||||
|
@ -234,7 +235,7 @@ func (s *SQLLitePersistenceTestSuite) TestMultiDevicePublicBundle() {
|
|||
|
||||
// Returns the most recent bundle
|
||||
actualBundle, err = s.service.GetPublicBundle(&key.PublicKey,
|
||||
[]*Installation{
|
||||
[]*multidevice.Installation{
|
||||
{ID: "1", Version: 1},
|
||||
{ID: "2", Version: 1},
|
||||
})
|
||||
|
@ -347,211 +348,4 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoNoBundle() {
|
|||
s.Nil(ratchetInfo, "It returns nil when no bundle is there")
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallations() {
|
||||
identity := []byte("alice")
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationVersions() {
|
||||
identity := []byte("alice")
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
}
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
|
||||
installationsWithDowngradedVersion := []*Installation{
|
||||
{ID: "alice-1", Version: 0},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
3,
|
||||
installationsWithDowngradedVersion,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err = s.service.GetActiveInstallations(5, identity)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsLimit() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
installations = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-3", Version: 3},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
2,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
installations = []*Installation{
|
||||
{ID: "alice-2", Version: 2},
|
||||
{ID: "alice-3", Version: 3},
|
||||
{ID: "alice-4", Version: 4},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
3,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
enabledInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Equal(installations, enabledInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestAddInstallationsDisabled() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
false,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Nil(actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestDisableInstallation() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add the installations again
|
||||
installations = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err = s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{{ID: "alice-2", Version: 2}}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
}
|
||||
|
||||
func (s *SQLLitePersistenceTestSuite) TestEnableInstallation() {
|
||||
identity := []byte("alice")
|
||||
|
||||
installations := []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
|
||||
err := s.service.AddInstallations(
|
||||
identity,
|
||||
1,
|
||||
installations,
|
||||
true,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.DisableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err := s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected := []*Installation{{ID: "alice-2", Version: 2}}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
|
||||
err = s.service.EnableInstallation(identity, "alice-1")
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualInstallations, err = s.service.GetActiveInstallations(3, identity)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expected = []*Installation{
|
||||
{ID: "alice-1", Version: 1},
|
||||
{ID: "alice-2", Version: 2},
|
||||
}
|
||||
s.Require().Equal(expected, actualInstallations)
|
||||
|
||||
}
|
||||
|
||||
// TODO: Add test for MarkBundleExpired
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
var discoveryTopic = "contact-discovery"
|
||||
var discoveryTopicBytes = toTopic(discoveryTopic)
|
||||
|
||||
var topicSalt = []byte{0x01, 0x02, 0x03, 0x04}
|
||||
|
||||
func toTopic(s string) whisper.TopicType {
|
||||
return whisper.BytesToTopic(crypto.Keccak256([]byte(s)))
|
||||
}
|
||||
|
||||
func SharedSecretToTopic(secret []byte) whisper.TopicType {
|
||||
return whisper.BytesToTopic(crypto.Keccak256(append(secret, topicSalt...)))
|
||||
}
|
||||
|
||||
func defaultWhisperMessage() whisper.NewMessage {
|
||||
msg := whisper.NewMessage{}
|
||||
|
||||
msg.TTL = 10
|
||||
msg.PowTarget = 0.002
|
||||
msg.PowTime = 1
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func PublicMessageToWhisper(rpcMsg SendPublicMessageRPC, payload []byte) whisper.NewMessage {
|
||||
msg := defaultWhisperMessage()
|
||||
|
||||
msg.Topic = toTopic(rpcMsg.Chat)
|
||||
|
||||
msg.Payload = payload
|
||||
msg.Sig = rpcMsg.Sig
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func DirectMessageToWhisper(rpcMsg SendDirectMessageRPC, payload []byte, sharedSecret []byte) whisper.NewMessage {
|
||||
var topicBytes whisper.TopicType
|
||||
msg := defaultWhisperMessage()
|
||||
|
||||
if rpcMsg.Chat == "" {
|
||||
if sharedSecret != nil {
|
||||
topicBytes = SharedSecretToTopic(sharedSecret)
|
||||
} else {
|
||||
topicBytes = discoveryTopicBytes
|
||||
msg.PublicKey = rpcMsg.PubKey
|
||||
}
|
||||
} else {
|
||||
topicBytes = toTopic(rpcMsg.Chat)
|
||||
msg.PublicKey = rpcMsg.PubKey
|
||||
}
|
||||
|
||||
msg.Topic = topicBytes
|
||||
|
||||
msg.Payload = payload
|
||||
msg.Sig = rpcMsg.Sig
|
||||
|
||||
return msg
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPublicMessageToWhisper(t *testing.T) {
|
||||
rpcMessage := SendPublicMessageRPC{
|
||||
Chat: "test-chat",
|
||||
Sig: "test",
|
||||
}
|
||||
|
||||
payload := []byte("test")
|
||||
whisperMessage := PublicMessageToWhisper(rpcMessage, payload)
|
||||
|
||||
assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL")
|
||||
assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target")
|
||||
assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time")
|
||||
assert.Equalf(t, whisper.TopicType{0xa4, 0xab, 0xdf, 0x64}, whisperMessage.Topic, "It sets the topic")
|
||||
}
|
||||
|
||||
func TestDirectMessageToWhisper(t *testing.T) {
|
||||
rpcMessage := SendDirectMessageRPC{
|
||||
PubKey: []byte("some pubkey"),
|
||||
Sig: "test",
|
||||
}
|
||||
|
||||
payload := []byte("test")
|
||||
whisperMessage := DirectMessageToWhisper(rpcMessage, payload, nil)
|
||||
|
||||
assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL")
|
||||
assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target")
|
||||
assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time")
|
||||
assert.Equalf(t, whisper.TopicType{0xf8, 0x94, 0x6a, 0xac}, whisperMessage.Topic, "It sets the discovery topic")
|
||||
}
|
||||
|
||||
func TestDirectMessageToWhisperWithSharedSecret(t *testing.T) {
|
||||
rpcMessage := SendDirectMessageRPC{
|
||||
PubKey: []byte("some pubkey"),
|
||||
Sig: "test",
|
||||
}
|
||||
|
||||
payload := []byte("test")
|
||||
secret := []byte("test-secret")
|
||||
|
||||
whisperMessage := DirectMessageToWhisper(rpcMessage, payload, secret)
|
||||
|
||||
assert.Equalf(t, uint32(10), whisperMessage.TTL, "It sets the TTL")
|
||||
assert.Equalf(t, 0.002, whisperMessage.PowTarget, "It sets the pow target")
|
||||
assert.Equalf(t, uint32(1), whisperMessage.PowTime, "It sets the pow time")
|
||||
assert.Equalf(t, whisper.TopicType{0xd8, 0xa2, 0xf3, 0x64}, whisperMessage.Topic, "It sets the discovery topic")
|
||||
}
|
|
@ -2,16 +2,14 @@ package chat
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -19,33 +17,7 @@ const (
|
|||
sskLen = 16
|
||||
)
|
||||
|
||||
// ToBase64 returns a Base64 encoding representation of the protobuf Bundle message
|
||||
func (bundle *Bundle) ToBase64() (string, error) {
|
||||
marshaledMessage, err := proto.Marshal(bundle)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(marshaledMessage), nil
|
||||
}
|
||||
|
||||
// FromBase64 unmarshals a Bundle from a Base64 encoding representation of the protobuf Bundle message
|
||||
func FromBase64(str string) (*Bundle, error) {
|
||||
bundle := &Bundle{}
|
||||
|
||||
decodedBundle, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := proto.Unmarshal(decodedBundle, bundle); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bundle, nil
|
||||
}
|
||||
|
||||
func buildSignatureMaterial(bundle *Bundle) []byte {
|
||||
func buildSignatureMaterial(bundle *protobuf.Bundle) []byte {
|
||||
signedPreKeys := bundle.GetSignedPreKeys()
|
||||
timestamp := bundle.GetTimestamp()
|
||||
var keys []string
|
||||
|
@ -73,7 +45,7 @@ func buildSignatureMaterial(bundle *Bundle) []byte {
|
|||
|
||||
}
|
||||
|
||||
func SignBundle(identity *ecdsa.PrivateKey, bundleContainer *BundleContainer) error {
|
||||
func SignBundle(identity *ecdsa.PrivateKey, bundleContainer *protobuf.BundleContainer) error {
|
||||
signatureMaterial := buildSignatureMaterial(bundleContainer.GetBundle())
|
||||
|
||||
signature, err := crypto.Sign(crypto.Keccak256(signatureMaterial), identity)
|
||||
|
@ -85,7 +57,7 @@ func SignBundle(identity *ecdsa.PrivateKey, bundleContainer *BundleContainer) er
|
|||
}
|
||||
|
||||
// NewBundleContainer creates a new BundleContainer from an identity private key
|
||||
func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*BundleContainer, error) {
|
||||
func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*protobuf.BundleContainer, error) {
|
||||
preKey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -95,35 +67,35 @@ func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*Bun
|
|||
compressedIdentityKey := crypto.CompressPubkey(&identity.PublicKey)
|
||||
|
||||
encodedPreKey := crypto.FromECDSA(preKey)
|
||||
signedPreKeys := make(map[string]*SignedPreKey)
|
||||
signedPreKeys[installationID] = &SignedPreKey{
|
||||
ProtocolVersion: protocolCurrentVersion,
|
||||
signedPreKeys := make(map[string]*protobuf.SignedPreKey)
|
||||
signedPreKeys[installationID] = &protobuf.SignedPreKey{
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
SignedPreKey: compressedPreKey,
|
||||
}
|
||||
|
||||
bundle := Bundle{
|
||||
bundle := protobuf.Bundle{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Identity: compressedIdentityKey,
|
||||
SignedPreKeys: signedPreKeys,
|
||||
}
|
||||
|
||||
return &BundleContainer{
|
||||
return &protobuf.BundleContainer{
|
||||
Bundle: &bundle,
|
||||
PrivateSignedPreKey: encodedPreKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyBundle checks that a bundle is valid
|
||||
func VerifyBundle(bundle *Bundle) error {
|
||||
func VerifyBundle(bundle *protobuf.Bundle) error {
|
||||
_, err := ExtractIdentity(bundle)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExtractIdentity extracts the identity key from a given bundle
|
||||
func ExtractIdentity(bundle *Bundle) (string, error) {
|
||||
func ExtractIdentity(bundle *protobuf.Bundle) (*ecdsa.PublicKey, error) {
|
||||
bundleIdentityKey, err := crypto.DecompressPubkey(bundle.GetIdentity())
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatureMaterial := buildSignatureMaterial(bundle)
|
||||
|
@ -133,14 +105,14 @@ func ExtractIdentity(bundle *Bundle) (string, error) {
|
|||
bundle.GetSignature(),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if crypto.PubkeyToAddress(*recoveredKey) != crypto.PubkeyToAddress(*bundleIdentityKey) {
|
||||
return "", errors.New("identity key and signature mismatch")
|
||||
return nil, errors.New("identity key and signature mismatch")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("0x%x", crypto.FromECDSAPub(recoveredKey)), nil
|
||||
return recoveredKey, nil
|
||||
}
|
||||
|
||||
// PerformDH generates a shared key given a private and a public key
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -14,12 +15,11 @@ const (
|
|||
aliceEphemeralKey = "11111111111111111111111111111111"
|
||||
bobPrivateKey = "22222222222222222222222222222222"
|
||||
bobSignedPreKey = "33333333333333333333333333333333"
|
||||
base64Bundle = "CiECkJmdu/QwNL/7HdU+rB60wzpOocT0i6WFz944MIQPBVUSKAoBMhIjCiECPHKt20/fCa+U8MlNf+kqOGp+cM+KHYWRY4a7JTXHsbEiQT9Wse3UkJgo6/1HzxQfHcZNBaMH0j+0eylfBf1ropsLZ7yZM98k/qDQ3ZW5uHXQ4zhY8E1Q7HDytqm62k5JIPYA"
|
||||
)
|
||||
|
||||
var sharedKey = []byte{0xa4, 0xe9, 0x23, 0xd0, 0xaf, 0x8f, 0xe7, 0x8a, 0x5, 0x63, 0x63, 0xbe, 0x20, 0xe7, 0x1c, 0xa, 0x58, 0xe5, 0x69, 0xea, 0x8f, 0xc1, 0xf7, 0x92, 0x89, 0xec, 0xa1, 0xd, 0x9f, 0x68, 0x13, 0x3a}
|
||||
|
||||
func bobBundle() (*Bundle, error) {
|
||||
func bobBundle() (*protobuf.Bundle, error) {
|
||||
privateKey, err := crypto.ToECDSA([]byte(bobPrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -37,10 +37,10 @@ func bobBundle() (*Bundle, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
signedPreKeys := make(map[string]*SignedPreKey)
|
||||
signedPreKeys[bobInstallationID] = &SignedPreKey{SignedPreKey: compressedPreKey}
|
||||
signedPreKeys := make(map[string]*protobuf.SignedPreKey)
|
||||
signedPreKeys[bobInstallationID] = &protobuf.SignedPreKey{SignedPreKey: compressedPreKey}
|
||||
|
||||
bundle := Bundle{
|
||||
bundle := protobuf.Bundle{
|
||||
Identity: crypto.CompressPubkey(&privateKey.PublicKey),
|
||||
SignedPreKeys: signedPreKeys,
|
||||
Signature: signature,
|
||||
|
@ -76,8 +76,8 @@ func TestNewBundleContainer(t *testing.T) {
|
|||
|
||||
require.Equal(
|
||||
t,
|
||||
&privateKey.PublicKey,
|
||||
recoveredPublicKey,
|
||||
privateKey.PublicKey,
|
||||
*recoveredPublicKey,
|
||||
"The correct public key should be recovered",
|
||||
)
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ func TestSignBundle(t *testing.T) {
|
|||
|
||||
// We add a signed pre key
|
||||
signedPreKeys := bundle1.GetSignedPreKeys()
|
||||
signedPreKeys["2"] = &SignedPreKey{SignedPreKey: []byte("key")}
|
||||
signedPreKeys["2"] = &protobuf.SignedPreKey{SignedPreKey: []byte("key")}
|
||||
|
||||
err = SignBundle(privateKey, bundleContainer1)
|
||||
require.NoError(t, err)
|
||||
|
@ -115,40 +115,12 @@ func TestSignBundle(t *testing.T) {
|
|||
|
||||
require.Equal(
|
||||
t,
|
||||
&privateKey.PublicKey,
|
||||
recoveredPublicKey,
|
||||
privateKey.PublicKey,
|
||||
*recoveredPublicKey,
|
||||
"The correct public key should be recovered",
|
||||
)
|
||||
}
|
||||
|
||||
func TestToBase64(t *testing.T) {
|
||||
bundle, err := bobBundle()
|
||||
require.NoError(t, err, "Test bundle should be generated without errors")
|
||||
|
||||
actualBase64Bundle, err := bundle.ToBase64()
|
||||
require.NoError(t, err, "No error should be reported")
|
||||
require.Equal(
|
||||
t,
|
||||
base64Bundle,
|
||||
actualBase64Bundle,
|
||||
"The correct bundle should be generated",
|
||||
)
|
||||
}
|
||||
|
||||
func TestFromBase64(t *testing.T) {
|
||||
expectedBundle, err := bobBundle()
|
||||
require.NoError(t, err, "Test bundle should be generated without errors")
|
||||
|
||||
actualBundle, err := FromBase64(base64Bundle)
|
||||
require.NoError(t, err, "Bundle should be unmarshaled without errors")
|
||||
require.Equal(
|
||||
t,
|
||||
expectedBundle,
|
||||
actualBundle,
|
||||
"The correct bundle should be generated",
|
||||
)
|
||||
}
|
||||
|
||||
func TestExtractIdentity(t *testing.T) {
|
||||
privateKey, err := crypto.ToECDSA([]byte(alicePrivateKey))
|
||||
require.NoError(t, err, "Private key should be generated without errors")
|
||||
|
@ -168,8 +140,8 @@ func TestExtractIdentity(t *testing.T) {
|
|||
|
||||
require.Equal(
|
||||
t,
|
||||
"0x042ed557f5ad336b31a49857e4e9664954ac33385aa20a93e2d64bfe7f08f51277bcb27c1259f802a52ed3ea7ac939043f0cc864e27400294bf121f23877995852",
|
||||
recoveredPublicKey,
|
||||
privateKey.PublicKey,
|
||||
*recoveredPublicKey,
|
||||
"The correct public key should be recovered",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ package filter
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
@ -18,79 +19,262 @@ const (
|
|||
|
||||
// The number of partitions
|
||||
var nPartitions = big.NewInt(5000)
|
||||
|
||||
func toTopic(s string) []byte {
|
||||
return crypto.Keccak256([]byte(s))[:whisper.TopicLength]
|
||||
}
|
||||
|
||||
func chatIDToPartitionedTopic(identity string) (string, error) {
|
||||
partition := big.NewInt(0)
|
||||
publicKeyBytes, err := hex.DecodeString(identity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
partition.Mod(publicKey.X, nPartitions)
|
||||
|
||||
return fmt.Sprintf("contact-discovery-%d", partition.Int64()), nil
|
||||
}
|
||||
var minPow = 0.0
|
||||
|
||||
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 {
|
||||
keyID string
|
||||
whisper *whisper.Whisper
|
||||
topic *topic.Service
|
||||
secret *sharedsecret.Service
|
||||
chats map[string]*Chat
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func New(k string, w *whisper.Whisper, t *topic.Service) *Service {
|
||||
// New returns a new filter service
|
||||
func New(w *whisper.Whisper, s *sharedsecret.Service) *Service {
|
||||
return &Service{
|
||||
keyID: k,
|
||||
whisper: w,
|
||||
topic: t,
|
||||
secret: s,
|
||||
mutex: sync.Mutex{},
|
||||
chats: make(map[string]*Chat),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadDiscovery adds the discovery filter
|
||||
func (s *Service) LoadDiscovery(myKey *ecdsa.PrivateKey) error {
|
||||
// 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")
|
||||
}
|
||||
myKey, err := s.whisper.GetPrivateKey(keyID)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error("Error loading one to one chats", "err", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add discovery topic
|
||||
log.Debug("Loading discovery topics")
|
||||
err = s.loadDiscovery(myKey)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the negotiated secrets
|
||||
log.Debug("Loading negotiated topics")
|
||||
secrets, err := s.secret.All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, secret := range secrets {
|
||||
if _, err := s.ProcessNegotiatedSecret(secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
discoveryChat := &Chat{
|
||||
ChatID: discoveryTopic,
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
||||
if err := s.whisper.Unsubscribe(chat.FilterID); err != nil {
|
||||
return err
|
||||
}
|
||||
if chat.SymKeyID != "" {
|
||||
s.whisper.DeleteSymKey(chat.SymKeyID)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
discoveryResponse, err := s.AddAsymmetricFilter(myKey, discoveryChat.ChatID, true)
|
||||
// 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) {
|
||||
keyID := s.whisper.SelectedKeyPairID()
|
||||
if keyID == "" {
|
||||
return nil, errors.New("no key selected")
|
||||
}
|
||||
myKey, err := s.whisper.GetPrivateKey(keyID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.load(myKey, chat)
|
||||
}
|
||||
|
||||
// Get returns a negotiated filter given an identity
|
||||
func (s *Service) GetNegotiated(identity *ecdsa.PublicKey) *Chat {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
return s.chats[negotiatedID(identity)]
|
||||
}
|
||||
|
||||
// GetByID returns a filter by chatID
|
||||
func (s *Service) GetByID(chatID string) *Chat {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
return s.chats[chatID]
|
||||
}
|
||||
|
||||
// ProcessNegotiatedSecret adds a filter based on the agreed secret
|
||||
func (s *Service) ProcessNegotiatedSecret(secret *sharedsecret.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 nil, err
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
log.Info("PROCESSING SECRET", "chat-id", chatID, "topic", filter.Topic, "symKey", keyString)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
discoveryChat := &Chat{
|
||||
ChatID: discoveryTopic,
|
||||
Listen: true,
|
||||
}
|
||||
|
||||
discoveryResponse, err := s.addAsymmetricFilter(myKey, discoveryChat.ChatID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -102,125 +286,93 @@ func (s *Service) LoadDiscovery(myKey *ecdsa.PrivateKey) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Init(chats []*Chat) error {
|
||||
log.Debug("Initializing filter service")
|
||||
myKey, err := s.whisper.GetPrivateKey(s.keyID)
|
||||
if err != nil {
|
||||
return 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)
|
||||
if err != nil {
|
||||
log.Error("Error loading one to one chats", "err", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Add discovery topic
|
||||
log.Debug("Loading discovery topics")
|
||||
err = s.LoadDiscovery(myKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the various one to one and public chats
|
||||
log.Debug("Loading chats")
|
||||
for _, chat := range chats {
|
||||
err = s.Load(myKey, chat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the negotiated topics
|
||||
log.Debug("Loading negotiated topics")
|
||||
secrets, err := s.topic.All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, secret := range secrets {
|
||||
if err := s.ProcessNegotiatedSecret(secret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
for _, chat := range s.chats {
|
||||
if err := s.Remove(chat); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Remove(chat *Chat) error {
|
||||
// loadPublic adds a filter for a public chat
|
||||
func (s *Service) loadPublic(chat *Chat) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if err := s.whisper.Unsubscribe(chat.ChatID); err != nil {
|
||||
return err
|
||||
}
|
||||
if chat.SymKeyID != "" {
|
||||
s.whisper.DeleteSymKey(chat.SymKeyID)
|
||||
}
|
||||
delete(s.chats, chat.ChatID)
|
||||
|
||||
if _, ok := s.chats[chat.ChatID]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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 {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
contactCodeChatID := identity + "-contact-code"
|
||||
contactCodeFilter, err := s.AddSymmetric(contactCodeChatID)
|
||||
filterAndTopic, err := s.addSymmetric(chat.ChatID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.chats[contactCodeChatID] = &Chat{
|
||||
ChatID: contactCodeChatID,
|
||||
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 := "0x" + 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: true,
|
||||
}
|
||||
|
||||
partitionedTopicChatID, err := chatIDToPartitionedTopic(identity)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.chats[partitionedTopicChatID] = &Chat{
|
||||
ChatID: partitionedTopicChatID,
|
||||
FilterID: partitionedTopicFilter.FilterID,
|
||||
Topic: partitionedTopicFilter.Topic,
|
||||
Identity: identity,
|
||||
Listen: listen,
|
||||
}
|
||||
|
||||
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)
|
||||
topic := ToTopic(chatID)
|
||||
topics := [][]byte{topic}
|
||||
|
||||
symKeyID, err := s.whisper.AddSymKeyFromPassword(chatID)
|
||||
|
@ -235,7 +387,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(),
|
||||
|
@ -249,28 +401,29 @@ 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
|
||||
}
|
||||
|
||||
topic := toTopic(chatID)
|
||||
topic := ToTopic(chatID)
|
||||
topics := [][]byte{topic}
|
||||
|
||||
f := &whisper.Filter{
|
||||
KeyAsym: keyAsym,
|
||||
PoW: pow,
|
||||
AllowP2P: listen,
|
||||
AllowP2P: true,
|
||||
Topics: topics,
|
||||
Messages: s.whisper.NewMessageStore(),
|
||||
}
|
||||
|
@ -280,73 +433,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))
|
||||
return fmt.Sprintf("0x%x-negotiated", crypto.FromECDSAPub(identity))
|
||||
}
|
||||
|
||||
func (s *Service) Get(identity *ecdsa.PublicKey) *Chat {
|
||||
return s.chats[negotiatedID(identity)]
|
||||
}
|
||||
func (s *Service) load(myKey *ecdsa.PrivateKey, chat *Chat) ([]*Chat, error) {
|
||||
log.Debug("Loading chat", "chatID", chat.ChatID)
|
||||
|
||||
func (s *Service) ProcessNegotiatedSecret(secret *topic.Secret) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
if chat.OneToOne {
|
||||
return s.loadOneToOne(myKey, chat.Identity, false)
|
||||
|
||||
keyString := fmt.Sprintf("%x", secret.Key)
|
||||
filter, err := s.AddSymmetric(keyString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identityStr := fmt.Sprintf("0x%x", crypto.FromECDSAPub(secret.Identity))
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
@ -51,7 +51,7 @@ func (s *ServiceTestSuite) SetupTest() {
|
|||
keyStrs := []string{"c6cbd7d76bc5baca530c875663711b947efa6a86a900a9e8645ce32e5821484e", "d51dd64ad19ea84968a308dca246012c00d2b2101d41bce740acd1c650acc509"}
|
||||
keyTopics := []int{4490, 3991}
|
||||
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "topic")
|
||||
dbFile, err := ioutil.TempFile(os.TempDir(), "filter")
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.path = dbFile.Name()
|
||||
|
@ -67,12 +67,12 @@ func (s *ServiceTestSuite) SetupTest() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
// Build services
|
||||
topicService := topic.NewService(topic.NewSQLLitePersistence(db))
|
||||
sharedSecretService := sharedsecret.NewService(sharedsecret.NewSQLLitePersistence(db))
|
||||
whisper := whisper.New(nil)
|
||||
keyID, err := whisper.AddKeyPair(s.keys[0].privateKey)
|
||||
_, err = whisper.AddKeyPair(s.keys[0].privateKey)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.service = New(keyID, whisper, topicService)
|
||||
s.service = New(whisper, sharedSecretService)
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TearDownTest() {
|
||||
|
@ -82,21 +82,24 @@ func (s *ServiceTestSuite) TearDownTest() {
|
|||
func (s *ServiceTestSuite) TestDiscoveryAndPartitionedTopic() {
|
||||
chats := []*Chat{}
|
||||
partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[0].partitionedTopic)
|
||||
contactCodeTopic := s.keys[0].PublicKeyString() + "-contact-code"
|
||||
contactCodeTopic := "0x" + 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,46 +113,95 @@ func (s *ServiceTestSuite) TestPublicAndOneToOneChats() {
|
|||
OneToOne: true,
|
||||
},
|
||||
}
|
||||
partitionedTopic := fmt.Sprintf("contact-discovery-%d", s.keys[1].partitionedTopic)
|
||||
contactCodeTopic := s.keys[1].PublicKeyString() + "-contact-code"
|
||||
contactCodeTopic := "0x" + s.keys[1].PublicKeyString() + "-contact-code"
|
||||
|
||||
err := s.service.Init(chats)
|
||||
response, 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")
|
||||
actualChats := make(map[string]*Chat)
|
||||
|
||||
statusFilter := s.service.chats["status"]
|
||||
for _, chat := range response {
|
||||
actualChats[chat.ChatID] = chat
|
||||
}
|
||||
|
||||
s.Require().Equal(5, len(actualChats), "It creates two additional filters for the one to one and one for the public chat")
|
||||
|
||||
statusFilter := actualChats["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]
|
||||
contactCodeFilter := actualChats[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() {
|
||||
chats := []*Chat{}
|
||||
|
||||
negotiatedTopic1 := s.keys[0].PublicKeyString() + "-negotiated"
|
||||
negotiatedTopic2 := s.keys[1].PublicKeyString() + "-negotiated"
|
||||
negotiatedTopic1 := "0x" + s.keys[0].PublicKeyString() + "-negotiated"
|
||||
negotiatedTopic2 := "0x" + s.keys[1].PublicKeyString() + "-negotiated"
|
||||
|
||||
// We send a message to ourselves
|
||||
_, _, err := s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[0].privateKey.PublicKey, []string{"0-2"})
|
||||
_, _, err := s.service.secret.Send(s.keys[0].privateKey, "0-1", &s.keys[0].privateKey.PublicKey, []string{"0-2"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We send a message to someone else
|
||||
_, _, err = s.service.topic.Send(s.keys[0].privateKey, "0-1", &s.keys[1].privateKey.PublicKey, []string{"0-2"})
|
||||
_, _, err = s.service.secret.Send(s.keys[0].privateKey, "0-1", &s.keys[1].privateKey.PublicKey, []string{"0-2"})
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.Init(chats)
|
||||
response, 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")
|
||||
actualChats := make(map[string]*Chat)
|
||||
|
||||
negotiatedFilter1 := s.service.chats[negotiatedTopic1]
|
||||
for _, chat := range response {
|
||||
actualChats[chat.ChatID] = chat
|
||||
}
|
||||
|
||||
s.Require().Equal(5, len(actualChats), "It creates two additional filters for the negotiated topics")
|
||||
|
||||
negotiatedFilter1 := actualChats[negotiatedTopic1]
|
||||
s.Require().NotNil(negotiatedFilter1, "It adds the negotiated filter")
|
||||
negotiatedFilter2 := s.service.chats[negotiatedTopic2]
|
||||
negotiatedFilter2 := actualChats[negotiatedTopic2]
|
||||
s.Require().NotNil(negotiatedFilter2, "It adds the negotiated filter")
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestLoadChat() {
|
||||
chats := []*Chat{}
|
||||
|
||||
_, err := s.service.Init(chats)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// We add a public chat
|
||||
response1, err := s.service.Load(&Chat{ChatID: "status"})
|
||||
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(1, len(response1))
|
||||
s.Require().Equal("status", response1[0].ChatID)
|
||||
s.Require().True(response1[0].Listen)
|
||||
}
|
||||
|
||||
func (s *ServiceTestSuite) TestNoInstallationIDs() {
|
||||
chats := []*Chat{}
|
||||
|
||||
negotiatedTopic1 := "0x" + s.keys[1].PublicKeyString() + "-negotiated"
|
||||
|
||||
// We send a message to someone else, but without any installation ID
|
||||
_, _, err := s.service.secret.Send(s.keys[0].privateKey, "0-1", &s.keys[1].privateKey.PublicKey, []string{})
|
||||
s.Require().NoError(err)
|
||||
|
||||
response, err := s.service.Init(chats)
|
||||
s.Require().NoError(err)
|
||||
|
||||
actualChats := make(map[string]*Chat)
|
||||
|
||||
for _, chat := range response {
|
||||
actualChats[chat.ChatID] = chat
|
||||
}
|
||||
|
||||
s.Require().Equal(4, len(actualChats), "It creates two additional filters for the negotiated topics")
|
||||
|
||||
negotiatedFilter1 := actualChats[negotiatedTopic1]
|
||||
s.Require().NotNil(negotiatedFilter1, "It adds the negotiated filter")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,446 @@
|
|||
package publisher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/multidevice"
|
||||
"github.com/status-im/status-go/services/shhext/chat/protobuf"
|
||||
"github.com/status-im/status-go/services/shhext/chat/sharedsecret"
|
||||
"github.com/status-im/status-go/services/shhext/dedup"
|
||||
"github.com/status-im/status-go/services/shhext/filter"
|
||||
"github.com/status-im/status-go/services/shhext/whisperutils"
|
||||
"github.com/status-im/status-go/signal"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tickerInterval = 120
|
||||
maxInstallations = 3
|
||||
)
|
||||
|
||||
var (
|
||||
errProtocolNotInitialized = errors.New("procotol is not initialized")
|
||||
// ErrPFSNotEnabled is returned when an endpoint PFS only is called but
|
||||
// PFS is disabled
|
||||
ErrPFSNotEnabled = errors.New("pfs not enabled")
|
||||
)
|
||||
|
||||
//type Persistence interface {
|
||||
//}
|
||||
|
||||
type Service struct {
|
||||
whisper *whisper.Whisper
|
||||
whisperAPI *whisper.PublicWhisperAPI
|
||||
protocol *chat.ProtocolService
|
||||
// persistence Persistence
|
||||
log log.Logger
|
||||
filter *filter.Service
|
||||
config *Config
|
||||
quit chan struct{}
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
PfsEnabled bool
|
||||
DataDir string
|
||||
InstallationID string
|
||||
}
|
||||
|
||||
func New(config *Config, w *whisper.Whisper) *Service {
|
||||
return &Service{
|
||||
config: config,
|
||||
whisper: w,
|
||||
whisperAPI: whisper.NewPublicWhisperAPI(w),
|
||||
log: log.New("package", "status-go/services/publisher.Service"),
|
||||
}
|
||||
}
|
||||
|
||||
// InitProtocolWithPassword creates an instance of ProtocolService given an address and password used to generate an encryption key.
|
||||
func (s *Service) InitProtocolWithPassword(address string, password string) error {
|
||||
digest := sha3.Sum256([]byte(password))
|
||||
encKey := fmt.Sprintf("%x", digest)
|
||||
return s.initProtocol(address, encKey, password)
|
||||
}
|
||||
|
||||
// InitProtocolWithEncyptionKey creates an instance of ProtocolService given an address and encryption key.
|
||||
func (s *Service) InitProtocolWithEncyptionKey(address string, encKey string) error {
|
||||
return s.initProtocol(address, encKey, "")
|
||||
}
|
||||
|
||||
func (s *Service) initProtocol(address, encKey, password string) error {
|
||||
if !s.config.PfsEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Clean(s.config.DataDir), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
v0Path := filepath.Join(s.config.DataDir, fmt.Sprintf("%x.db", address))
|
||||
v1Path := filepath.Join(s.config.DataDir, fmt.Sprintf("%s.db", s.config.InstallationID))
|
||||
v2Path := filepath.Join(s.config.DataDir, fmt.Sprintf("%s.v2.db", s.config.InstallationID))
|
||||
v3Path := filepath.Join(s.config.DataDir, fmt.Sprintf("%s.v3.db", s.config.InstallationID))
|
||||
v4Path := filepath.Join(s.config.DataDir, fmt.Sprintf("%s.v4.db", s.config.InstallationID))
|
||||
|
||||
if password != "" {
|
||||
if err := appDB.MigrateDBFile(v0Path, v1Path, "ON", password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := appDB.MigrateDBFile(v1Path, v2Path, password, encKey); err != nil {
|
||||
// Remove db file as created with a blank password and never used,
|
||||
// and there's no need to rekey in this case
|
||||
os.Remove(v1Path)
|
||||
os.Remove(v2Path)
|
||||
}
|
||||
}
|
||||
|
||||
if err := appDB.MigrateDBKeyKdfIterations(v2Path, v3Path, encKey); err != nil {
|
||||
os.Remove(v2Path)
|
||||
os.Remove(v3Path)
|
||||
}
|
||||
|
||||
// Fix IOS not encrypting database
|
||||
if err := appDB.EncryptDatabase(v3Path, v4Path, encKey); err != nil {
|
||||
os.Remove(v3Path)
|
||||
os.Remove(v4Path)
|
||||
}
|
||||
|
||||
// Desktop was passing a network dependent directory, which meant that
|
||||
// if running on testnet it would not access the right db. This copies
|
||||
// the db from mainnet to the root location.
|
||||
networkDependentPath := filepath.Join(s.config.DataDir, "ethereum", "mainnet_rpc", fmt.Sprintf("%s.v4.db", s.config.InstallationID))
|
||||
if _, err := os.Stat(networkDependentPath); err == nil {
|
||||
if err := os.Rename(networkDependentPath, v4Path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
persistence, err := chat.NewSQLLitePersistence(v4Path, encKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addedBundlesHandler := func(addedBundles []multidevice.IdentityAndIDPair) {
|
||||
handler := SignalHandler{}
|
||||
for _, bundle := range addedBundles {
|
||||
handler.BundleAdded(bundle[0], bundle[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sharedsecret
|
||||
sharedSecretService := sharedsecret.NewService(persistence.GetSharedSecretStorage())
|
||||
// Initialize filter
|
||||
filterService := filter.New(s.whisper, sharedSecretService)
|
||||
s.filter = filterService
|
||||
|
||||
// Initialize multidevice
|
||||
multideviceConfig := &multidevice.Config{
|
||||
InstallationID: s.config.InstallationID,
|
||||
ProtocolVersion: chat.ProtocolVersion,
|
||||
MaxInstallations: maxInstallations,
|
||||
}
|
||||
multideviceService := multidevice.New(multideviceConfig, persistence.GetMultideviceStorage())
|
||||
|
||||
s.protocol = chat.NewProtocolService(
|
||||
chat.NewEncryptionService(
|
||||
persistence,
|
||||
chat.DefaultEncryptionServiceConfig(s.config.InstallationID)),
|
||||
sharedSecretService,
|
||||
multideviceService,
|
||||
addedBundlesHandler,
|
||||
s.onNewSharedSecretHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *protobuf.Bundle) ([]multidevice.IdentityAndIDPair, error) {
|
||||
if s.protocol == nil {
|
||||
return nil, errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
}
|
||||
|
||||
func (s *Service) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*protobuf.Bundle, error) {
|
||||
if s.protocol == nil {
|
||||
return nil, errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.GetBundle(myIdentityKey)
|
||||
}
|
||||
|
||||
// EnableInstallation enables an installation for multi-device sync.
|
||||
func (s *Service) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
if s.protocol == nil {
|
||||
return errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.EnableInstallation(myIdentityKey, installationID)
|
||||
}
|
||||
|
||||
func (s *Service) GetPublicBundle(identityKey *ecdsa.PublicKey) (*protobuf.Bundle, error) {
|
||||
if s.protocol == nil {
|
||||
return nil, errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.GetPublicBundle(identityKey)
|
||||
}
|
||||
|
||||
// DisableInstallation disables an installation for multi-device sync.
|
||||
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
if s.protocol == nil {
|
||||
return errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.DisableInstallation(myIdentityKey, installationID)
|
||||
}
|
||||
|
||||
func (s *Service) Start() error {
|
||||
s.startTicker()
|
||||
return nil
|
||||
}
|
||||
func (s *Service) Stop() error {
|
||||
if s.filter != nil {
|
||||
if err := s.filter.Stop(); err != nil {
|
||||
log.Error("Failed to stop filter service with error", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetNegotiatedChat(identity *ecdsa.PublicKey) *filter.Chat {
|
||||
return s.filter.GetNegotiated(identity)
|
||||
}
|
||||
|
||||
func (s *Service) LoadFilters(chats []*filter.Chat) ([]*filter.Chat, error) {
|
||||
return s.filter.Init(chats)
|
||||
}
|
||||
|
||||
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) onNewSharedSecretHandler(sharedSecrets []*sharedsecret.Secret) {
|
||||
var filters []*signal.Filter
|
||||
for _, sharedSecret := range sharedSecrets {
|
||||
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)
|
||||
|
||||
}
|
||||
if len(filters) != 0 {
|
||||
handler := SignalHandler{}
|
||||
handler.WhisperFilterAdded(filters)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) ProcessMessage(dedupMessage dedup.DeduplicateMessage) error {
|
||||
if !s.config.PfsEnabled {
|
||||
return nil
|
||||
}
|
||||
msg := dedupMessage.Message
|
||||
|
||||
privateKeyID := s.whisper.SelectedKeyPairID()
|
||||
if privateKeyID == "" {
|
||||
return errors.New("no key selected")
|
||||
}
|
||||
|
||||
privateKey, err := s.whisper.GetPrivateKey(privateKeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(msg.Sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshal message
|
||||
protocolMessage := &protobuf.ProtocolMessage{}
|
||||
|
||||
if err := proto.Unmarshal(msg.Payload, protocolMessage); err != nil {
|
||||
s.log.Debug("Not a protocol message", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := s.protocol.HandleMessage(privateKey, publicKey, protocolMessage, dedupMessage.DedupID)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
// Set the decrypted payload
|
||||
msg.Payload = response
|
||||
case chat.ErrDeviceNotFound:
|
||||
// Notify that someone tried to contact us using an invalid bundle
|
||||
if privateKey.PublicKey != *publicKey {
|
||||
s.log.Warn("Device not found, sending signal", "err", err)
|
||||
keyString := fmt.Sprintf("0x%x", crypto.FromECDSAPub(publicKey))
|
||||
handler := SignalHandler{}
|
||||
handler.DecryptMessageFailed(keyString)
|
||||
}
|
||||
default:
|
||||
// Log and pass to the client, even if failed to decrypt
|
||||
s.log.Error("Failed handling message with error", "err", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendDirectMessage sends a 1:1 chat message to the underlying transport
|
||||
func (s *Service) SendDirectMessage(ctx context.Context, msg chat.SendDirectMessageRPC) (hexutil.Bytes, error) {
|
||||
if !s.config.PfsEnabled {
|
||||
return nil, ErrPFSNotEnabled
|
||||
}
|
||||
|
||||
privateKey, err := s.whisper.GetPrivateKey(msg.Sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKey, err := crypto.UnmarshalPubkey(msg.PubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var msgSpec *chat.ProtocolMessageSpec
|
||||
|
||||
if msg.DH {
|
||||
s.log.Debug("Building dh message")
|
||||
msgSpec, err = s.protocol.BuildDHMessage(privateKey, publicKey, msg.Payload)
|
||||
} else {
|
||||
s.log.Debug("Building direct message")
|
||||
msgSpec, err = s.protocol.BuildDirectMessage(privateKey, publicKey, msg.Payload)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whisperMessage, err := s.directMessageToWhisper(privateKey, publicKey, msg.PubKey, msg.Sig, msgSpec)
|
||||
if err != nil {
|
||||
s.log.Error("sshext-service", "error building whisper message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.whisperAPI.Post(ctx, *whisperMessage)
|
||||
}
|
||||
|
||||
func (s *Service) directMessageToWhisper(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, destination hexutil.Bytes, signature string, spec *chat.ProtocolMessageSpec) (*whisper.NewMessage, error) {
|
||||
// marshal for sending to wire
|
||||
marshaledMessage, err := proto.Marshal(spec.Message)
|
||||
if err != nil {
|
||||
s.log.Error("encryption-service", "error marshaling message", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whisperMessage := whisperutils.DefaultWhisperMessage()
|
||||
whisperMessage.Payload = marshaledMessage
|
||||
whisperMessage.Sig = signature
|
||||
|
||||
if spec.SharedSecret != nil {
|
||||
chat := s.GetNegotiatedChat(theirPublicKey)
|
||||
if chat != nil {
|
||||
s.log.Debug("Sending on negotiated topic")
|
||||
whisperMessage.SymKeyID = chat.SymKeyID
|
||||
whisperMessage.Topic = chat.Topic
|
||||
whisperMessage.PublicKey = nil
|
||||
return &whisperMessage, nil
|
||||
}
|
||||
} else if spec.PartitionedTopic() {
|
||||
s.log.Debug("Sending on partitioned topic")
|
||||
// Create filter on demand
|
||||
if _, err := s.filter.LoadPartitioned(myPrivateKey, theirPublicKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := filter.PublicKeyToPartitionedTopicBytes(theirPublicKey)
|
||||
whisperMessage.Topic = whisper.BytesToTopic(t)
|
||||
whisperMessage.PublicKey = destination
|
||||
return &whisperMessage, nil
|
||||
}
|
||||
|
||||
s.log.Debug("Sending on old discovery topic")
|
||||
whisperMessage.Topic = whisperutils.DiscoveryTopicBytes
|
||||
whisperMessage.PublicKey = destination
|
||||
|
||||
return &whisperMessage, nil
|
||||
}
|
||||
|
||||
// SendPublicMessage sends a public chat message to the underlying transport
|
||||
func (s *Service) SendPublicMessage(ctx context.Context, msg chat.SendPublicMessageRPC) (hexutil.Bytes, error) {
|
||||
if !s.config.PfsEnabled {
|
||||
return nil, ErrPFSNotEnabled
|
||||
}
|
||||
|
||||
filter := s.filter.GetByID(msg.Chat)
|
||||
if filter == nil {
|
||||
return nil, errors.New("not subscribed to chat")
|
||||
}
|
||||
|
||||
// Enrich with transport layer info
|
||||
whisperMessage := whisperutils.DefaultWhisperMessage()
|
||||
whisperMessage.Payload = msg.Payload
|
||||
whisperMessage.Sig = msg.Sig
|
||||
whisperMessage.Topic = whisperutils.ToTopic(msg.Chat)
|
||||
whisperMessage.SymKeyID = filter.SymKeyID
|
||||
|
||||
// And dispatch
|
||||
return s.whisperAPI.Post(ctx, whisperMessage)
|
||||
}
|
||||
|
||||
func (s *Service) ConfirmMessagesProcessed(ids [][]byte) error {
|
||||
return s.protocol.ConfirmMessagesProcessed(ids)
|
||||
}
|
||||
|
||||
func (s *Service) startTicker() {
|
||||
s.ticker = time.NewTicker(tickerInterval * time.Second)
|
||||
s.quit = make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-s.ticker.C:
|
||||
err := s.perform()
|
||||
if err != nil {
|
||||
s.log.Error("could not execute tick", "err", err)
|
||||
}
|
||||
case <-s.quit:
|
||||
s.ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Service) perform() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package publisher
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
// SignalHandler sends signals on protocol events
|
||||
type SignalHandler struct{}
|
||||
|
||||
func (h SignalHandler) DecryptMessageFailed(pubKey string) {
|
||||
signal.SendDecryptMessageFailed(pubKey)
|
||||
}
|
||||
|
||||
func (h SignalHandler) BundleAdded(identity string, installationID string) {
|
||||
signal.SendBundleAdded(identity, installationID)
|
||||
}
|
||||
|
||||
func (h SignalHandler) WhisperFilterAdded(filters []*signal.Filter) {
|
||||
signal.SendWhisperFilterAdded(filters)
|
||||
}
|
|
@ -2,10 +2,6 @@ package shhext
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
@ -16,16 +12,12 @@ import (
|
|||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/db"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/shhext/chat"
|
||||
appDB "github.com/status-im/status-go/services/shhext/chat/db"
|
||||
"github.com/status-im/status-go/services/shhext/chat/topic"
|
||||
"github.com/status-im/status-go/services/shhext/dedup"
|
||||
"github.com/status-im/status-go/services/shhext/filter"
|
||||
"github.com/status-im/status-go/services/shhext/mailservers"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/services/shhext/publisher"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -35,8 +27,6 @@ const (
|
|||
defaultTimeoutWaitAdded = 5 * time.Second
|
||||
)
|
||||
|
||||
var errProtocolNotInitialized = errors.New("protocol is not initialized")
|
||||
|
||||
// EnvelopeEventsHandler used for two different event types.
|
||||
type EnvelopeEventsHandler interface {
|
||||
EnvelopeSent(common.Hash)
|
||||
|
@ -47,6 +37,7 @@ type EnvelopeEventsHandler interface {
|
|||
|
||||
// Service is a service that provides some additional Whisper API.
|
||||
type Service struct {
|
||||
*publisher.Service
|
||||
storage db.TransactionalStorage
|
||||
w *whisper.Whisper
|
||||
config params.ShhextConfig
|
||||
|
@ -57,10 +48,6 @@ type Service struct {
|
|||
server *p2p.Server
|
||||
nodeID *ecdsa.PrivateKey
|
||||
deduplicator *dedup.Deduplicator
|
||||
protocol *chat.ProtocolService
|
||||
dataDir string
|
||||
installationID string
|
||||
pfsEnabled bool
|
||||
peerStore *mailservers.PeerStore
|
||||
cache *mailservers.Cache
|
||||
connManager *mailservers.ConnectionManager
|
||||
|
@ -71,7 +58,7 @@ type Service struct {
|
|||
// Make sure that Service implements node.Service interface.
|
||||
var _ node.Service = (*Service)(nil)
|
||||
|
||||
// New returns a new Service. dataDir is a folder path to a network-independent location
|
||||
// New returns a new Service.
|
||||
func New(w *whisper.Whisper, handler EnvelopeEventsHandler, ldb *leveldb.DB, config params.ShhextConfig) *Service {
|
||||
cache := mailservers.NewCache(ldb)
|
||||
ps := mailservers.NewPeerStore(cache)
|
||||
|
@ -88,7 +75,14 @@ func New(w *whisper.Whisper, handler EnvelopeEventsHandler, ldb *leveldb.DB, con
|
|||
requestsRegistry: requestsRegistry,
|
||||
}
|
||||
envelopesMonitor := NewEnvelopesMonitor(w, handler, config.MailServerConfirmations, ps, config.MaxMessageDeliveryAttempts)
|
||||
publisherConfig := &publisher.Config{
|
||||
PfsEnabled: config.PFSEnabled,
|
||||
DataDir: config.BackupDisabledDataDir,
|
||||
InstallationID: config.InstallationID,
|
||||
}
|
||||
publisherService := publisher.New(publisherConfig, w)
|
||||
return &Service{
|
||||
Service: publisherService,
|
||||
storage: db.NewLevelDBStorage(ldb),
|
||||
w: w,
|
||||
config: config,
|
||||
|
@ -97,9 +91,6 @@ func New(w *whisper.Whisper, handler EnvelopeEventsHandler, ldb *leveldb.DB, con
|
|||
requestsRegistry: requestsRegistry,
|
||||
historyUpdates: historyUpdates,
|
||||
deduplicator: dedup.NewDeduplicator(w, ldb),
|
||||
dataDir: config.BackupDisabledDataDir,
|
||||
installationID: config.InstallationID,
|
||||
pfsEnabled: config.PFSEnabled,
|
||||
peerStore: ps,
|
||||
cache: cache,
|
||||
}
|
||||
|
@ -121,138 +112,6 @@ func (s *Service) Protocols() []p2p.Protocol {
|
|||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// InitProtocolWithPassword creates an instance of ProtocolService given an address and password used to generate an encryption key.
|
||||
func (s *Service) InitProtocolWithPassword(address string, password string) error {
|
||||
digest := sha3.Sum256([]byte(password))
|
||||
encKey := fmt.Sprintf("%x", digest)
|
||||
return s.initProtocol(address, encKey, password)
|
||||
}
|
||||
|
||||
// InitProtocolWithEncyptionKey creates an instance of ProtocolService given an address and encryption key.
|
||||
func (s *Service) InitProtocolWithEncyptionKey(address string, encKey string) error {
|
||||
return s.initProtocol(address, encKey, "")
|
||||
}
|
||||
|
||||
func (s *Service) initProtocol(address, encKey, password string) error {
|
||||
if !s.pfsEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Clean(s.dataDir), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
v0Path := filepath.Join(s.dataDir, fmt.Sprintf("%x.db", address))
|
||||
v1Path := filepath.Join(s.dataDir, fmt.Sprintf("%s.db", s.installationID))
|
||||
v2Path := filepath.Join(s.dataDir, fmt.Sprintf("%s.v2.db", s.installationID))
|
||||
v3Path := filepath.Join(s.dataDir, fmt.Sprintf("%s.v3.db", s.installationID))
|
||||
v4Path := filepath.Join(s.dataDir, fmt.Sprintf("%s.v4.db", s.installationID))
|
||||
|
||||
if password != "" {
|
||||
if err := appDB.MigrateDBFile(v0Path, v1Path, "ON", password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := appDB.MigrateDBFile(v1Path, v2Path, password, encKey); err != nil {
|
||||
// Remove db file as created with a blank password and never used,
|
||||
// and there's no need to rekey in this case
|
||||
os.Remove(v1Path)
|
||||
os.Remove(v2Path)
|
||||
}
|
||||
}
|
||||
|
||||
if err := appDB.MigrateDBKeyKdfIterations(v2Path, v3Path, encKey); err != nil {
|
||||
os.Remove(v2Path)
|
||||
os.Remove(v3Path)
|
||||
}
|
||||
|
||||
// Fix IOS not encrypting database
|
||||
if err := appDB.EncryptDatabase(v3Path, v4Path, encKey); err != nil {
|
||||
os.Remove(v3Path)
|
||||
os.Remove(v4Path)
|
||||
}
|
||||
|
||||
// Desktop was passing a network dependent directory, which meant that
|
||||
// if running on testnet it would not access the right db. This copies
|
||||
// the db from mainnet to the root location.
|
||||
networkDependentPath := filepath.Join(s.dataDir, "ethereum", "mainnet_rpc", fmt.Sprintf("%s.v4.db", s.installationID))
|
||||
if _, err := os.Stat(networkDependentPath); err == nil {
|
||||
if err := os.Rename(networkDependentPath, v4Path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
persistence, err := chat.NewSQLLitePersistence(v4Path, encKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addedBundlesHandler := func(addedBundles []chat.IdentityAndIDPair) {
|
||||
handler := EnvelopeSignalHandler{}
|
||||
for _, bundle := range addedBundles {
|
||||
handler.BundleAdded(bundle[0], bundle[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize topics
|
||||
topicService := topic.NewService(persistence.GetTopicStorage())
|
||||
filterService := filter.New(s.config.AsymKeyID, s.w, topicService)
|
||||
s.filter = filterService
|
||||
|
||||
s.protocol = chat.NewProtocolService(
|
||||
chat.NewEncryptionService(
|
||||
persistence,
|
||||
chat.DefaultEncryptionServiceConfig(s.installationID)),
|
||||
topicService,
|
||||
addedBundlesHandler,
|
||||
s.onNewTopicHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ProcessPublicBundle(myIdentityKey *ecdsa.PrivateKey, bundle *chat.Bundle) ([]chat.IdentityAndIDPair, error) {
|
||||
if s.protocol == nil {
|
||||
return nil, errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.ProcessPublicBundle(myIdentityKey, bundle)
|
||||
}
|
||||
|
||||
func (s *Service) GetBundle(myIdentityKey *ecdsa.PrivateKey) (*chat.Bundle, error) {
|
||||
if s.protocol == nil {
|
||||
return nil, errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.GetBundle(myIdentityKey)
|
||||
}
|
||||
|
||||
// EnableInstallation enables an installation for multi-device sync.
|
||||
func (s *Service) EnableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
if s.protocol == nil {
|
||||
return errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.EnableInstallation(myIdentityKey, installationID)
|
||||
}
|
||||
|
||||
func (s *Service) GetPublicBundle(identityKey *ecdsa.PublicKey) (*chat.Bundle, error) {
|
||||
if s.protocol == nil {
|
||||
return nil, errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.GetPublicBundle(identityKey)
|
||||
}
|
||||
|
||||
// DisableInstallation disables an installation for multi-device sync.
|
||||
func (s *Service) DisableInstallation(myIdentityKey *ecdsa.PublicKey, installationID string) error {
|
||||
if s.protocol == nil {
|
||||
return errProtocolNotInitialized
|
||||
}
|
||||
|
||||
return s.protocol.DisableInstallation(myIdentityKey, installationID)
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
apis := []rpc.API{
|
||||
|
@ -293,7 +152,7 @@ func (s *Service) Start(server *p2p.Server) error {
|
|||
s.mailMonitor.Start()
|
||||
s.nodeID = server.PrivateKey
|
||||
s.server = server
|
||||
return nil
|
||||
return s.Service.Start()
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
|
@ -314,38 +173,5 @@ func (s *Service) Stop() error {
|
|||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetNegotiatedChat(identity *ecdsa.PublicKey) *filter.Chat {
|
||||
return s.filter.Get(identity)
|
||||
}
|
||||
|
||||
func (s *Service) LoadFilters(chats []*filter.Chat) error {
|
||||
return s.filter.Init(chats)
|
||||
}
|
||||
|
||||
func (s *Service) RemoveFilter(chat *filter.Chat) {
|
||||
// remove filter
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error("Failed to process negotiated secret", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
// TODO: send back chat filter
|
||||
log.Info("FILTER IDS", "filter", filters)
|
||||
if len(filters) != 0 {
|
||||
log.Info("SENDING FILTERS")
|
||||
handler := EnvelopeSignalHandler{}
|
||||
handler.WhisperFilterAdded(filters)
|
||||
}
|
||||
|
||||
return s.Service.Stop()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package whisperutils
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
whisper "github.com/status-im/whisper/whisperv6"
|
||||
)
|
||||
|
||||
var discoveryTopic = "contact-discovery"
|
||||
var DiscoveryTopicBytes = ToTopic(discoveryTopic)
|
||||
|
||||
func ToTopic(s string) whisper.TopicType {
|
||||
return whisper.BytesToTopic(crypto.Keccak256([]byte(s)))
|
||||
}
|
||||
|
||||
func DefaultWhisperMessage() whisper.NewMessage {
|
||||
msg := whisper.NewMessage{}
|
||||
|
||||
msg.TTL = 10
|
||||
msg.PowTarget = 0.002
|
||||
msg.PowTime = 1
|
||||
|
||||
return msg
|
||||
}
|
|
@ -63,10 +63,17 @@ type BundleAddedSignal struct {
|
|||
}
|
||||
|
||||
type Filter struct {
|
||||
Identity string `json:"identity"`
|
||||
FilterID string `json:"filterId"`
|
||||
SymKeyID string `json:"symKeyId"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE secret_installation_ids;
|
||||
DROP TABLE secrets;
|
|
@ -1,11 +1,11 @@
|
|||
CREATE TABLE topics (
|
||||
CREATE TABLE secrets (
|
||||
identity BLOB NOT NULL PRIMARY KEY ON CONFLICT IGNORE,
|
||||
secret BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE topic_installation_ids (
|
||||
CREATE TABLE secret_installation_ids (
|
||||
id TEXT NOT NULL,
|
||||
identity_id BLOB NOT NULL,
|
||||
UNIQUE(id, identity_id) ON CONFLICT IGNORE,
|
||||
FOREIGN KEY (identity_id) REFERENCES topics(identity)
|
||||
FOREIGN KEY (identity_id) REFERENCES secrets(identity)
|
||||
);
|
|
@ -1,2 +0,0 @@
|
|||
DROP TABLE topic_installation_ids;
|
||||
DROP TABLE topics;
|
263
vendor/github.com/ethereum/go-ethereum/whisper/whisperv6/filter.go
generated
vendored
Normal file
263
vendor/github.com/ethereum/go-ethereum/whisper/whisperv6/filter.go
generated
vendored
Normal file
|
@ -0,0 +1,263 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package whisperv6
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Filter represents a Whisper message filter
|
||||
type Filter struct {
|
||||
Src *ecdsa.PublicKey // Sender of the message
|
||||
KeyAsym *ecdsa.PrivateKey // Private Key of recipient
|
||||
KeySym []byte // Key associated with the Topic
|
||||
Topics [][]byte // Topics to filter messages with
|
||||
PoW float64 // Proof of work as described in the Whisper spec
|
||||
AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages
|
||||
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
|
||||
id string // unique identifier
|
||||
|
||||
Messages map[common.Hash]*ReceivedMessage
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// Filters represents a collection of filters
|
||||
type Filters struct {
|
||||
watchers map[string]*Filter
|
||||
|
||||
topicMatcher map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic
|
||||
allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is
|
||||
|
||||
whisper *Whisper
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewFilters returns a newly created filter collection
|
||||
func NewFilters(w *Whisper) *Filters {
|
||||
return &Filters{
|
||||
watchers: make(map[string]*Filter),
|
||||
topicMatcher: make(map[TopicType]map[*Filter]struct{}),
|
||||
allTopicsMatcher: make(map[*Filter]struct{}),
|
||||
whisper: w,
|
||||
}
|
||||
}
|
||||
|
||||
// Install will add a new filter to the filter collection
|
||||
func (fs *Filters) Install(watcher *Filter) (string, error) {
|
||||
if watcher.KeySym != nil && watcher.KeyAsym != nil {
|
||||
return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
|
||||
}
|
||||
|
||||
if watcher.Messages == nil {
|
||||
watcher.Messages = make(map[common.Hash]*ReceivedMessage)
|
||||
}
|
||||
|
||||
id, err := GenerateRandomID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
|
||||
if fs.watchers[id] != nil {
|
||||
return "", fmt.Errorf("failed to generate unique ID")
|
||||
}
|
||||
|
||||
if watcher.expectsSymmetricEncryption() {
|
||||
watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
|
||||
}
|
||||
|
||||
watcher.id = id
|
||||
fs.watchers[id] = watcher
|
||||
fs.addTopicMatcher(watcher)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Uninstall will remove a filter whose id has been specified from
|
||||
// the filter collection
|
||||
func (fs *Filters) Uninstall(id string) bool {
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
if fs.watchers[id] != nil {
|
||||
fs.removeFromTopicMatchers(fs.watchers[id])
|
||||
delete(fs.watchers, id)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addTopicMatcher adds a filter to the topic matchers.
|
||||
// If the filter's Topics array is empty, it will be tried on every topic.
|
||||
// Otherwise, it will be tried on the topics specified.
|
||||
func (fs *Filters) addTopicMatcher(watcher *Filter) {
|
||||
if len(watcher.Topics) == 0 {
|
||||
fs.allTopicsMatcher[watcher] = struct{}{}
|
||||
} else {
|
||||
for _, t := range watcher.Topics {
|
||||
topic := BytesToTopic(t)
|
||||
if fs.topicMatcher[topic] == nil {
|
||||
fs.topicMatcher[topic] = make(map[*Filter]struct{})
|
||||
}
|
||||
fs.topicMatcher[topic][watcher] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeFromTopicMatchers removes a filter from the topic matchers
|
||||
func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
|
||||
delete(fs.allTopicsMatcher, watcher)
|
||||
for _, topic := range watcher.Topics {
|
||||
delete(fs.topicMatcher[BytesToTopic(topic)], watcher)
|
||||
}
|
||||
}
|
||||
|
||||
// getWatchersByTopic returns a slice containing the filters that
|
||||
// match a specific topic
|
||||
func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter {
|
||||
res := make([]*Filter, 0, len(fs.allTopicsMatcher))
|
||||
for watcher := range fs.allTopicsMatcher {
|
||||
res = append(res, watcher)
|
||||
}
|
||||
for watcher := range fs.topicMatcher[topic] {
|
||||
res = append(res, watcher)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Get returns a filter from the collection with a specific ID
|
||||
func (fs *Filters) Get(id string) *Filter {
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
return fs.watchers[id]
|
||||
}
|
||||
|
||||
// NotifyWatchers notifies any filter that has declared interest
|
||||
// for the envelope's topic.
|
||||
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
|
||||
var msg *ReceivedMessage
|
||||
|
||||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
|
||||
candidates := fs.getWatchersByTopic(env.Topic)
|
||||
for _, watcher := range candidates {
|
||||
if p2pMessage && !watcher.AllowP2P {
|
||||
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id))
|
||||
continue
|
||||
}
|
||||
|
||||
var match bool
|
||||
if msg != nil {
|
||||
match = watcher.MatchMessage(msg)
|
||||
} else {
|
||||
match = watcher.MatchEnvelope(env)
|
||||
if match {
|
||||
msg = env.Open(watcher)
|
||||
if msg == nil {
|
||||
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id)
|
||||
}
|
||||
} else {
|
||||
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id)
|
||||
}
|
||||
}
|
||||
|
||||
if match && msg != nil {
|
||||
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
|
||||
if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
|
||||
watcher.Trigger(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Filter) expectsAsymmetricEncryption() bool {
|
||||
return f.KeyAsym != nil
|
||||
}
|
||||
|
||||
func (f *Filter) expectsSymmetricEncryption() bool {
|
||||
return f.KeySym != nil
|
||||
}
|
||||
|
||||
// Trigger adds a yet-unknown message to the filter's list of
|
||||
// received messages.
|
||||
func (f *Filter) Trigger(msg *ReceivedMessage) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if _, exist := f.Messages[msg.EnvelopeHash]; !exist {
|
||||
f.Messages[msg.EnvelopeHash] = msg
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve will return the list of all received messages associated
|
||||
// to a filter.
|
||||
func (f *Filter) Retrieve() (all []*ReceivedMessage) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
all = make([]*ReceivedMessage, 0, len(f.Messages))
|
||||
for _, msg := range f.Messages {
|
||||
all = append(all, msg)
|
||||
}
|
||||
|
||||
f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages
|
||||
return all
|
||||
}
|
||||
|
||||
// MatchMessage checks if the filter matches an already decrypted
|
||||
// message (i.e. a Message that has already been handled by
|
||||
// MatchEnvelope when checked by a previous filter).
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
|
||||
if f.PoW > 0 && msg.PoW < f.PoW {
|
||||
return false
|
||||
}
|
||||
|
||||
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
|
||||
return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
|
||||
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
|
||||
return f.SymKeyHash == msg.SymKeyHash
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchEnvelope checks if it's worth decrypting the message. If
|
||||
// it returns `true`, client code is expected to attempt decrypting
|
||||
// the message and subsequently call MatchMessage.
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
|
||||
log.Trace("checking pow", "filter", f.PoW, "envelope", envelope.pow)
|
||||
return f.PoW <= 0 || envelope.pow >= f.PoW
|
||||
}
|
||||
|
||||
// IsPubKeyEqual checks that two public keys are equal
|
||||
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
|
||||
if !ValidatePublicKey(a) {
|
||||
return false
|
||||
} else if !ValidatePublicKey(b) {
|
||||
return false
|
||||
}
|
||||
// the curve is always the same, just compare the points
|
||||
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
|
||||
}
|
|
@ -197,6 +197,7 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
|
|||
fs.mutex.RLock()
|
||||
defer fs.mutex.RUnlock()
|
||||
|
||||
log.Info("Got envelope for topic", "topic", env.Topic)
|
||||
candidates := fs.getWatchersByTopic(env.Topic)
|
||||
for _, watcher := range candidates {
|
||||
if p2pMessage && !watcher.AllowP2P {
|
||||
|
@ -279,6 +280,7 @@ func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
|
|||
// the message and subsequently call MatchMessage.
|
||||
// Topics are not checked here, since this is done by topic matchers.
|
||||
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
|
||||
log.Trace("checking pow", "filter", f.PoW, "envelope", envelope.pow)
|
||||
return f.PoW <= 0 || envelope.pow >= f.PoW
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue