feat(communities)_: introduce bloom filter members list
iterates: status-im/status-desktop#15064
This commit is contained in:
parent
1715defec8
commit
cb20c4c64a
|
@ -52,7 +52,7 @@ type Config struct {
|
|||
Logger *zap.Logger
|
||||
RequestedToJoinAt uint64
|
||||
RequestsToJoin []*RequestToJoin
|
||||
MemberIdentity *ecdsa.PublicKey
|
||||
MemberIdentity *ecdsa.PrivateKey
|
||||
EventsData *EventsData
|
||||
Shard *shard.Shard
|
||||
PubsubTopicPrivateKey *ecdsa.PrivateKey
|
||||
|
@ -115,6 +115,7 @@ type CommunityChat struct {
|
|||
CategoryID string `json:"categoryID"`
|
||||
TokenGated bool `json:"tokenGated"`
|
||||
HideIfPermissionsNotMet bool `json:"hideIfPermissionsNotMet"`
|
||||
MissingEncryptionKey bool `json:"missingEncryptionKey"`
|
||||
}
|
||||
|
||||
type CommunityCategory struct {
|
||||
|
@ -189,15 +190,15 @@ func (o *Community) MarshalPublicAPIJSON() ([]byte, error) {
|
|||
for id, c := range o.config.CommunityDescription.Chats {
|
||||
// NOTE: Here `CanPost` is only set for ChatMessage and Emoji reactions. But it can be different for pin/etc.
|
||||
// Consider adding more properties to `CommunityChat` to reflect that.
|
||||
canPost, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||
canPost, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canPostReactions, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||
canPostReactions, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canView := o.CanView(o.config.MemberIdentity, id)
|
||||
canView := o.CanView(o.MemberIdentity(), id)
|
||||
|
||||
chat := CommunityChat{
|
||||
ID: id,
|
||||
|
@ -314,10 +315,10 @@ func (o *Community) MarshalJSONWithMediaServer(mediaServer *server.MediaServer)
|
|||
Joined: o.config.Joined,
|
||||
JoinedAt: o.config.JoinedAt,
|
||||
Spectated: o.config.Spectated,
|
||||
CanRequestAccess: o.CanRequestAccess(o.config.MemberIdentity),
|
||||
CanRequestAccess: o.CanRequestAccess(o.MemberIdentity()),
|
||||
CanJoin: o.canJoin(),
|
||||
CanManageUsers: o.CanManageUsers(o.config.MemberIdentity),
|
||||
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.config.MemberIdentity),
|
||||
CanManageUsers: o.CanManageUsers(o.MemberIdentity()),
|
||||
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.MemberIdentity()),
|
||||
RequestedToJoinAt: o.RequestedToJoinAt(),
|
||||
IsMember: o.isMember(),
|
||||
Muted: o.config.Muted,
|
||||
|
@ -342,15 +343,15 @@ func (o *Community) MarshalJSONWithMediaServer(mediaServer *server.MediaServer)
|
|||
for id, c := range o.config.CommunityDescription.Chats {
|
||||
// NOTE: Here `CanPost` is only set for ChatMessage. But it can be different for reactions/pin/etc.
|
||||
// Consider adding more properties to `CommunityChat` to reflect that.
|
||||
canPost, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||
canPost, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canPostReactions, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||
canPostReactions, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canView := o.CanView(o.config.MemberIdentity, id)
|
||||
canView := o.CanView(o.MemberIdentity(), id)
|
||||
|
||||
chat := CommunityChat{
|
||||
ID: id,
|
||||
|
@ -368,6 +369,7 @@ func (o *Community) MarshalJSONWithMediaServer(mediaServer *server.MediaServer)
|
|||
CategoryID: c.CategoryId,
|
||||
HideIfPermissionsNotMet: c.HideIfPermissionsNotMet,
|
||||
Position: int(c.Position),
|
||||
MissingEncryptionKey: !o.IsMemberInChat(o.MemberIdentity(), id) && o.IsMemberLikelyInChat(id),
|
||||
}
|
||||
communityItem.Chats[id] = chat
|
||||
}
|
||||
|
@ -466,10 +468,10 @@ func (o *Community) MarshalJSON() ([]byte, error) {
|
|||
Joined: o.config.Joined,
|
||||
JoinedAt: o.config.JoinedAt,
|
||||
Spectated: o.config.Spectated,
|
||||
CanRequestAccess: o.CanRequestAccess(o.config.MemberIdentity),
|
||||
CanRequestAccess: o.CanRequestAccess(o.MemberIdentity()),
|
||||
CanJoin: o.canJoin(),
|
||||
CanManageUsers: o.CanManageUsers(o.config.MemberIdentity),
|
||||
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.config.MemberIdentity),
|
||||
CanManageUsers: o.CanManageUsers(o.MemberIdentity()),
|
||||
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.MemberIdentity()),
|
||||
RequestedToJoinAt: o.RequestedToJoinAt(),
|
||||
IsMember: o.isMember(),
|
||||
Muted: o.config.Muted,
|
||||
|
@ -494,15 +496,15 @@ func (o *Community) MarshalJSON() ([]byte, error) {
|
|||
for id, c := range o.config.CommunityDescription.Chats {
|
||||
// NOTE: Here `CanPost` is only set for ChatMessage. But it can be different for reactions/pin/etc.
|
||||
// Consider adding more properties to `CommunityChat` to reflect that.
|
||||
canPost, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||
canPost, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canPostReactions, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||
canPostReactions, err := o.CanPost(o.MemberIdentity(), id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
canView := o.CanView(o.config.MemberIdentity, id)
|
||||
canView := o.CanView(o.MemberIdentity(), id)
|
||||
|
||||
chat := CommunityChat{
|
||||
ID: id,
|
||||
|
@ -519,6 +521,7 @@ func (o *Community) MarshalJSON() ([]byte, error) {
|
|||
CategoryID: c.CategoryId,
|
||||
HideIfPermissionsNotMet: c.HideIfPermissionsNotMet,
|
||||
Position: int(c.Position),
|
||||
MissingEncryptionKey: !o.IsMemberInChat(o.MemberIdentity(), id) && o.IsMemberLikelyInChat(id),
|
||||
}
|
||||
|
||||
if chat.TokenGated {
|
||||
|
@ -898,6 +901,32 @@ func (o *Community) IsMemberInChat(pk *ecdsa.PublicKey, chatID string) bool {
|
|||
return o.getChatMember(pk, chatID) != nil
|
||||
}
|
||||
|
||||
// Uses bloom filter members list to estimate presence in the channel.
|
||||
// False positive rate is 0.1%.
|
||||
func (o *Community) IsMemberLikelyInChat(chatID string) bool {
|
||||
if o.IsControlNode() || o.IsPrivilegedMember(o.MemberIdentity()) || !o.channelEncrypted(chatID) {
|
||||
return true
|
||||
}
|
||||
|
||||
chat, ok := o.config.CommunityDescription.Chats[chatID]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// For communities controlled by clients that haven't updated to newer version yet we assume no membership.
|
||||
if chat.MembersList == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
res, err := verifyMembershipWithBloomFilter(chat.MembersList, o.config.MemberIdentity, o.ControlNode(), chatID, o.Clock())
|
||||
if err != nil {
|
||||
o.config.Logger.Error("failed to estimate membership", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (o *Community) RemoveUserFromChat(pk *ecdsa.PublicKey, chatID string) (*protobuf.CommunityDescription, error) {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
|
@ -975,7 +1004,7 @@ func (o *Community) RemoveUserFromOrg(pk *ecdsa.PublicKey) (*protobuf.CommunityD
|
|||
func (o *Community) RemoveAllUsersFromOrg() *CommunityChanges {
|
||||
o.increaseClock()
|
||||
|
||||
myPublicKey := common.PubkeyToHex(o.config.MemberIdentity)
|
||||
myPublicKey := common.PubkeyToHex(o.MemberIdentity())
|
||||
member := o.config.CommunityDescription.Members[myPublicKey]
|
||||
|
||||
membersToRemove := o.config.CommunityDescription.Members
|
||||
|
@ -1274,7 +1303,7 @@ func (o *Community) MuteTill() time.Time {
|
|||
}
|
||||
|
||||
func (o *Community) MemberIdentity() *ecdsa.PublicKey {
|
||||
return o.config.MemberIdentity
|
||||
return &o.config.MemberIdentity.PublicKey
|
||||
}
|
||||
|
||||
// UpdateCommunityDescription will update the community to the new community description and return a list of changes
|
||||
|
@ -1412,15 +1441,15 @@ func (o *Community) IsControlNode() bool {
|
|||
}
|
||||
|
||||
func (o *Community) IsOwner() bool {
|
||||
return o.IsMemberOwner(o.config.MemberIdentity)
|
||||
return o.IsMemberOwner(o.MemberIdentity())
|
||||
}
|
||||
|
||||
func (o *Community) IsTokenMaster() bool {
|
||||
return o.IsMemberTokenMaster(o.config.MemberIdentity)
|
||||
return o.IsMemberTokenMaster(o.MemberIdentity())
|
||||
}
|
||||
|
||||
func (o *Community) IsAdmin() bool {
|
||||
return o.IsMemberAdmin(o.config.MemberIdentity)
|
||||
return o.IsMemberAdmin(o.MemberIdentity())
|
||||
}
|
||||
|
||||
func (o *Community) GetPrivilegedMembers() []*ecdsa.PublicKey {
|
||||
|
@ -1460,15 +1489,15 @@ func (o *Community) GetFilteredPrivilegedMembers(skipMembers map[string]struct{}
|
|||
}
|
||||
|
||||
func (o *Community) HasPermissionToSendCommunityEvents() bool {
|
||||
return !o.IsControlNode() && o.hasRoles(o.config.MemberIdentity, manageCommunityRoles())
|
||||
return !o.IsControlNode() && o.hasRoles(o.MemberIdentity(), manageCommunityRoles())
|
||||
}
|
||||
|
||||
func (o *Community) hasPermissionToSendCommunityEvent(event protobuf.CommunityEvent_EventType) bool {
|
||||
return !o.IsControlNode() && canRolesPerformEvent(o.rolesOf(o.config.MemberIdentity), event)
|
||||
return !o.IsControlNode() && canRolesPerformEvent(o.rolesOf(o.MemberIdentity()), event)
|
||||
}
|
||||
|
||||
func (o *Community) hasPermissionToSendTokenPermissionCommunityEvent(event protobuf.CommunityEvent_EventType, permissionType protobuf.CommunityTokenPermission_Type) bool {
|
||||
roles := o.rolesOf(o.config.MemberIdentity)
|
||||
roles := o.rolesOf(o.MemberIdentity())
|
||||
return !o.IsControlNode() && canRolesPerformEvent(roles, event) && canRolesModifyPermission(roles, permissionType)
|
||||
}
|
||||
|
||||
|
@ -1672,6 +1701,11 @@ func (o *Community) marshaledDescription() ([]byte, error) {
|
|||
// see https://github.com/status-im/status-desktop/issues/12188
|
||||
dehydrateChannelsMembers(clone)
|
||||
|
||||
err := generateBloomFiltersForChannels(clone, o.PrivateKey())
|
||||
if err != nil {
|
||||
o.config.Logger.Error("failed to generate bloom filters", zap.Error(err))
|
||||
}
|
||||
|
||||
if o.encryptor != nil {
|
||||
err := encryptDescription(o.encryptor, o, clone)
|
||||
if err != nil {
|
||||
|
@ -2256,11 +2290,11 @@ func (o *Community) CanDeleteMessageForEveryone(pk *ecdsa.PublicKey) bool {
|
|||
}
|
||||
|
||||
func (o *Community) isMember() bool {
|
||||
return o.hasMember(o.config.MemberIdentity)
|
||||
return o.hasMember(o.MemberIdentity())
|
||||
}
|
||||
|
||||
func (o *Community) CanMemberIdentityPost(chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
|
||||
return o.CanPost(o.config.MemberIdentity, chatID, messageType)
|
||||
return o.CanPost(o.MemberIdentity(), chatID, messageType)
|
||||
}
|
||||
|
||||
// CanJoin returns whether a user can join the community, only if it's
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package communities
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
|
||||
"github.com/bits-and-blooms/bloom/v3"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/encryption"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
func generateBloomFiltersForChannels(description *protobuf.CommunityDescription, privateKey *ecdsa.PrivateKey) error {
|
||||
for channelID, channel := range description.Chats {
|
||||
if !channelEncrypted(ChatID(description.ID, channelID), description.TokenPermissions) {
|
||||
continue
|
||||
}
|
||||
|
||||
filter, err := generateBloomFilter(channel.Members, privateKey, channelID, description.Clock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
marshaledFilter, err := filter.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel.MembersList = &protobuf.CommunityBloomFilter{
|
||||
Data: marshaledFilter,
|
||||
M: uint64(filter.Cap()),
|
||||
K: uint64(filter.K()),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextPowerOfTwo(x int) uint {
|
||||
return 1 << bits.Len(uint(x))
|
||||
}
|
||||
|
||||
func max(x, y uint) uint {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func generateBloomFilter(members map[string]*protobuf.CommunityMember, privateKey *ecdsa.PrivateKey, channelID string, clock uint64) (*bloom.BloomFilter, error) {
|
||||
membersCount := len(members)
|
||||
if membersCount == 0 {
|
||||
return nil, errors.New("invalid members count")
|
||||
}
|
||||
|
||||
const falsePositiveRate = 0.001
|
||||
numberOfItems := max(128, nextPowerOfTwo(membersCount)) // This makes it difficult to guess the exact number of members, even with knowledge of filter size and parameters.
|
||||
filter := bloom.NewWithEstimates(numberOfItems, falsePositiveRate)
|
||||
|
||||
for pk := range members {
|
||||
publicKey, err := common.HexToPubkey(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := bloomFilterValue(privateKey, publicKey, channelID, clock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter.Add(value)
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func verifyMembershipWithBloomFilter(membersList *protobuf.CommunityBloomFilter, privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, channelID string, clock uint64) (bool, error) {
|
||||
filter := bloom.New(uint(membersList.M), uint(membersList.K))
|
||||
err := filter.UnmarshalBinary(membersList.Data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
value, err := bloomFilterValue(privateKey, publicKey, channelID, clock)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return filter.Test(value), nil
|
||||
}
|
||||
|
||||
func bloomFilterValue(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, channelID string, clock uint64) ([]byte, error) {
|
||||
sharedSecret, err := encryption.GenerateSharedKey(privateKey, publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clockBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(clockBytes, clock)
|
||||
|
||||
return crypto.Keccak256(sharedSecret, []byte(channelID), clockBytes), nil
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package communities
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
func TestCommunityBloomFilter(t *testing.T) {
|
||||
suite.Run(t, new(CommunityBloomFilterSuite))
|
||||
}
|
||||
|
||||
type CommunityBloomFilterSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (s *CommunityBloomFilterSuite) TestBasic() {
|
||||
ownerIdentity, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
memberIdentity, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
nonMemberIdentity, err := crypto.GenerateKey()
|
||||
s.Require().NoError(err)
|
||||
|
||||
communityID := "cid"
|
||||
encryptedChannelID := "enc"
|
||||
nonEncryptedChannelID := "non-enc"
|
||||
|
||||
description := &protobuf.CommunityDescription{
|
||||
ID: communityID,
|
||||
Clock: 1,
|
||||
Chats: map[string]*protobuf.CommunityChat{
|
||||
encryptedChannelID: {
|
||||
Members: map[string]*protobuf.CommunityMember{
|
||||
common.PubkeyToHex(&memberIdentity.PublicKey): {},
|
||||
},
|
||||
},
|
||||
nonEncryptedChannelID: {
|
||||
Members: map[string]*protobuf.CommunityMember{
|
||||
common.PubkeyToHex(&memberIdentity.PublicKey): {},
|
||||
},
|
||||
},
|
||||
},
|
||||
TokenPermissions: map[string]*protobuf.CommunityTokenPermission{
|
||||
"permissionID": {
|
||||
Id: "permissionID",
|
||||
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
||||
TokenCriteria: []*protobuf.TokenCriteria{{}},
|
||||
ChatIds: []string{ChatID(communityID, encryptedChannelID)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = generateBloomFiltersForChannels(description, ownerIdentity)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(description.Chats[encryptedChannelID].MembersList)
|
||||
s.Require().Nil(description.Chats[nonEncryptedChannelID].MembersList)
|
||||
|
||||
filter := description.Chats[encryptedChannelID].MembersList
|
||||
s.Require().True(verifyMembershipWithBloomFilter(filter, memberIdentity, &ownerIdentity.PublicKey, encryptedChannelID, description.Clock))
|
||||
s.Require().False(verifyMembershipWithBloomFilter(filter, nonMemberIdentity, &ownerIdentity.PublicKey, encryptedChannelID, description.Clock))
|
||||
}
|
|
@ -30,7 +30,7 @@ func createTestCommunity(identity *ecdsa.PrivateKey) (*Community, error) {
|
|||
ControlNode: &identity.PublicKey,
|
||||
ControlDevice: true,
|
||||
Joined: true,
|
||||
MemberIdentity: &identity.PublicKey,
|
||||
MemberIdentity: identity,
|
||||
}
|
||||
|
||||
return New(config, &TimeSourceStub{}, &DescriptionEncryptorMock{})
|
||||
|
|
|
@ -390,7 +390,7 @@ func (s *CommunitySuite) TestValidateRequestToJoin() {
|
|||
},
|
||||
{
|
||||
name: "not admin",
|
||||
config: Config{MemberIdentity: signer, CommunityDescription: description},
|
||||
config: Config{MemberIdentity: key, CommunityDescription: description},
|
||||
signer: signer,
|
||||
request: request,
|
||||
err: ErrNotAdmin,
|
||||
|
@ -813,7 +813,7 @@ func (s *CommunitySuite) emptyCommunityDescriptionWithChat() *protobuf.Community
|
|||
|
||||
func (s *CommunitySuite) newConfig(identity *ecdsa.PrivateKey, description *protobuf.CommunityDescription) Config {
|
||||
return Config{
|
||||
MemberIdentity: &identity.PublicKey,
|
||||
MemberIdentity: identity,
|
||||
ID: &identity.PublicKey,
|
||||
CommunityDescription: description,
|
||||
PrivateKey: identity,
|
||||
|
@ -998,7 +998,7 @@ func (s *CommunitySuite) TestMarshalJSON() {
|
|||
s.Require().True(community.ChannelEncrypted(testChatID1))
|
||||
|
||||
communityDescription := community.config.CommunityDescription
|
||||
ownerKey, err := crypto.GenerateKey()
|
||||
ownerKey := s.identity
|
||||
s.Require().NoError(err)
|
||||
|
||||
memberKey, err := crypto.GenerateKey()
|
||||
|
@ -1043,6 +1043,7 @@ func (s *CommunitySuite) TestMarshalJSON() {
|
|||
"position": float64(0),
|
||||
"tokenGated": true,
|
||||
"viewersCanPostReactions": false,
|
||||
"missingEncryptionKey": false,
|
||||
}
|
||||
|
||||
expectedChats[testChatID1] = expectedChat
|
||||
|
@ -1074,6 +1075,7 @@ func (s *CommunitySuite) TestMarshalJSON() {
|
|||
"position": float64(0),
|
||||
"tokenGated": false,
|
||||
"viewersCanPostReactions": false,
|
||||
"missingEncryptionKey": false,
|
||||
}
|
||||
|
||||
expectedChats[testChatID1] = expectedChat
|
||||
|
|
|
@ -845,7 +845,7 @@ func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish boo
|
|||
Logger: m.logger,
|
||||
Joined: true,
|
||||
JoinedAt: time.Now().Unix(),
|
||||
MemberIdentity: &m.identity.PublicKey,
|
||||
MemberIdentity: m.identity,
|
||||
CommunityDescription: description,
|
||||
Shard: nil,
|
||||
LastOpenedAt: 0,
|
||||
|
@ -1709,7 +1709,7 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey, clock uint64) (*Communi
|
|||
Logger: m.logger,
|
||||
Joined: true,
|
||||
JoinedAt: time.Now().Unix(),
|
||||
MemberIdentity: &m.identity.PublicKey,
|
||||
MemberIdentity: m.identity,
|
||||
CommunityDescription: description,
|
||||
LastOpenedAt: 0,
|
||||
}
|
||||
|
@ -2134,7 +2134,7 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des
|
|||
CommunityDescription: processedDescription,
|
||||
Logger: m.logger,
|
||||
CommunityDescriptionProtocolMessage: payload,
|
||||
MemberIdentity: &m.identity.PublicKey,
|
||||
MemberIdentity: m.identity,
|
||||
ID: pubKey,
|
||||
ControlNode: signer,
|
||||
Shard: shard.FromProtobuff(communityShard),
|
||||
|
@ -3851,7 +3851,7 @@ func (m *Manager) dbRecordBundleToCommunity(r *CommunityRecordBundle) (*Communit
|
|||
descriptionEncryptor = m
|
||||
}
|
||||
|
||||
return recordBundleToCommunity(r, &m.identity.PublicKey, m.installationID, m.logger, m.timesource, descriptionEncryptor, func(community *Community) error {
|
||||
return recordBundleToCommunity(r, m.identity, m.installationID, m.logger, m.timesource, descriptionEncryptor, func(community *Community) error {
|
||||
_, description, err := m.preprocessDescription(community.ID(), community.config.CommunityDescription)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -71,7 +71,7 @@ func recordToRequestToJoin(r *RequestToJoinRecord) *RequestToJoin {
|
|||
}
|
||||
}
|
||||
|
||||
func recordBundleToCommunity(r *CommunityRecordBundle, memberIdentity *ecdsa.PublicKey, installationID string,
|
||||
func recordBundleToCommunity(r *CommunityRecordBundle, memberIdentity *ecdsa.PrivateKey, installationID string,
|
||||
logger *zap.Logger, timesource common.TimeSource, encryptor DescriptionEncryptor, initializer func(*Community) error) (*Community, error) {
|
||||
var privateKey *ecdsa.PrivateKey
|
||||
var controlNode *ecdsa.PublicKey
|
||||
|
|
|
@ -48,7 +48,7 @@ func (s *PersistenceSuite) SetupTest() {
|
|||
s.Require().NoError(err)
|
||||
|
||||
s.db = &Persistence{db: db, recordBundleToCommunity: func(r *CommunityRecordBundle) (*Community, error) {
|
||||
return recordBundleToCommunity(r, &s.identity.PublicKey, "", nil, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil)
|
||||
return recordBundleToCommunity(r, s.identity, "", nil, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil)
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ func (s *PersistenceSuite) makeNewCommunity(identity *ecdsa.PrivateKey) *Communi
|
|||
s.Require().NoError(err, "crypto.GenerateKey shouldn't give any error")
|
||||
|
||||
com, err := New(Config{
|
||||
MemberIdentity: &identity.PublicKey,
|
||||
MemberIdentity: identity,
|
||||
PrivateKey: comPrivKey,
|
||||
ControlNode: &comPrivKey.PublicKey,
|
||||
ControlDevice: true,
|
||||
|
|
|
@ -1208,6 +1208,11 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) testViewChannelPermissions(v
|
|||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// bob should not be in the bloom filter list
|
||||
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
||||
s.Require().NoError(err)
|
||||
s.Require().False(community.IsMemberLikelyInChat(chat.CommunityChatID()))
|
||||
|
||||
// make bob satisfy channel criteria
|
||||
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermissionRequest.TokenCriteria[0])
|
||||
defer s.resetMockedBalances() // reset mocked balances, this test in run with different test cases
|
||||
|
@ -1245,6 +1250,11 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) testViewChannelPermissions(v
|
|||
s.Require().Len(response.Messages(), 1)
|
||||
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
||||
|
||||
// bob should be in the bloom filter list
|
||||
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
||||
s.Require().NoError(err)
|
||||
s.Require().True(community.IsMemberLikelyInChat(chat.CommunityChatID()))
|
||||
|
||||
// bob can/can't post reactions
|
||||
response, err = s.bob.SendEmojiReaction(context.Background(), chat.ID, msg.ID, protobuf.EmojiReaction_THUMBS_UP)
|
||||
if !viewersCanAddReactions {
|
||||
|
|
|
@ -66,7 +66,7 @@ func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {
|
|||
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||
}
|
||||
|
||||
func generateSharedKey(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
||||
func GenerateSharedKey(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
||||
|
||||
const encryptedPayloadKeyLength = 16
|
||||
|
||||
|
@ -87,7 +87,7 @@ func buildGroupRekeyMessage(privateKey *ecdsa.PrivateKey, groupID []byte, timest
|
|||
|
||||
for _, k := range keys {
|
||||
|
||||
sharedKey, err := generateSharedKey(privateKey, k)
|
||||
sharedKey, err := GenerateSharedKey(privateKey, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ func decryptGroupRekeyMessage(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.Pub
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
sharedKey, err := generateSharedKey(privateKey, publicKey)
|
||||
sharedKey, err := GenerateSharedKey(privateKey, publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -427,7 +427,7 @@ func (p *Protocol) EncryptCommunityGrants(privateKey *ecdsa.PrivateKey, recipien
|
|||
grants := make(map[uint32][]byte)
|
||||
|
||||
for recipientKey, grant := range recipientGrants {
|
||||
sharedKey, err := generateSharedKey(privateKey, recipientKey)
|
||||
sharedKey, err := GenerateSharedKey(privateKey, recipientKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ func (p *Protocol) DecryptCommunityGrant(myIdentityKey *ecdsa.PrivateKey, sender
|
|||
return nil, errors.New("can't find related grant in the map")
|
||||
}
|
||||
|
||||
sharedKey, err := generateSharedKey(myIdentityKey, senderKey)
|
||||
sharedKey, err := GenerateSharedKey(myIdentityKey, senderKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -144,6 +144,13 @@ message CommunityChat {
|
|||
int32 position = 5;
|
||||
bool viewers_can_post_reactions = 6;
|
||||
bool hide_if_permissions_not_met = 7;
|
||||
CommunityBloomFilter members_list = 8;
|
||||
}
|
||||
|
||||
message CommunityBloomFilter {
|
||||
bytes data = 1;
|
||||
uint64 m = 2;
|
||||
uint64 k = 3;
|
||||
}
|
||||
|
||||
message CommunityCategory {
|
||||
|
|
Loading…
Reference in New Issue