diff --git a/protocol/communities/community.go b/protocol/communities/community.go index 9a8a62298..4025964b2 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -59,9 +59,10 @@ type Community struct { config *Config mutex sync.Mutex timesource common.TimeSource + encryptor DescriptionEncryptor } -func New(config Config, timesource common.TimeSource) (*Community, error) { +func New(config Config, timesource common.TimeSource, encryptor DescriptionEncryptor) (*Community, error) { if config.MemberIdentity == nil { return nil, errors.New("no member identity") } @@ -78,9 +79,11 @@ func New(config Config, timesource common.TimeSource) (*Community, error) { config.Logger = logger } - community := &Community{config: &config, timesource: timesource} - community.initialize() - return community, nil + if config.CommunityDescription == nil { + config.CommunityDescription = &protobuf.CommunityDescription{} + } + + return &Community{config: &config, timesource: timesource, encryptor: encryptor}, nil } type CommunityAdminSettings struct { @@ -501,13 +504,6 @@ func (o *Community) GetMemberPubkeys() []*ecdsa.PublicKey { return nil } -func (o *Community) initialize() { - if o.config.CommunityDescription == nil { - o.config.CommunityDescription = &protobuf.CommunityDescription{} - - } -} - type CommunitySettings struct { CommunityID string `json:"communityId"` HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled"` @@ -1377,11 +1373,20 @@ func (o *Community) Description() *protobuf.CommunityDescription { } func (o *Community) marshaledDescription() ([]byte, error) { + clone := proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription) + // This is only workaround to lower the size of the message that goes over the wire, // see https://github.com/status-im/status-desktop/issues/12188 - clone := o.CreateDeepCopy() - clone.DehydrateChannelsMembers() - return proto.Marshal(clone.config.CommunityDescription) + dehydrateChannelsMembers(o.IDString(), clone) + + if o.encryptor != nil { + err := encryptDescription(o.encryptor, o, clone) + if err != nil { + return nil, err + } + } + + return proto.Marshal(clone) } func (o *Community) MarshaledDescription() ([]byte, error) { @@ -1419,28 +1424,28 @@ func (o *Community) ToProtocolMessageBytes() ([]byte, error) { return o.toProtocolMessageBytes() } -func (o *Community) DehydrateChannelsMembers() { +func channelHasTokenPermissions(communityID string, channelID string, permissions map[string]*protobuf.CommunityTokenPermission) bool { + for _, tokenPermission := range permissions { + if includes(tokenPermission.ChatIds, communityID+channelID) { + return true + } + } + return false +} + +func dehydrateChannelsMembers(communityID string, description *protobuf.CommunityDescription) { // To save space, we don't attach members for channels without permissions, // otherwise the message will hit waku msg size limit. - for channelID, channel := range o.chats() { - if !o.ChannelHasTokenPermissions(o.ChatID(channelID)) { + for channelID, channel := range description.Chats { + if !channelHasTokenPermissions(communityID, channelID, description.TokenPermissions) { channel.Members = map[string]*protobuf.CommunityMember{} // clean members } } } -func HydrateChannelsMembers(communityID string, description *protobuf.CommunityDescription) { - channelHasTokenPermissions := func(channelID string) bool { - for _, tokenPermission := range description.TokenPermissions { - if includes(tokenPermission.ChatIds, communityID+channelID) { - return true - } - } - return false - } - +func hydrateChannelsMembers(communityID string, description *protobuf.CommunityDescription) { for channelID, channel := range description.Chats { - if !channelHasTokenPermissions(channelID) { + if !channelHasTokenPermissions(communityID, channelID, description.TokenPermissions) { channel.Members = make(map[string]*protobuf.CommunityMember) for pubKey, member := range description.Members { channel.Members[pubKey] = member @@ -1597,14 +1602,15 @@ func (o *Community) HasTokenPermissions() bool { return len(o.tokenPermissions()) > 0 } +func (o *Community) channelEncrypted(channelID string) bool { + return o.channelHasTokenPermissions(o.ChatID(channelID)) +} + func (o *Community) ChannelEncrypted(channelID string) bool { return o.ChannelHasTokenPermissions(o.ChatID(channelID)) } -func (o *Community) ChannelHasTokenPermissions(chatID string) bool { - o.mutex.Lock() - defer o.mutex.Unlock() - +func (o *Community) channelHasTokenPermissions(chatID string) bool { for _, tokenPermission := range o.tokenPermissions() { if includes(tokenPermission.ChatIds, chatID) { return true @@ -1614,6 +1620,12 @@ func (o *Community) ChannelHasTokenPermissions(chatID string) bool { return false } +func (o *Community) ChannelHasTokenPermissions(chatID string) bool { + o.mutex.Lock() + defer o.mutex.Unlock() + return o.channelHasTokenPermissions(chatID) +} + func TokenPermissionsByType(permissions map[string]*CommunityTokenPermission, permissionType protobuf.CommunityTokenPermission_Type) []*CommunityTokenPermission { result := make([]*CommunityTokenPermission, 0) for _, tokenPermission := range permissions { diff --git a/protocol/communities/community_description_encryption.go b/protocol/communities/community_description_encryption.go new file mode 100644 index 000000000..63b51dfbc --- /dev/null +++ b/protocol/communities/community_description_encryption.go @@ -0,0 +1,95 @@ +package communities + +import ( + "github.com/golang/protobuf/proto" + + "go.uber.org/zap" + + "github.com/status-im/status-go/protocol/protobuf" +) + +type DescriptionEncryptor interface { + encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) + encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error) + decryptCommunityDescription(keyIDSeqNo string, d []byte) (*protobuf.CommunityDescription, error) +} + +// Encrypts members and chats +func encryptDescription(encryptor DescriptionEncryptor, community *Community, description *protobuf.CommunityDescription) error { + description.PrivateData = make(map[string][]byte) + + for channelID, channel := range description.Chats { + if !community.channelEncrypted(channelID) { + continue + } + + descriptionToEncrypt := &protobuf.CommunityDescription{ + Chats: map[string]*protobuf.CommunityChat{ + channelID: proto.Clone(channel).(*protobuf.CommunityChat), + }, + } + + keyIDSeqNo, encryptedDescription, err := encryptor.encryptCommunityDescriptionChannel(community, channelID, descriptionToEncrypt) + if err != nil { + return err + } + + // Set private data and cleanup unencrypted channel's members + description.PrivateData[keyIDSeqNo] = encryptedDescription + channel.Members = make(map[string]*protobuf.CommunityMember) + } + + if community.Encrypted() { + descriptionToEncrypt := &protobuf.CommunityDescription{ + Members: description.Members, + Chats: description.Chats, + } + + keyIDSeqNo, encryptedDescription, err := encryptor.encryptCommunityDescription(community, descriptionToEncrypt) + if err != nil { + return err + } + + // Set private data and cleanup unencrypted members and chats + description.PrivateData[keyIDSeqNo] = encryptedDescription + description.Members = make(map[string]*protobuf.CommunityMember) + description.Chats = make(map[string]*protobuf.CommunityChat) + } + + return nil +} + +// Decrypts members and chats +func decryptDescription(encryptor DescriptionEncryptor, description *protobuf.CommunityDescription, logger *zap.Logger) error { + for keyIDSeqNo, encryptedDescription := range description.PrivateData { + decryptedDescription, err := encryptor.decryptCommunityDescription(keyIDSeqNo, encryptedDescription) + if err != nil { + // ignore error, try to decrypt next data + logger.Debug("failed to decrypt community private data", zap.String("keyIDSeqNo", keyIDSeqNo), zap.Error(err)) + continue + } + + for pk, member := range decryptedDescription.Members { + if description.Members == nil { + description.Members = make(map[string]*protobuf.CommunityMember) + } + description.Members[pk] = member + } + + for id, decryptedChannel := range decryptedDescription.Chats { + if description.Chats == nil { + description.Chats = make(map[string]*protobuf.CommunityChat) + } + + if channel := description.Chats[id]; channel != nil { + if len(channel.Members) == 0 { + channel.Members = decryptedChannel.Members + } + } else { + description.Chats[id] = decryptedChannel + } + } + } + + return nil +} diff --git a/protocol/communities/community_description_encryption_test.go b/protocol/communities/community_description_encryption_test.go new file mode 100644 index 000000000..982ee113b --- /dev/null +++ b/protocol/communities/community_description_encryption_test.go @@ -0,0 +1,181 @@ +package communities + +import ( + "crypto/ecdsa" + "errors" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/uuid" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" + + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/protobuf" +) + +func TestCommunityEncryptionDescriptionSuite(t *testing.T) { + suite.Run(t, new(CommunityEncryptionDescriptionSuite)) +} + +type CommunityEncryptionDescriptionSuite struct { + suite.Suite + + descriptionEncryptor *DescriptionEncryptorMock + identity *ecdsa.PrivateKey + communityID []byte + logger *zap.Logger +} + +func (s *CommunityEncryptionDescriptionSuite) SetupTest() { + s.descriptionEncryptor = &DescriptionEncryptorMock{ + descriptions: map[string]*protobuf.CommunityDescription{}, + channelIDToKeyIDSeqNo: map[string]string{}, + } + + identity, err := crypto.GenerateKey() + s.Require().NoError(err) + s.identity = identity + s.communityID = crypto.CompressPubkey(&identity.PublicKey) + + s.logger, err = zap.NewDevelopment() + s.Require().NoError(err) +} + +type DescriptionEncryptorMock struct { + descriptions map[string]*protobuf.CommunityDescription + channelIDToKeyIDSeqNo map[string]string +} + +func (dem *DescriptionEncryptorMock) encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) { + keyIDSeqNo := uuid.New().String() + dem.descriptions[keyIDSeqNo] = d + return keyIDSeqNo, []byte("encryptedDescription"), nil +} + +func (dem *DescriptionEncryptorMock) encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error) { + keyIDSeqNo := uuid.New().String() + dem.descriptions[keyIDSeqNo] = d + dem.channelIDToKeyIDSeqNo[channelID] = keyIDSeqNo + return keyIDSeqNo, []byte("encryptedDescription"), nil +} + +func (dem *DescriptionEncryptorMock) decryptCommunityDescription(keyIDSeqNo string, d []byte) (*protobuf.CommunityDescription, error) { + description := dem.descriptions[keyIDSeqNo] + if description == nil { + return nil, errors.New("no key to decrypt private data") + } + return description, nil +} + +func (dem *DescriptionEncryptorMock) forgetAllKeys() { + dem.descriptions = make(map[string]*protobuf.CommunityDescription) +} + +func (dem *DescriptionEncryptorMock) forgetChannelKeys() { + for _, keyIDSeqNo := range dem.channelIDToKeyIDSeqNo { + delete(dem.descriptions, keyIDSeqNo) + } +} + +func (s *CommunityEncryptionDescriptionSuite) description() *protobuf.CommunityDescription { + return &protobuf.CommunityDescription{ + IntroMessage: "one of not encrypted fields", + Members: map[string]*protobuf.CommunityMember{ + "memberA": &protobuf.CommunityMember{}, + "memberB": &protobuf.CommunityMember{}, + }, + Chats: map[string]*protobuf.CommunityChat{ + "channelA": &protobuf.CommunityChat{ + Members: map[string]*protobuf.CommunityMember{ + "memberA": &protobuf.CommunityMember{}, + "memberB": &protobuf.CommunityMember{}, + }, + }, + "channelB": &protobuf.CommunityChat{ + Members: map[string]*protobuf.CommunityMember{ + "memberA": &protobuf.CommunityMember{}, + }, + }, + }, + PrivateData: map[string][]byte{}, + + // ensure community and channel encryption + TokenPermissions: map[string]*protobuf.CommunityTokenPermission{ + "community-level-permission": &protobuf.CommunityTokenPermission{ + Id: "community-level-permission", + Type: protobuf.CommunityTokenPermission_BECOME_MEMBER, + TokenCriteria: []*protobuf.TokenCriteria{}, + ChatIds: []string{}, + }, + "channel-level-permission": &protobuf.CommunityTokenPermission{ + Id: "community-level-permission", + Type: protobuf.CommunityTokenPermission_BECOME_MEMBER, + TokenCriteria: []*protobuf.TokenCriteria{}, + ChatIds: []string{types.EncodeHex(crypto.CompressPubkey(&s.identity.PublicKey)) + "channelB"}, + }, + }, + } +} + +func (s *CommunityEncryptionDescriptionSuite) TestEncryptionDecryption() { + description := s.description() + + err := encryptDescription(s.descriptionEncryptor, &Community{ + config: &Config{ID: &s.identity.PublicKey, CommunityDescription: description}, + }, description) + s.Require().NoError(err) + s.Require().Len(description.PrivateData, 2) + + // members and chats should become empty (encrypted) + s.Require().Empty(description.Members) + s.Require().Empty(description.Chats) + s.Require().Equal(description.IntroMessage, "one of not encrypted fields") + + // members and chats should be brought back + err = decryptDescription(s.descriptionEncryptor, description, s.logger) + s.Require().NoError(err) + s.Require().Len(description.Members, 2) + s.Require().Len(description.Chats, 2) + s.Require().Len(description.Chats["channelA"].Members, 2) + s.Require().Len(description.Chats["channelB"].Members, 1) + s.Require().Equal(description.IntroMessage, "one of not encrypted fields") +} + +func (s *CommunityEncryptionDescriptionSuite) TestDecryption_NoKeys() { + encryptedDescription := func() *protobuf.CommunityDescription { + description := s.description() + + err := encryptDescription(s.descriptionEncryptor, &Community{ + config: &Config{ID: &s.identity.PublicKey, CommunityDescription: description}, + }, description) + s.Require().NoError(err) + + return description + }() + + description := proto.Clone(encryptedDescription).(*protobuf.CommunityDescription) + // forget channel keys, so channel members can't be decrypted + s.descriptionEncryptor.forgetChannelKeys() + + // encrypted channel should have no members + err := decryptDescription(s.descriptionEncryptor, description, s.logger) + s.Require().NoError(err) + s.Require().Len(description.Members, 2) + s.Require().Len(description.Chats, 2) + s.Require().Len(description.Chats["channelA"].Members, 2) + s.Require().Len(description.Chats["channelB"].Members, 0) // encrypted channel + s.Require().Equal(description.IntroMessage, "one of not encrypted fields") + + description = proto.Clone(encryptedDescription).(*protobuf.CommunityDescription) + // forget the keys, so chats and members can't be decrypted + s.descriptionEncryptor.forgetAllKeys() + + // members and chats should be empty + err = decryptDescription(s.descriptionEncryptor, description, s.logger) + s.Require().NoError(err) + s.Require().Empty(description.Members) + s.Require().Empty(description.Chats) + s.Require().Equal(description.IntroMessage, "one of not encrypted fields") +} diff --git a/protocol/communities/community_encryption_key_action.go b/protocol/communities/community_encryption_key_action.go index ce7e7abd1..3f84db783 100644 --- a/protocol/communities/community_encryption_key_action.go +++ b/protocol/communities/community_encryption_key_action.go @@ -2,6 +2,11 @@ package communities import "github.com/status-im/status-go/protocol/protobuf" +type KeyDistributor interface { + Generate(community *Community, keyActions *EncryptionKeyActions) error + Distribute(community *Community, keyActions *EncryptionKeyActions) error +} + type EncryptionKeyActionType int const ( diff --git a/protocol/communities/community_encryption_key_action_test.go b/protocol/communities/community_encryption_key_action_test.go index 19514ea12..be237e945 100644 --- a/protocol/communities/community_encryption_key_action_test.go +++ b/protocol/communities/community_encryption_key_action_test.go @@ -33,7 +33,7 @@ func createTestCommunity(identity *ecdsa.PrivateKey) (*Community, error) { MemberIdentity: &identity.PublicKey, } - return New(config, &TimeSourceStub{}) + return New(config, &TimeSourceStub{}, &DescriptionEncryptorMock{}) } func TestCommunityEncryptionKeyActionSuite(t *testing.T) { diff --git a/protocol/communities/community_event.go b/protocol/communities/community_event.go index ae2a31ccd..fa37102ce 100644 --- a/protocol/communities/community_event.go +++ b/protocol/communities/community_event.go @@ -200,6 +200,13 @@ func (o *Community) UpdateCommunityByEvents(communityEventMessage *CommunityEven // during saving the community o.mergeCommunityEvents(communityEventMessage) + if o.encryptor != nil { + err = decryptDescription(o.encryptor, description, o.config.Logger) + if err != nil { + return err + } + } + o.config.CommunityDescription = description o.config.CommunityDescriptionProtocolMessage = communityEventMessage.EventsBaseCommunityDescription diff --git a/protocol/communities/community_test.go b/protocol/communities/community_test.go index dc575e70e..90591e2c1 100644 --- a/protocol/communities/community_test.go +++ b/protocol/communities/community_test.go @@ -440,7 +440,7 @@ func (s *CommunitySuite) TestValidateRequestToJoin() { for _, tc := range testCases { s.Run(tc.name, func() { - org, err := New(tc.config, &TimeSourceStub{}) + org, err := New(tc.config, &TimeSourceStub{}, &DescriptionEncryptorMock{}) s.Require().NoError(err) err = org.ValidateRequestToJoin(tc.signer, tc.request) s.Require().Equal(tc.err, err) @@ -512,7 +512,7 @@ func (s *CommunitySuite) TestCanPost() { s.Run(tc.name, func() { var grant []byte var err error - org, err := New(tc.config, &TimeSourceStub{}) + org, err := New(tc.config, &TimeSourceStub{}, &DescriptionEncryptorMock{}) s.Require().NoError(err) if tc.grant == validGrant { @@ -882,7 +882,7 @@ func (s *CommunitySuite) buildCommunity(owner *ecdsa.PublicKey) *Community { config.ID = owner config.CommunityDescription = s.buildCommunityDescription() - org, err := New(config, &TimeSourceStub{}) + org, err := New(config, &TimeSourceStub{}, &DescriptionEncryptorMock{}) s.Require().NoError(err) return org } diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 478e1a1ad..dbe7702bd 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -4,11 +4,13 @@ import ( "context" "crypto/ecdsa" "database/sql" + "encoding/hex" "fmt" "io/ioutil" "net" "os" "sort" + "strconv" "strings" "sync" "time" @@ -98,6 +100,7 @@ type Manager struct { stopped bool RekeyInterval time.Duration PermissionChecker PermissionChecker + keyDistributor KeyDistributor } type HistoryArchiveDownloadTask struct { @@ -218,7 +221,7 @@ type OwnerVerifier interface { SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) } -func NewManager(identity *ecdsa.PrivateKey, installationID string, db *sql.DB, encryptor *encryption.Protocol, logger *zap.Logger, ensverifier *ens.Verifier, ownerVerifier OwnerVerifier, transport *transport.Transport, timesource common.TimeSource, torrentConfig *params.TorrentConfig, opts ...ManagerOption) (*Manager, error) { +func NewManager(identity *ecdsa.PrivateKey, installationID string, db *sql.DB, encryptor *encryption.Protocol, logger *zap.Logger, ensverifier *ens.Verifier, ownerVerifier OwnerVerifier, transport *transport.Transport, timesource common.TimeSource, keyDistributor KeyDistributor, torrentConfig *params.TorrentConfig, opts ...ManagerOption) (*Manager, error) { if identity == nil { return nil, errors.New("empty identity") } @@ -257,6 +260,7 @@ func NewManager(identity *ecdsa.PrivateKey, installationID string, db *sql.DB, e torrentConfig: torrentConfig, torrentTasks: make(map[string]metainfo.Hash), historyArchiveDownloadTasks: make(map[string]*HistoryArchiveDownloadTask), + keyDistributor: keyDistributor, } manager.persistence = &Persistence{ @@ -741,7 +745,12 @@ func (m *Manager) CreateCommunity(request *requests.CreateCommunity, publish boo CommunityDescription: description, Shard: nil, } - community, err := New(config, m.timesource) + + var descriptionEncryptor DescriptionEncryptor + if m.encryptor != nil { + descriptionEncryptor = m + } + community, err := New(config, m.timesource, descriptionEncryptor) if err != nil { return nil, err } @@ -777,11 +786,23 @@ func (m *Manager) CreateCommunityTokenPermission(request *requests.CreateCommuni return nil, nil, err } + originCommunity := community.CreateDeepCopy() + community, changes, err := m.createCommunityTokenPermission(request, community) if err != nil { return nil, nil, err } + // ensure key is generated before marshaling, + // as it requires key to encrypt description + if m.keyDistributor != nil && community.IsControlNode() { + encryptionKeyActions := EvaluateCommunityEncryptionKeyActions(originCommunity, community) + err := m.keyDistributor.Generate(community, encryptionKeyActions) + if err != nil { + return nil, nil, err + } + } + err = m.saveAndPublish(community) if err != nil { return nil, nil, err @@ -1209,11 +1230,15 @@ func (m *Manager) ImportCommunity(key *ecdsa.PrivateKey, clock uint64) (*Communi MemberIdentity: &m.identity.PublicKey, CommunityDescription: description, } - community, err = New(config, m.timesource) + + var descriptionEncryptor DescriptionEncryptor + if m.encryptor != nil { + descriptionEncryptor = m + } + community, err = New(config, m.timesource, descriptionEncryptor) if err != nil { return nil, err } - } else { community.config.PrivateKey = key community.config.ControlDevice = true @@ -1541,13 +1566,15 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des id = crypto.CompressPubkey(signer) } - community, err := m.GetByID(id) + err = m.preprocessDescription(id, description) if err != nil { return nil, err } - // Workaround for https://github.com/status-im/status-desktop/issues/12188 - HydrateChannelsMembers(types.EncodeHex(id), description) + community, err := m.GetByID(id) + if err != nil { + return nil, err + } // We should queue only if the community has a token owner, and the owner has been verified hasTokenOwnership := HasTokenOwnership(description) @@ -1568,7 +1595,11 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des Shard: shard.FromProtobuff(communityShard), } - community, err = New(config, m.timesource) + var descriptionEncryptor DescriptionEncryptor + if m.encryptor != nil { + descriptionEncryptor = m + } + community, err = New(config, m.timesource, descriptionEncryptor) if err != nil { return nil, err } @@ -1608,7 +1639,20 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des return m.handleCommunityDescriptionMessageCommon(community, description, payload, verifiedOwner) } +func (m *Manager) preprocessDescription(id types.HexBytes, description *protobuf.CommunityDescription) error { + err := decryptDescription(m, description, m.logger) + if err != nil { + return err + } + + // Workaround for https://github.com/status-im/status-desktop/issues/12188 + hydrateChannelsMembers(types.EncodeHex(id), description) + + return nil +} + func (m *Manager) handleCommunityDescriptionMessageCommon(community *Community, description *protobuf.CommunityDescription, payload []byte, newControlNode *ecdsa.PublicKey) (*CommunityResponse, error) { + changes, err := community.UpdateCommunityDescription(description, payload, newControlNode) if err != nil { return nil, err @@ -1797,6 +1841,15 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message if community.IsControlNode() { community.config.EventsData = nil // clear events, they are already applied community.increaseClock() + + if m.keyDistributor != nil { + encryptionKeyActions := EvaluateCommunityEncryptionKeyActions(originCommunity, community) + err := m.keyDistributor.Generate(community, encryptionKeyActions) + if err != nil { + return nil, err + } + } + err = m.persistence.SaveCommunity(community) if err != nil { return nil, err @@ -2828,6 +2881,11 @@ func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, return nil, ErrNotAuthorized } + err = m.preprocessDescription(community.ID(), request.Community) + if err != nil { + return nil, err + } + _, err = community.UpdateCommunityDescription(request.Community, appMetadataMsg, nil) if err != nil { return nil, err @@ -3195,8 +3253,18 @@ func (m *Manager) BanUserFromCommunity(request *requests.BanUserFromCommunity) ( } func (m *Manager) dbRecordBundleToCommunity(r *CommunityRecordBundle) (*Community, error) { - return recordBundleToCommunity(r, &m.identity.PublicKey, m.installationID, m.logger, m.timesource, func(community *Community) error { - err := community.updateCommunityDescriptionByEvents() + var descriptionEncryptor DescriptionEncryptor + if m.encryptor != nil { + descriptionEncryptor = m + } + + return recordBundleToCommunity(r, &m.identity.PublicKey, m.installationID, m.logger, m.timesource, descriptionEncryptor, func(community *Community) error { + err := m.preprocessDescription(community.ID(), community.config.CommunityDescription) + if err != nil { + return err + } + + err = community.updateCommunityDescriptionByEvents() if err != nil { return err } @@ -3210,9 +3278,6 @@ func (m *Manager) dbRecordBundleToCommunity(r *CommunityRecordBundle) (*Communit community.config.PubsubTopicPrivateKey = privKey } - // Workaround for https://github.com/status-im/status-desktop/issues/12188 - HydrateChannelsMembers(community.IDString(), community.config.CommunityDescription) - return nil }) } @@ -5114,6 +5179,65 @@ func (m *Manager) SetCuratedCommunities(communities *CuratedCommunities) error { return m.persistence.SetCuratedCommunities(communities) } +func (m *Manager) encryptCommunityDescriptionImpl(groupID []byte, d *protobuf.CommunityDescription) (string, []byte, error) { + payload, err := proto.Marshal(d) + if err != nil { + return "", nil, err + } + + encryptedPayload, ratchet, newSeqNo, err := m.encryptor.EncryptWithHashRatchet(groupID, payload) + if err != nil { + return "", nil, err + } + + keyID, err := ratchet.GetKeyID() + if err != nil { + return "", nil, err + } + + keyIDSeqNo := fmt.Sprintf("%s%d", hex.EncodeToString(keyID), newSeqNo) + + return keyIDSeqNo, encryptedPayload, nil +} + +func (m *Manager) encryptCommunityDescription(community *Community, d *protobuf.CommunityDescription) (string, []byte, error) { + return m.encryptCommunityDescriptionImpl(community.ID(), d) +} + +func (m *Manager) encryptCommunityDescriptionChannel(community *Community, channelID string, d *protobuf.CommunityDescription) (string, []byte, error) { + return m.encryptCommunityDescriptionImpl([]byte(community.IDString()+channelID), d) +} + +func (m *Manager) decryptCommunityDescription(keyIDSeqNo string, d []byte) (*protobuf.CommunityDescription, error) { + const hashHexLength = 64 + if len(keyIDSeqNo) <= hashHexLength { + return nil, errors.New("invalid keyIDSeqNo") + } + + keyID, err := hex.DecodeString(keyIDSeqNo[:hashHexLength]) + if err != nil { + return nil, err + } + + seqNo, err := strconv.ParseUint(keyIDSeqNo[hashHexLength:], 10, 32) + if err != nil { + return nil, err + } + + decryptedPayload, err := m.encryptor.DecryptWithHashRatchet(keyID, uint32(seqNo), d) + if err != nil { + return nil, err + } + + var description protobuf.CommunityDescription + err = proto.Unmarshal(decryptedPayload, &description) + if err != nil { + return nil, err + } + + return &description, nil +} + func ToLinkPreveiwThumbnail(image images.IdentityImage) (*common.LinkPreviewThumbnail, error) { thumbnail := &common.LinkPreviewThumbnail{} diff --git a/protocol/communities/manager_test.go b/protocol/communities/manager_test.go index d67d2a468..65f4913a0 100644 --- a/protocol/communities/manager_test.go +++ b/protocol/communities/manager_test.go @@ -55,7 +55,7 @@ func (s *ManagerSuite) buildManager(ownerVerifier OwnerVerifier) *Manager { key, err := crypto.GenerateKey() s.Require().NoError(err) s.Require().NoError(err) - m, err := NewManager(key, "", db, nil, nil, nil, ownerVerifier, nil, &TimeSourceStub{}, nil) + m, err := NewManager(key, "", db, nil, nil, nil, ownerVerifier, nil, &TimeSourceStub{}, nil, nil) s.Require().NoError(err) s.Require().NoError(m.Start()) return m @@ -169,7 +169,7 @@ func (s *ManagerSuite) setupManagerForTokenPermissions() (*Manager, *testCollect WithTokenManager(tm), } - m, err := NewManager(key, "", db, nil, nil, nil, nil, nil, &TimeSourceStub{}, nil, options...) + m, err := NewManager(key, "", db, nil, nil, nil, nil, nil, &TimeSourceStub{}, nil, nil, options...) s.Require().NoError(err) s.Require().NoError(m.Start()) diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index ab6ce5c45..7bac7e3df 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -260,32 +260,7 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st return nil, err } - defer func() { - if err != nil { - // Don't shadow original error - _ = rows.Close() - return - - } - err = rows.Close() - }() - - for rows.Next() { - r, err := scanCommunity(rows.Scan) - if err != nil { - return nil, err - } - - org, err := p.recordBundleToCommunity(r) - if err != nil { - return nil, err - } - - response = append(response, org) - } - - return response, nil - + return p.rowsToCommunities(rows) } func (p *Persistence) AllCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) { @@ -302,7 +277,7 @@ func (p *Persistence) SpectatedCommunities(memberIdentity *ecdsa.PublicKey) ([]* return p.queryCommunities(memberIdentity, query) } -func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *sql.Rows) (comms []*Community, err error) { +func (p *Persistence) rowsToCommunityRecords(rows *sql.Rows) (result []*CommunityRecordBundle, err error) { defer func() { if err != nil { // Don't shadow original error @@ -318,8 +293,20 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s if err != nil { return nil, err } + result = append(result, r) + } - org, err := p.recordBundleToCommunity(r) + return result, nil +} + +func (p *Persistence) rowsToCommunities(rows *sql.Rows) (comms []*Community, err error) { + records, err := p.rowsToCommunityRecords(rows) + if err != nil { + return nil, err + } + + for _, record := range records { + org, err := p.recordBundleToCommunity(record) if err != nil { return nil, err } @@ -338,7 +325,7 @@ func (p *Persistence) JoinedAndPendingCommunitiesWithRequests(memberIdentity *ec return nil, err } - return p.rowsToCommunities(memberIdentity, rows) + return p.rowsToCommunities(rows) } func (p *Persistence) DeletedCommunities(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) { @@ -349,7 +336,7 @@ func (p *Persistence) DeletedCommunities(memberIdentity *ecdsa.PublicKey) (comms return nil, err } - return p.rowsToCommunities(memberIdentity, rows) + return p.rowsToCommunities(rows) } func (p *Persistence) CommunitiesWithPrivateKey(memberIdentity *ecdsa.PublicKey) ([]*Community, error) { diff --git a/protocol/communities/persistence_mapping.go b/protocol/communities/persistence_mapping.go index 7c8b43fc4..dc499461b 100644 --- a/protocol/communities/persistence_mapping.go +++ b/protocol/communities/persistence_mapping.go @@ -70,7 +70,7 @@ func recordToRequestToJoin(r *RequestToJoinRecord) *RequestToJoin { } func recordBundleToCommunity(r *CommunityRecordBundle, memberIdentity *ecdsa.PublicKey, installationID string, - logger *zap.Logger, timesource common.TimeSource, initializer func(*Community) error) (*Community, error) { + logger *zap.Logger, timesource common.TimeSource, encryptor DescriptionEncryptor, initializer func(*Community) error) (*Community, error) { var privateKey *ecdsa.PrivateKey var controlNode *ecdsa.PublicKey var err error @@ -135,7 +135,7 @@ func recordBundleToCommunity(r *CommunityRecordBundle, memberIdentity *ecdsa.Pub Shard: s, } - community, err := New(config, timesource) + community, err := New(config, timesource, encryptor) if err != nil { return nil, err } diff --git a/protocol/communities/persistence_test.go b/protocol/communities/persistence_test.go index bd2d6b52c..6fc1bf9e2 100644 --- a/protocol/communities/persistence_test.go +++ b/protocol/communities/persistence_test.go @@ -46,7 +46,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{}, nil) + return recordBundleToCommunity(r, &s.identity.PublicKey, "", nil, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil) }} } @@ -263,7 +263,7 @@ func (s *PersistenceSuite) makeNewCommunity(identity *ecdsa.PrivateKey) *Communi ControlNode: &comPrivKey.PublicKey, ControlDevice: true, ID: &comPrivKey.PublicKey, - }, &TimeSourceStub{}) + }, &TimeSourceStub{}, &DescriptionEncryptorMock{}) s.NoError(err, "New shouldn't give any error") md, err := com.MarshaledDescription() diff --git a/protocol/communities_key_distributor.go b/protocol/communities_key_distributor.go index 8a36e1e6f..55dcbf698 100644 --- a/protocol/communities_key_distributor.go +++ b/protocol/communities_key_distributor.go @@ -10,28 +10,34 @@ import ( "github.com/status-im/status-go/protocol/protobuf" ) -type CommunitiesKeyDistributor interface { - Distribute(community *communities.Community, keyActions *communities.EncryptionKeyActions) error -} - type CommunitiesKeyDistributorImpl struct { sender *common.MessageSender encryptor *encryption.Protocol } +func (ckd *CommunitiesKeyDistributorImpl) Generate(community *communities.Community, keyActions *communities.EncryptionKeyActions) error { + if !community.IsControlNode() { + return communities.ErrNotControlNode + } + return iterateActions(community, keyActions, ckd.generateKey) +} + func (ckd *CommunitiesKeyDistributorImpl) Distribute(community *communities.Community, keyActions *communities.EncryptionKeyActions) error { if !community.IsControlNode() { return communities.ErrNotControlNode } + return iterateActions(community, keyActions, ckd.distributeKey) +} - err := ckd.distributeKey(community, community.ID(), &keyActions.CommunityKeyAction) +func iterateActions(community *communities.Community, keyActions *communities.EncryptionKeyActions, fn func(community *communities.Community, hashRatchetGroupID []byte, keyAction *communities.EncryptionKeyAction) error) error { + err := fn(community, community.ID(), &keyActions.CommunityKeyAction) if err != nil { return err } for channelID := range keyActions.ChannelKeysActions { keyAction := keyActions.ChannelKeysActions[channelID] - err := ckd.distributeKey(community, []byte(community.IDString()+channelID), &keyAction) + err := fn(community, []byte(community.IDString()+channelID), &keyAction) if err != nil { return err } @@ -40,6 +46,14 @@ func (ckd *CommunitiesKeyDistributorImpl) Distribute(community *communities.Comm return nil } +func (ckd *CommunitiesKeyDistributorImpl) generateKey(community *communities.Community, hashRatchetGroupID []byte, keyAction *communities.EncryptionKeyAction) error { + if keyAction.ActionType != communities.EncryptionKeyAdd { + return nil + } + _, err := ckd.encryptor.GenerateHashRatchetKey(hashRatchetGroupID) + return err +} + func (ckd *CommunitiesKeyDistributorImpl) distributeKey(community *communities.Community, hashRatchetGroupID []byte, keyAction *communities.EncryptionKeyAction) error { pubkeys := make([]*ecdsa.PublicKey, len(keyAction.Members)) i := 0 @@ -50,7 +64,11 @@ func (ckd *CommunitiesKeyDistributorImpl) distributeKey(community *communities.C switch keyAction.ActionType { case communities.EncryptionKeyAdd: - fallthrough + // key must be already generated + err := ckd.sendKeyExchangeMessage(community, hashRatchetGroupID, pubkeys, common.KeyExMsgReuse) + if err != nil { + return err + } case communities.EncryptionKeyRekey: err := ckd.sendKeyExchangeMessage(community, hashRatchetGroupID, pubkeys, common.KeyExMsgRekey) diff --git a/protocol/communities_messenger_token_permissions_test.go b/protocol/communities_messenger_token_permissions_test.go index 6413ad65f..1761c075d 100644 --- a/protocol/communities_messenger_token_permissions_test.go +++ b/protocol/communities_messenger_token_permissions_test.go @@ -47,6 +47,10 @@ type TestCommunitiesKeyDistributor struct { mutex sync.RWMutex } +func (tckd *TestCommunitiesKeyDistributor) Generate(community *communities.Community, keyActions *communities.EncryptionKeyActions) error { + return tckd.CommunitiesKeyDistributorImpl.Generate(community, keyActions) +} + func (tckd *TestCommunitiesKeyDistributor) Distribute(community *communities.Community, keyActions *communities.EncryptionKeyActions) error { err := tckd.CommunitiesKeyDistributorImpl.Distribute(community, keyActions) if err != nil { @@ -659,12 +663,14 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestBecomeMemberPermissions( s.Require().Len(community.Members(), 1) // bob receives community changes + // chats and members should be empty, + // this info is available only to members _, err = WaitOnMessengerResponse( s.bob, func(r *MessengerResponse) bool { - return len(r.Communities()) > 0 + return len(r.Communities()) > 0 && len(r.Communities()[0].Members()) == 0 && len(r.Communities()[0].Chats()) == 0 }, - "no community", + "no community that satisfies criteria", ) s.Require().NoError(err) @@ -984,6 +990,22 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions() err = <-waitOnChannelToBeRekeyedOnceBobIsKicked s.Require().NoError(err) + // bob receives community changes + // channel members should be empty, + // this info is available only to channel members + _, err = WaitOnMessengerResponse( + s.bob, + func(r *MessengerResponse) bool { + if len(r.Communities()) == 0 { + return false + } + channel := r.Communities()[0].Chats()[chat.CommunityChatID()] + return channel != nil && len(channel.Members) == 0 + }, + "no community that satisfies criteria", + ) + s.Require().NoError(err) + // send message to the channel msg = s.sendChatMessage(s.owner, chat.ID, "hello on closed channel") diff --git a/protocol/encryption/encryptor.go b/protocol/encryption/encryptor.go index 5496fc8c2..9a283b014 100644 --- a/protocol/encryption/encryptor.go +++ b/protocol/encryption/encryptor.go @@ -348,7 +348,7 @@ func (s *encryptor) DecryptPayload(myIdentityKey *ecdsa.PrivateKey, theirIdentit ratchet.Timestamp = uint64(header.DeprecatedKeyId) } - decryptedPayload, err := s.decryptWithHR(ratchet, header.SeqNo, payload) + decryptedPayload, err := s.DecryptWithHR(ratchet, header.SeqNo, payload) return decryptedPayload, err } @@ -650,43 +650,11 @@ func (s *encryptor) EncryptHashRatchetPayload(ratchet *HashRatchetKeyCompatibili defer s.mutex.Unlock() logger.Debug("encrypting hash ratchet message") - dmp, err := s.encryptWithHR(ratchet, payload) - response := make(map[string]*EncryptedMessageProtocol) - response[noInstallationID] = dmp - return response, err -} - -func samePublicKeys(pubKey1, pubKey2 ecdsa.PublicKey) bool { - return pubKey1.X.Cmp(pubKey2.X) == 0 && pubKey1.Y.Cmp(pubKey2.Y) == 0 -} - -func (s *encryptor) encryptWithHR(ratchet *HashRatchetKeyCompatibility, payload []byte) (*EncryptedMessageProtocol, error) { - hrCache, err := s.persistence.GetHashRatchetKeyByID(ratchet, 0) // Get latest seqNo - + encryptedPayload, newSeqNo, err := s.EncryptWithHR(ratchet, payload) if err != nil { return nil, err } - if hrCache == nil { - return nil, errors.New("no encryption key found for the community") - } - var dbHash []byte - if len(hrCache.Hash) == 0 { - dbHash = hrCache.Key - } else { - dbHash = hrCache.Hash - } - - hash := crypto.Keccak256Hash(dbHash) - encryptedPayload, err := crypto.EncryptSymmetric(hash.Bytes(), payload) - if err != nil { - return nil, err - } - newSeqNo := hrCache.SeqNo + 1 - err = s.persistence.SaveHashRatchetKeyHash(ratchet, hash.Bytes(), newSeqNo) - if err != nil { - return nil, err - } keyID, err := ratchet.GetKeyID() if err != nil { return nil, err @@ -701,16 +669,54 @@ func (s *encryptor) encryptWithHR(ratchet *HashRatchetKeyCompatibility, payload }, Payload: encryptedPayload, } - return dmp, nil + + response := make(map[string]*EncryptedMessageProtocol) + response[noInstallationID] = dmp + return response, err } -func (s *encryptor) decryptWithHR(ratchet *HashRatchetKeyCompatibility, seqNo uint32, payload []byte) ([]byte, error) { +func samePublicKeys(pubKey1, pubKey2 ecdsa.PublicKey) bool { + return pubKey1.X.Cmp(pubKey2.X) == 0 && pubKey1.Y.Cmp(pubKey2.Y) == 0 +} + +func (s *encryptor) EncryptWithHR(ratchet *HashRatchetKeyCompatibility, payload []byte) ([]byte, uint32, error) { + hrCache, err := s.persistence.GetHashRatchetCache(ratchet, 0) // Get latest seqNo + + if err != nil { + return nil, 0, err + } + if hrCache == nil { + return nil, 0, errors.New("no encryption key found for the community") + } + + var dbHash []byte + if len(hrCache.Hash) == 0 { + dbHash = hrCache.Key + } else { + dbHash = hrCache.Hash + } + + hash := crypto.Keccak256Hash(dbHash) + encryptedPayload, err := crypto.EncryptSymmetric(hash.Bytes(), payload) + if err != nil { + return nil, 0, err + } + newSeqNo := hrCache.SeqNo + 1 + err = s.persistence.SaveHashRatchetKeyHash(ratchet, hash.Bytes(), newSeqNo) + if err != nil { + return nil, 0, err + } + + return encryptedPayload, newSeqNo, nil +} + +func (s *encryptor) DecryptWithHR(ratchet *HashRatchetKeyCompatibility, seqNo uint32, payload []byte) ([]byte, error) { // Key exchange message, nothing to decrypt if seqNo == 0 { return payload, nil } - hrCache, err := s.persistence.GetHashRatchetKeyByID(ratchet, seqNo) + hrCache, err := s.persistence.GetHashRatchetCache(ratchet, seqNo) if err != nil { return nil, err } diff --git a/protocol/encryption/persistence.go b/protocol/encryption/persistence.go index 88ea47a30..2d7b48db7 100644 --- a/protocol/encryption/persistence.go +++ b/protocol/encryption/persistence.go @@ -738,10 +738,10 @@ type HRCache struct { SeqNo uint32 } -// GetHashRatchetKeyByID retrieves a hash ratchet key by group ID and seqNo. +// GetHashRatchetCache retrieves a hash ratchet key by group ID and seqNo. // If cache data with given seqNo (e.g. 0) is not found, // then the query will return the cache data with the latest seqNo -func (s *sqlitePersistence) GetHashRatchetKeyByID(ratchet *HashRatchetKeyCompatibility, seqNo uint32) (*HRCache, error) { +func (s *sqlitePersistence) GetHashRatchetCache(ratchet *HashRatchetKeyCompatibility, seqNo uint32) (*HRCache, error) { stmt, err := s.DB.Prepare(`WITH input AS ( select ? AS group_id, ? AS key_id, ? as seq_no, ? AS old_key_id ), @@ -983,3 +983,23 @@ func (s *sqlitePersistence) SaveHashRatchetKey(ratchet *HashRatchetKeyCompatibil return err } + +func (s *sqlitePersistence) GetHashRatchetKeyByID(keyID []byte) (*HashRatchetKeyCompatibility, error) { + ratchet := &HashRatchetKeyCompatibility{ + keyID: keyID, + } + + err := s.DB.QueryRow(` + SELECT group_id, key_timestamp, key + FROM hash_ratchet_encryption + WHERE key_id = ?`, keyID).Scan(&ratchet.GroupID, &ratchet.Timestamp, &ratchet.Key) + + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + + return ratchet, nil +} diff --git a/protocol/encryption/persistence_test.go b/protocol/encryption/persistence_test.go index 16dc0deb0..5edda8391 100644 --- a/protocol/encryption/persistence_test.go +++ b/protocol/encryption/persistence_test.go @@ -1,6 +1,7 @@ package encryption import ( + "reflect" "testing" "github.com/stretchr/testify/suite" @@ -336,3 +337,27 @@ func (s *SQLLitePersistenceTestSuite) TestRatchetInfoNoBundle() { } // TODO: Add test for MarkBundleExpired + +func (s *SQLLitePersistenceTestSuite) TestGetHashRatchetKeyByID() { + key := &HashRatchetKeyCompatibility{ + GroupID: []byte{1, 2, 3}, + keyID: []byte{4, 5, 6}, + Timestamp: 1, + Key: []byte{7, 8, 9}, + } + err := s.service.SaveHashRatchetKey(key) + s.Require().NoError(err) + + retrievedKey, err := s.service.GetHashRatchetKeyByID(key.keyID) + s.Require().NoError(err) + s.Require().True(reflect.DeepEqual(key.GroupID, retrievedKey.GroupID)) + s.Require().True(reflect.DeepEqual(key.keyID, retrievedKey.keyID)) + s.Require().True(reflect.DeepEqual(key.Key, retrievedKey.Key)) + s.Require().Equal(key.Timestamp, retrievedKey.Timestamp) + + cachedKey, err := s.service.GetHashRatchetCache(retrievedKey, 0) + s.Require().NoError(err) + s.Require().True(reflect.DeepEqual(key.keyID, cachedKey.KeyID)) + s.Require().True(reflect.DeepEqual(key.Key, cachedKey.Key)) + s.Require().EqualValues(0, cachedKey.SeqNo) +} diff --git a/protocol/encryption/protocol.go b/protocol/encryption/protocol.go index 6a5c72fe2..99da38eb1 100644 --- a/protocol/encryption/protocol.go +++ b/protocol/encryption/protocol.go @@ -751,3 +751,29 @@ func getProtocolVersion(bundles []*Bundle, installationID string) uint32 { return defaultMinVersion } + +func (p *Protocol) EncryptWithHashRatchet(groupID []byte, payload []byte) ([]byte, *HashRatchetKeyCompatibility, uint32, error) { + ratchet, err := p.encryptor.persistence.GetCurrentKeyForGroup(groupID) + if err != nil { + return nil, nil, 0, err + } + + encryptedPayload, newSeqNo, err := p.encryptor.EncryptWithHR(ratchet, payload) + if err != nil { + return nil, nil, 0, err + } + + return encryptedPayload, ratchet, newSeqNo, nil +} + +func (p *Protocol) DecryptWithHashRatchet(keyID []byte, seqNo uint32, payload []byte) ([]byte, error) { + ratchet, err := p.encryptor.persistence.GetHashRatchetKeyByID(keyID) + if err != nil { + return nil, err + } + if ratchet == nil { + return nil, errors.New("no ratchet key for given keyID") + } + + return p.encryptor.DecryptWithHR(ratchet, seqNo, payload) +} diff --git a/protocol/messenger.go b/protocol/messenger.go index ab0d25957..31bfd99d1 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -111,7 +111,7 @@ type Messenger struct { pushNotificationClient *pushnotificationclient.Client pushNotificationServer *pushnotificationserver.Server communitiesManager *communities.Manager - communitiesKeyDistributor CommunitiesKeyDistributor + communitiesKeyDistributor communities.KeyDistributor accountsManager account.Manager mentionsManager *MentionManager storeNodeRequestsManager *StoreNodeRequestManager @@ -467,7 +467,12 @@ func NewMessenger( managerOptions = append(managerOptions, communities.WithCommunityTokensService(c.communityTokensService)) } - communitiesManager, err := communities.NewManager(identity, installationID, database, encryptionProtocol, logger, ensVerifier, c.communityTokensService, transp, transp, c.torrentConfig, managerOptions...) + communitiesKeyDistributor := &CommunitiesKeyDistributorImpl{ + sender: sender, + encryptor: encryptionProtocol, + } + + communitiesManager, err := communities.NewManager(identity, installationID, database, encryptionProtocol, logger, ensVerifier, c.communityTokensService, transp, transp, communitiesKeyDistributor, c.torrentConfig, managerOptions...) if err != nil { return nil, err } @@ -487,24 +492,21 @@ func NewMessenger( ctx, cancel := context.WithCancel(context.Background()) messenger = &Messenger{ - config: &c, - node: node, - identity: identity, - persistence: sqlitePersistence, - transport: transp, - encryptor: encryptionProtocol, - sender: sender, - anonMetricsClient: anonMetricsClient, - anonMetricsServer: anonMetricsServer, - telemetryClient: telemetryClient, - communityTokensService: c.communityTokensService, - pushNotificationClient: pushNotificationClient, - pushNotificationServer: pushNotificationServer, - communitiesManager: communitiesManager, - communitiesKeyDistributor: &CommunitiesKeyDistributorImpl{ - sender: sender, - encryptor: encryptionProtocol, - }, + config: &c, + node: node, + identity: identity, + persistence: sqlitePersistence, + transport: transp, + encryptor: encryptionProtocol, + sender: sender, + anonMetricsClient: anonMetricsClient, + anonMetricsServer: anonMetricsServer, + telemetryClient: telemetryClient, + communityTokensService: c.communityTokensService, + pushNotificationClient: pushNotificationClient, + pushNotificationServer: pushNotificationServer, + communitiesManager: communitiesManager, + communitiesKeyDistributor: communitiesKeyDistributor, accountsManager: accountsManager, ensVerifier: ensVerifier, featureFlags: c.featureFlags, diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index d3e97e83b..f86db022f 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -292,7 +292,16 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti }() publishOrgAndDistributeEncryptionKeys := func(community *communities.Community) { - err := m.publishOrg(community) + recentlyPublishedOrg := recentlyPublishedOrgs[community.IDString()] + + // evaluate and distribute encryption keys (if any) + encryptionKeyActions := communities.EvaluateCommunityEncryptionKeyActions(recentlyPublishedOrg, community) + err := m.communitiesKeyDistributor.Distribute(community, encryptionKeyActions) + if err != nil { + m.logger.Warn("failed to distribute encryption keys", zap.Error(err)) + } + + err = m.publishOrg(community) if err != nil { m.logger.Warn("failed to publish org", zap.Error(err)) return @@ -307,8 +316,6 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti } m.logger.Debug("published public shard info") - recentlyPublishedOrg := recentlyPublishedOrgs[community.IDString()] - // signal client with published community if m.config.messengerSignalsHandler != nil { if recentlyPublishedOrg == nil || community.Clock() > recentlyPublishedOrg.Clock() { @@ -318,13 +325,6 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti } } - // evaluate and distribute encryption keys (if any) - encryptionKeyActions := communities.EvaluateCommunityEncryptionKeyActions(recentlyPublishedOrg, community) - err = m.communitiesKeyDistributor.Distribute(community, encryptionKeyActions) - if err != nil { - m.logger.Warn("failed to distribute encryption keys", zap.Error(err)) - } - recentlyPublishedOrgs[community.IDString()] = community.CreateDeepCopy() } diff --git a/protocol/protobuf/communities.pb.go b/protocol/protobuf/communities.pb.go index 87adf34ff..44e051cb0 100644 --- a/protocol/protobuf/communities.pb.go +++ b/protocol/protobuf/communities.pb.go @@ -568,9 +568,11 @@ type CommunityDescription struct { CommunityTokensMetadata []*CommunityTokenMetadata `protobuf:"bytes,16,rep,name=community_tokens_metadata,json=communityTokensMetadata,proto3" json:"community_tokens_metadata,omitempty"` ActiveMembersCount uint64 `protobuf:"varint,17,opt,name=active_members_count,json=activeMembersCount,proto3" json:"active_members_count,omitempty"` ID string `protobuf:"bytes,18,opt,name=ID,proto3" json:"ID,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + // key is hash ratchet key_id + seq_no + PrivateData map[string][]byte `protobuf:"bytes,100,rep,name=privateData,proto3" json:"privateData,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *CommunityDescription) Reset() { *m = CommunityDescription{} } @@ -718,6 +720,13 @@ func (m *CommunityDescription) GetID() string { return "" } +func (m *CommunityDescription) GetPrivateData() map[string][]byte { + if m != nil { + return m.PrivateData + } + return nil +} + type CommunityAdminSettings struct { PinMessageAllMembersEnabled bool `protobuf:"varint,1,opt,name=pin_message_all_members_enabled,json=pinMessageAllMembersEnabled,proto3" json:"pin_message_all_members_enabled,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -1671,6 +1680,7 @@ func init() { proto.RegisterMapType((map[string]*CommunityCategory)(nil), "protobuf.CommunityDescription.CategoriesEntry") proto.RegisterMapType((map[string]*CommunityChat)(nil), "protobuf.CommunityDescription.ChatsEntry") proto.RegisterMapType((map[string]*CommunityMember)(nil), "protobuf.CommunityDescription.MembersEntry") + proto.RegisterMapType((map[string][]byte)(nil), "protobuf.CommunityDescription.PrivateDataEntry") proto.RegisterMapType((map[string]*CommunityTokenPermission)(nil), "protobuf.CommunityDescription.TokenPermissionsEntry") proto.RegisterType((*CommunityAdminSettings)(nil), "protobuf.CommunityAdminSettings") proto.RegisterType((*CommunityChat)(nil), "protobuf.CommunityChat") @@ -1696,139 +1706,141 @@ func init() { } var fileDescriptor_f937943d74c1cd8b = []byte{ - // 2130 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x73, 0x23, 0x47, - 0x15, 0xcf, 0x68, 0x24, 0x59, 0x7a, 0x92, 0x6c, 0xb9, 0xb3, 0x6b, 0xcf, 0x7a, 0x37, 0x59, 0xed, - 0x40, 0x0a, 0x67, 0x29, 0xb4, 0x89, 0x81, 0x62, 0x2b, 0x21, 0x7f, 0xb4, 0xb2, 0x58, 0x94, 0xb5, - 0x46, 0xa6, 0x2d, 0x67, 0x49, 0x0a, 0x98, 0x6a, 0xcf, 0xb4, 0xed, 0xae, 0x95, 0x66, 0xc4, 0x74, - 0xcb, 0x85, 0x38, 0x70, 0x00, 0x4e, 0x1c, 0xe1, 0x03, 0x70, 0xe0, 0x0e, 0x1f, 0x81, 0x03, 0x55, - 0x1c, 0x73, 0xe7, 0x03, 0x70, 0xe7, 0x23, 0x50, 0xdd, 0x3d, 0x33, 0x9a, 0x91, 0xe4, 0xf5, 0x86, - 0x40, 0x15, 0x27, 0xcd, 0x7b, 0xfd, 0xfa, 0xf5, 0xeb, 0xf7, 0x7e, 0xfd, 0xfa, 0xd7, 0x82, 0x6d, - 0x2f, 0x9c, 0x4c, 0x66, 0x01, 0x13, 0x8c, 0xf2, 0xf6, 0x34, 0x0a, 0x45, 0x88, 0x2a, 0xea, 0xe7, - 0x6c, 0x76, 0xbe, 0xf7, 0xba, 0x77, 0x49, 0x84, 0xcb, 0x7c, 0x1a, 0x08, 0x26, 0xe6, 0x7a, 0x78, - 0xaf, 0x46, 0x83, 0xd9, 0x84, 0x27, 0x02, 0xbf, 0x24, 0x91, 0xaf, 0x05, 0xfb, 0x0a, 0x4a, 0x4f, - 0x23, 0x12, 0x08, 0xf4, 0x00, 0xea, 0x89, 0xdb, 0xb9, 0xcb, 0x7c, 0xcb, 0x68, 0x19, 0xfb, 0x75, - 0x5c, 0x4b, 0x75, 0x7d, 0x1f, 0xdd, 0x85, 0xea, 0x84, 0x4e, 0xce, 0x68, 0x24, 0xc7, 0x0b, 0x6a, - 0xbc, 0xa2, 0x15, 0x7d, 0x1f, 0xed, 0xc2, 0x46, 0xbc, 0xb2, 0x65, 0xb6, 0x8c, 0xfd, 0x2a, 0x2e, - 0x4b, 0xb1, 0xef, 0xa3, 0x5b, 0x50, 0xf2, 0xc6, 0xa1, 0xf7, 0xc2, 0x2a, 0xb6, 0x8c, 0xfd, 0x22, - 0xd6, 0x82, 0xfd, 0xf7, 0x02, 0x6c, 0x75, 0x13, 0xdf, 0x03, 0xe5, 0x04, 0x7d, 0x17, 0x4a, 0x51, - 0x38, 0xa6, 0xdc, 0x32, 0x5a, 0xe6, 0xfe, 0xe6, 0xc1, 0xfd, 0x76, 0xb2, 0xa9, 0xf6, 0x92, 0x65, - 0x1b, 0x4b, 0x33, 0xac, 0xad, 0xd1, 0x27, 0xb0, 0x1d, 0xd1, 0x2b, 0x4a, 0xc6, 0xd4, 0x77, 0x89, - 0xe7, 0x85, 0xb3, 0x40, 0x70, 0xab, 0xd0, 0x32, 0xf7, 0x6b, 0x07, 0x77, 0x16, 0x2e, 0x70, 0x6c, - 0xd2, 0xd1, 0x16, 0x4f, 0x0a, 0x96, 0x81, 0x9b, 0x51, 0x5e, 0xc9, 0xd1, 0x43, 0xd8, 0x1e, 0x13, - 0x2e, 0xdc, 0xd9, 0xd4, 0x27, 0x82, 0xba, 0x3a, 0x70, 0x53, 0x05, 0xbe, 0x25, 0x07, 0x4e, 0x95, - 0xbe, 0xab, 0xb6, 0xf0, 0x1b, 0x03, 0x4a, 0x2a, 0x10, 0xd4, 0x80, 0x2a, 0x1e, 0x1e, 0xf5, 0x5c, - 0x67, 0xe8, 0xf4, 0x9a, 0xaf, 0xa1, 0x4d, 0x00, 0x25, 0x0e, 0x9f, 0x3b, 0x3d, 0xdc, 0x34, 0x52, - 0xb9, 0x73, 0x38, 0xe8, 0x3b, 0xcd, 0x22, 0xba, 0x0d, 0xdb, 0x4a, 0x1e, 0x0d, 0x9f, 0xf5, 0x1c, - 0x77, 0xd0, 0x39, 0x19, 0xf5, 0x70, 0xb3, 0x64, 0x17, 0x2b, 0x85, 0x66, 0xc1, 0x2e, 0x56, 0xcc, - 0xa6, 0xf9, 0x50, 0x1b, 0x0c, 0x3a, 0x4e, 0xe7, 0x69, 0xcf, 0x3d, 0x3d, 0xe9, 0xe1, 0x93, 0x87, - 0xb7, 0xb5, 0x6a, 0x78, 0xd8, 0xc3, 0x9d, 0x51, 0xcf, 0xed, 0x0e, 0x9d, 0x51, 0xcf, 0x19, 0xd9, - 0xbf, 0x36, 0x61, 0x27, 0x4d, 0xcf, 0x28, 0x7c, 0x41, 0x83, 0x01, 0x15, 0xc4, 0x27, 0x82, 0xa0, - 0x73, 0x40, 0x5e, 0x18, 0x88, 0x88, 0x78, 0xc2, 0x25, 0xbe, 0x1f, 0x51, 0xce, 0xe3, 0xe4, 0xd6, - 0x0e, 0xbe, 0xb7, 0x26, 0xb9, 0xb9, 0xd9, 0xed, 0x6e, 0x3c, 0xb5, 0x93, 0xcc, 0xec, 0x05, 0x22, - 0x9a, 0xe3, 0x6d, 0x6f, 0x59, 0x8f, 0x5a, 0x50, 0xf3, 0x29, 0xf7, 0x22, 0x36, 0x15, 0x2c, 0x0c, - 0x14, 0x32, 0xaa, 0x38, 0xab, 0x92, 0x18, 0x60, 0x13, 0x72, 0x41, 0x63, 0x68, 0x68, 0x01, 0xbd, - 0x07, 0x55, 0x21, 0x97, 0x1c, 0xcd, 0xa7, 0x54, 0xa1, 0x63, 0xf3, 0xe0, 0xde, 0x75, 0x61, 0x49, - 0x1b, 0xbc, 0x30, 0x47, 0x3b, 0x50, 0xe6, 0xf3, 0xc9, 0x59, 0x38, 0xb6, 0x4a, 0x1a, 0x6d, 0x5a, - 0x42, 0x08, 0x8a, 0x01, 0x99, 0x50, 0xab, 0xac, 0xb4, 0xea, 0x1b, 0xed, 0x41, 0xc5, 0xa7, 0x1e, - 0x9b, 0x90, 0x31, 0xb7, 0x36, 0x5a, 0xc6, 0x7e, 0x03, 0xa7, 0xf2, 0xde, 0xa1, 0xcc, 0xde, 0xba, - 0x8d, 0xa2, 0x26, 0x98, 0x2f, 0xe8, 0x5c, 0x9d, 0x83, 0x22, 0x96, 0x9f, 0x72, 0x17, 0x57, 0x64, - 0x3c, 0xa3, 0xf1, 0x0e, 0xb5, 0xf0, 0x5e, 0xe1, 0xb1, 0x61, 0xff, 0xd3, 0x80, 0x5b, 0x69, 0xbc, - 0xc7, 0x34, 0x9a, 0x30, 0xce, 0x59, 0x18, 0x70, 0x74, 0x07, 0x2a, 0x34, 0xe0, 0x6e, 0x18, 0x8c, - 0xb5, 0xa7, 0x0a, 0xde, 0xa0, 0x01, 0x1f, 0x06, 0xe3, 0x39, 0xb2, 0x60, 0x63, 0x1a, 0xb1, 0x2b, - 0x22, 0xb4, 0xbf, 0x0a, 0x4e, 0x44, 0xf4, 0x01, 0x94, 0x89, 0xe7, 0x51, 0xce, 0x55, 0xba, 0x36, - 0x0f, 0xde, 0x5a, 0x93, 0x94, 0xcc, 0x22, 0xed, 0x8e, 0x32, 0xc6, 0xf1, 0x24, 0xfb, 0x33, 0x28, - 0x6b, 0x0d, 0x42, 0xb0, 0x79, 0xea, 0x3c, 0x73, 0x86, 0xcf, 0x1d, 0xb7, 0xd3, 0xed, 0xf6, 0x4e, - 0x4e, 0x9a, 0xaf, 0xa1, 0x2d, 0xa8, 0x75, 0x4e, 0x47, 0x43, 0xa5, 0x38, 0x1e, 0x35, 0x0d, 0xb4, - 0x0b, 0x5b, 0x7d, 0xe7, 0xd3, 0xfe, 0xa8, 0x33, 0xea, 0x0f, 0x1d, 0x77, 0xe8, 0x1c, 0x7d, 0xd6, - 0x2c, 0xec, 0x15, 0x2a, 0x06, 0xda, 0x86, 0xc6, 0xa0, 0xe3, 0x9c, 0x76, 0x8e, 0x12, 0x5b, 0xd3, - 0xfe, 0xad, 0x09, 0x0d, 0x55, 0x8e, 0x6e, 0xc4, 0x04, 0x8d, 0x18, 0x41, 0x3f, 0x7d, 0x09, 0xc6, - 0xda, 0x8b, 0xb8, 0x73, 0x93, 0xbe, 0x04, 0xb4, 0xde, 0x81, 0xa2, 0x90, 0xe8, 0x28, 0xbc, 0x02, - 0x3a, 0x94, 0x65, 0x06, 0x18, 0xe6, 0x5a, 0x60, 0x14, 0x33, 0xc0, 0xd8, 0x81, 0x32, 0x99, 0xc8, - 0x83, 0x9f, 0x80, 0x48, 0x4b, 0xb2, 0xd1, 0x29, 0xa4, 0xb9, 0xcc, 0xe7, 0x56, 0xb9, 0x65, 0xee, - 0x17, 0x71, 0x45, 0x29, 0xfa, 0x3e, 0x47, 0xf7, 0xa1, 0x26, 0x4b, 0x3a, 0x25, 0x42, 0xd0, 0x28, - 0x50, 0x80, 0xaa, 0x62, 0xa0, 0x01, 0x3f, 0xd6, 0x9a, 0x1c, 0xdc, 0x2a, 0x0a, 0x3d, 0xff, 0x6d, - 0xb8, 0xfd, 0xc1, 0x04, 0x2b, 0x9f, 0x80, 0x05, 0x1c, 0xd0, 0x26, 0x14, 0xe2, 0xf6, 0x5d, 0xc5, - 0x05, 0xe6, 0xa3, 0xf7, 0x73, 0x29, 0xfc, 0xc6, 0x75, 0x29, 0x5c, 0x78, 0x68, 0x67, 0xb2, 0xf9, - 0x21, 0x6c, 0xea, 0x4c, 0x78, 0x71, 0xed, 0x2c, 0x53, 0x95, 0x76, 0xf7, 0x9a, 0xd2, 0xe2, 0x86, - 0xc8, 0xc1, 0xe3, 0x0e, 0x54, 0xe2, 0x5b, 0x81, 0x5b, 0xc5, 0x96, 0xb9, 0x5f, 0xc5, 0x1b, 0xfa, - 0x5a, 0xe0, 0xe8, 0x0d, 0x00, 0xc6, 0xdd, 0xe4, 0x08, 0x94, 0xd4, 0x11, 0xa8, 0x32, 0x7e, 0xac, - 0x15, 0xf6, 0x5f, 0x0c, 0x28, 0xaa, 0x93, 0x7e, 0x0f, 0xac, 0x04, 0xc4, 0xba, 0x61, 0x1e, 0xf7, - 0xf0, 0xa0, 0x7f, 0x72, 0xd2, 0x1f, 0x3a, 0xcd, 0xd7, 0x50, 0x13, 0xea, 0x4f, 0x7a, 0xdd, 0xe1, - 0x20, 0xe9, 0xae, 0x0a, 0xb6, 0xb1, 0x66, 0xd0, 0x1b, 0x3c, 0xe9, 0xe1, 0x66, 0x01, 0xdd, 0x82, - 0x66, 0xb7, 0xe3, 0xb8, 0x9f, 0xf6, 0x7b, 0xcf, 0xdd, 0xee, 0x0f, 0x3b, 0x8e, 0xd3, 0x3b, 0x6a, - 0x9a, 0xe8, 0x0d, 0xb8, 0x93, 0x6a, 0x3b, 0xce, 0xa1, 0x7b, 0x3c, 0x3c, 0x19, 0xa5, 0xc3, 0x45, - 0xb4, 0x0b, 0xaf, 0xc7, 0x7e, 0xf2, 0x7d, 0x1a, 0xed, 0x00, 0xca, 0x0d, 0xe8, 0x36, 0x5f, 0xb6, - 0x7f, 0x07, 0x99, 0x26, 0x70, 0x98, 0xef, 0x7e, 0xfa, 0x22, 0x31, 0x32, 0x37, 0x20, 0xea, 0xc1, - 0x86, 0xbe, 0x3c, 0x93, 0xcb, 0xea, 0x9b, 0x6b, 0x4a, 0x93, 0x71, 0xd3, 0xd6, 0x77, 0x5f, 0x7c, - 0x56, 0x92, 0xb9, 0xe8, 0x63, 0xa8, 0x4d, 0x17, 0xbd, 0x40, 0x81, 0xbe, 0x76, 0xf0, 0xe6, 0xcb, - 0x3b, 0x06, 0xce, 0x4e, 0x41, 0x07, 0x50, 0x49, 0xe8, 0x82, 0x2a, 0x43, 0xed, 0x60, 0x27, 0x33, - 0x5d, 0x55, 0x4b, 0x8f, 0xe2, 0xd4, 0x0e, 0x7d, 0x04, 0x25, 0x59, 0x47, 0x7d, 0x3a, 0x6a, 0x07, - 0x6f, 0xdf, 0x10, 0xba, 0xf4, 0x12, 0x07, 0xae, 0xe7, 0x49, 0x60, 0x9c, 0x91, 0xc0, 0x1d, 0x33, - 0x2e, 0xac, 0x0d, 0x0d, 0x8c, 0x33, 0x12, 0x1c, 0x31, 0x2e, 0x90, 0x03, 0xe0, 0x11, 0x41, 0x2f, - 0xc2, 0x88, 0x51, 0x79, 0x82, 0x96, 0x5a, 0xc9, 0xfa, 0x05, 0xd2, 0x09, 0x7a, 0x95, 0x8c, 0x07, - 0xf4, 0x18, 0x2c, 0x12, 0x79, 0x97, 0xec, 0x8a, 0xba, 0x13, 0x72, 0x11, 0x50, 0x31, 0x66, 0xc1, - 0x8b, 0xf8, 0x6a, 0xaf, 0xaa, 0x8a, 0xec, 0xc4, 0xe3, 0x83, 0x74, 0x58, 0xdd, 0xf0, 0xe8, 0x29, - 0x6c, 0x12, 0x7f, 0xc2, 0x02, 0x97, 0x53, 0x21, 0x58, 0x70, 0xc1, 0x2d, 0x50, 0xf9, 0x69, 0xad, - 0x89, 0xa6, 0x23, 0x0d, 0x4f, 0x62, 0x3b, 0xdc, 0x20, 0x59, 0x11, 0x7d, 0x0d, 0x1a, 0x2c, 0x10, - 0x51, 0xe8, 0x4e, 0x28, 0xe7, 0xf2, 0x1e, 0xac, 0xa9, 0xe3, 0x59, 0x57, 0xca, 0x81, 0xd6, 0x49, - 0xa3, 0x70, 0x96, 0x35, 0xaa, 0x6b, 0x23, 0xa5, 0x4c, 0x8c, 0x5a, 0x50, 0xa5, 0x81, 0x17, 0xcd, - 0xa7, 0x82, 0xfa, 0x56, 0x43, 0x1e, 0x1a, 0xc5, 0x64, 0x16, 0x4a, 0xd9, 0xe8, 0x04, 0xb9, 0xe0, - 0xd6, 0xa6, 0xca, 0xaa, 0xfa, 0x46, 0x04, 0xb6, 0xf5, 0x31, 0xce, 0x42, 0x65, 0x4b, 0x65, 0xf6, - 0x3b, 0x37, 0x64, 0x76, 0xa9, 0x39, 0xc4, 0xf9, 0x6d, 0x8a, 0x25, 0x35, 0xfa, 0x09, 0xdc, 0x59, - 0xf0, 0x47, 0x35, 0xca, 0xdd, 0x49, 0xcc, 0x25, 0xac, 0xa6, 0x5a, 0xaa, 0x75, 0x13, 0xe7, 0xc0, - 0xbb, 0x5e, 0x4e, 0xcf, 0x53, 0x2a, 0xf3, 0x0e, 0xdc, 0x22, 0x9e, 0x50, 0x25, 0xd4, 0xb8, 0x77, - 0x15, 0x61, 0xb3, 0xb6, 0x55, 0xfd, 0x90, 0x1e, 0x8b, 0x0f, 0x48, 0x57, 0xf5, 0xf0, 0x4d, 0x28, - 0xf4, 0x0f, 0x2d, 0xa4, 0xdb, 0x60, 0xff, 0x70, 0xef, 0x14, 0xea, 0xd9, 0x03, 0x94, 0xed, 0xb7, - 0x55, 0xdd, 0x6f, 0x1f, 0x65, 0xfb, 0x6d, 0x8e, 0x3b, 0x2e, 0xd1, 0xcf, 0x4c, 0x2b, 0xde, 0xfb, - 0x11, 0xc0, 0x02, 0xdc, 0x6b, 0x9c, 0x7e, 0x2b, 0xef, 0x74, 0x77, 0x8d, 0x53, 0x39, 0x3f, 0xeb, - 0xf2, 0x73, 0xd8, 0x5a, 0x82, 0xf3, 0x1a, 0xbf, 0xef, 0xe6, 0xfd, 0xde, 0x5d, 0xe7, 0x57, 0x3b, - 0x99, 0x67, 0x7d, 0x5f, 0xc0, 0xed, 0xb5, 0x05, 0x5d, 0xb3, 0xc2, 0xe3, 0xfc, 0x0a, 0xf6, 0xcd, - 0x17, 0x47, 0xf6, 0x8a, 0xfa, 0x59, 0x86, 0x95, 0xe6, 0x8e, 0x06, 0x3a, 0x84, 0xfb, 0x53, 0x16, - 0x24, 0x20, 0x77, 0xc9, 0x78, 0x9c, 0xd6, 0x94, 0x06, 0xe4, 0x6c, 0x4c, 0xfd, 0x98, 0x29, 0xdd, - 0x9d, 0xb2, 0x20, 0x86, 0x7d, 0x67, 0x3c, 0x4e, 0x8b, 0xa7, 0x4c, 0xec, 0x7f, 0x14, 0xa0, 0x91, - 0xcb, 0x20, 0xfa, 0x70, 0xd1, 0x4f, 0x35, 0xfd, 0xf8, 0xfa, 0x35, 0xb9, 0x7e, 0xb5, 0x46, 0x5a, - 0xf8, 0x6a, 0x8d, 0xd4, 0x7c, 0xc5, 0x46, 0x7a, 0x1f, 0x6a, 0x71, 0xab, 0x52, 0xaf, 0x2e, 0xcd, - 0x4e, 0x92, 0xee, 0x25, 0x1f, 0x5d, 0x7b, 0x50, 0x99, 0x86, 0x9c, 0x29, 0x66, 0x2d, 0xbb, 0x73, - 0x09, 0xa7, 0xf2, 0xff, 0x08, 0xd3, 0xb6, 0x0f, 0xdb, 0x2b, 0x20, 0x5a, 0x0e, 0xd4, 0x58, 0x09, - 0x34, 0x21, 0x58, 0x85, 0x3c, 0xf3, 0x4e, 0x83, 0x37, 0xf3, 0xc1, 0xdb, 0xbf, 0x37, 0x60, 0x6b, - 0xe9, 0x51, 0x26, 0x39, 0x71, 0x4c, 0x22, 0xe3, 0x05, 0x12, 0x11, 0xdd, 0x83, 0x2a, 0x67, 0x17, - 0x01, 0x11, 0xb3, 0x88, 0xc6, 0x6f, 0xcf, 0x85, 0x42, 0x12, 0x36, 0xef, 0x92, 0x30, 0x4d, 0xd8, - 0x4c, 0x4d, 0xd8, 0x94, 0x42, 0x12, 0x8d, 0x87, 0xd0, 0x64, 0xbc, 0xc3, 0x22, 0x3f, 0x0a, 0xa7, - 0x31, 0xe9, 0x52, 0x79, 0xae, 0xe0, 0x15, 0xbd, 0xfd, 0x2f, 0x23, 0x83, 0x5b, 0x4c, 0x7f, 0x3e, - 0xa3, 0x5c, 0x8c, 0xc2, 0x4f, 0x42, 0x76, 0xdd, 0x2d, 0x1e, 0x13, 0xfc, 0xcc, 0xce, 0x25, 0xc1, - 0x77, 0xe4, 0xe6, 0xaf, 0x7d, 0x11, 0x2f, 0x3f, 0xb5, 0x8b, 0xab, 0x4f, 0xed, 0x07, 0x50, 0xf7, - 0x19, 0x9f, 0x8e, 0xc9, 0x5c, 0xbb, 0x2e, 0xc5, 0x6f, 0x2a, 0xad, 0x53, 0xee, 0x7f, 0xb0, 0xee, - 0xd9, 0x5b, 0xbe, 0xe1, 0xd9, 0xbb, 0xfa, 0xe4, 0xb5, 0xff, 0x68, 0xc0, 0xbd, 0x74, 0xcb, 0x3d, - 0x9f, 0x89, 0x93, 0x4b, 0x12, 0x51, 0x7f, 0xc1, 0xc1, 0xd7, 0x6f, 0x7c, 0x79, 0x13, 0x85, 0xd5, - 0x4d, 0xac, 0x8d, 0xd0, 0xfc, 0xf2, 0x11, 0xfe, 0x39, 0x1b, 0x61, 0x97, 0x04, 0x1e, 0x1d, 0xff, - 0x5f, 0x97, 0xc6, 0xfe, 0xa2, 0x00, 0x6f, 0xae, 0x47, 0x11, 0xa6, 0x7c, 0x1a, 0x06, 0x9c, 0x5e, - 0x13, 0xf2, 0xf7, 0xa1, 0x9a, 0x2e, 0xf5, 0x92, 0x0e, 0x94, 0xb9, 0x9f, 0xf1, 0x62, 0x82, 0x3c, - 0x6d, 0xf2, 0x09, 0xa8, 0xa8, 0x81, 0xa9, 0x00, 0x9e, 0xca, 0x72, 0xbd, 0x8b, 0x88, 0x04, 0x22, - 0xde, 0x91, 0x16, 0x56, 0xb6, 0x5b, 0x5a, 0xdd, 0xee, 0x1b, 0x00, 0x9a, 0x35, 0xb9, 0xb3, 0x88, - 0xc5, 0xcf, 0xea, 0xaa, 0xd6, 0x9c, 0x46, 0x0c, 0x7d, 0x00, 0x77, 0x65, 0x7c, 0xd4, 0x13, 0xd4, - 0x77, 0x45, 0x38, 0x65, 0x5e, 0x42, 0xe9, 0x5d, 0xd9, 0x8a, 0x36, 0x94, 0x43, 0x2b, 0x35, 0x19, - 0x49, 0x8b, 0x98, 0xe2, 0x3f, 0xa3, 0x73, 0xf4, 0x16, 0x94, 0xd4, 0xbf, 0x51, 0xea, 0xa1, 0x54, - 0x3b, 0xd8, 0x5a, 0x6c, 0x56, 0xa2, 0xd0, 0xc7, 0x7a, 0xd4, 0xc6, 0xb0, 0xbb, 0x9a, 0xcf, 0x23, - 0x4a, 0xae, 0xe8, 0x7f, 0x8c, 0x4e, 0xfb, 0xc7, 0xf0, 0x20, 0xd3, 0x03, 0xf5, 0x35, 0xb3, 0x4c, - 0x03, 0xaf, 0xf1, 0x9e, 0xcf, 0x49, 0x61, 0x29, 0x27, 0xf6, 0x5f, 0x0d, 0xa8, 0x3d, 0x27, 0x2f, - 0x66, 0x09, 0x67, 0x6b, 0x82, 0xc9, 0xd9, 0x45, 0xfc, 0x8f, 0x9a, 0xfc, 0x94, 0xdd, 0x4c, 0xb0, - 0x09, 0xe5, 0x82, 0x4c, 0xa6, 0x6a, 0x7e, 0x11, 0x2f, 0x14, 0x72, 0x51, 0x95, 0x49, 0x55, 0xc4, - 0x3a, 0xd6, 0x82, 0xfa, 0xbf, 0x80, 0xcc, 0xc7, 0x21, 0x49, 0x50, 0x99, 0x88, 0x7a, 0xc4, 0xf7, - 0x59, 0x70, 0x11, 0x17, 0x30, 0x11, 0x65, 0x4f, 0xbe, 0x24, 0xfc, 0x52, 0x95, 0xad, 0x8e, 0xd5, - 0x37, 0xb2, 0xa1, 0x2e, 0x2e, 0x59, 0xe4, 0x1f, 0x93, 0x48, 0xe6, 0x21, 0x7e, 0xc0, 0xe6, 0x74, - 0xf6, 0xaf, 0x60, 0x2f, 0xb3, 0x81, 0x24, 0x2d, 0x09, 0x19, 0xb3, 0x60, 0xe3, 0x8a, 0x46, 0xf2, - 0xce, 0x53, 0x7b, 0x6a, 0xe0, 0x44, 0x94, 0xeb, 0x9d, 0x47, 0xe1, 0x24, 0xde, 0x92, 0xfa, 0x96, - 0x44, 0x4c, 0x84, 0xf1, 0x7f, 0x68, 0x05, 0x11, 0xca, 0xf5, 0xe5, 0x3b, 0x9f, 0x06, 0x42, 0x81, - 0x41, 0x3d, 0x0b, 0xeb, 0x38, 0xa7, 0xb3, 0xff, 0x64, 0x00, 0x5a, 0x0d, 0xe0, 0x25, 0x0b, 0x7f, - 0x0c, 0x95, 0x94, 0x6c, 0xea, 0x73, 0x93, 0xb9, 0xfd, 0xaf, 0xdf, 0x0a, 0x4e, 0x67, 0xa1, 0x77, - 0xa5, 0x07, 0x65, 0x93, 0xf4, 0xa8, 0xdb, 0x6b, 0x3d, 0xe0, 0xd4, 0xcc, 0xfe, 0x9b, 0x01, 0xf7, - 0x57, 0x7d, 0xf7, 0x03, 0x9f, 0xfe, 0xe2, 0x15, 0x72, 0xf5, 0xd5, 0x43, 0xde, 0x81, 0x72, 0x78, - 0x7e, 0xce, 0xa9, 0x88, 0xb3, 0x1b, 0x4b, 0xb2, 0x0a, 0x9c, 0xfd, 0x92, 0xc6, 0x7f, 0xb8, 0xaa, - 0xef, 0x65, 0x8c, 0x14, 0x53, 0x8c, 0xd8, 0x5f, 0x18, 0xb0, 0x7b, 0xcd, 0x2e, 0xd0, 0x33, 0xa8, - 0xc4, 0x4f, 0xa3, 0x84, 0x54, 0x3d, 0x7a, 0x59, 0x8c, 0x6a, 0x52, 0x3b, 0x16, 0x62, 0x7e, 0x95, - 0x3a, 0xd8, 0x3b, 0x87, 0x46, 0x6e, 0x68, 0x0d, 0x5d, 0xf9, 0x28, 0x4f, 0x57, 0xde, 0xbe, 0x71, - 0xb1, 0x34, 0x2b, 0x0b, 0xfa, 0xf2, 0xa4, 0xf1, 0x79, 0xad, 0xfd, 0xe8, 0xfd, 0x64, 0xe6, 0x59, - 0x59, 0x7d, 0x7d, 0xfb, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xdc, 0xcb, 0x13, 0x4d, 0x36, 0x17, - 0x00, 0x00, + // 2166 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x51, 0x73, 0x23, 0x47, + 0x11, 0xbe, 0xd5, 0x4a, 0xb6, 0xd4, 0x92, 0x6c, 0x79, 0x72, 0x67, 0xef, 0xf9, 0xee, 0x72, 0xba, + 0x85, 0x14, 0xce, 0x51, 0xe8, 0x12, 0x03, 0xc5, 0x55, 0x42, 0x2e, 0xd1, 0xc9, 0xe2, 0x50, 0xce, + 0x5a, 0x39, 0x63, 0x39, 0x47, 0x52, 0xc0, 0xd6, 0x78, 0x77, 0x6c, 0x4f, 0x9d, 0xb4, 0x2b, 0x76, + 0x46, 0x2e, 0xc4, 0x03, 0x0f, 0xc0, 0x2f, 0x80, 0x67, 0x8a, 0x07, 0xde, 0xe1, 0x27, 0xf0, 0x40, + 0x15, 0x8f, 0x79, 0xe7, 0x07, 0xf0, 0xce, 0x4f, 0xa0, 0x66, 0x66, 0x77, 0xb5, 0x2b, 0xc9, 0xe7, + 0x0b, 0x81, 0xaa, 0x3c, 0x69, 0xbb, 0xa7, 0xa7, 0xa7, 0xbb, 0xe7, 0xeb, 0x9e, 0x6e, 0xc1, 0x96, + 0x17, 0x8e, 0xc7, 0xd3, 0x80, 0x09, 0x46, 0x79, 0x6b, 0x12, 0x85, 0x22, 0x44, 0x65, 0xf5, 0x73, + 0x3a, 0x3d, 0xdb, 0x7d, 0xc3, 0xbb, 0x20, 0xc2, 0x65, 0x3e, 0x0d, 0x04, 0x13, 0x33, 0xbd, 0xbc, + 0x5b, 0xa5, 0xc1, 0x74, 0xcc, 0x13, 0x82, 0x5f, 0x90, 0xc8, 0xd7, 0x84, 0x7d, 0x09, 0xa5, 0x67, + 0x11, 0x09, 0x04, 0x7a, 0x00, 0xb5, 0x44, 0xed, 0xcc, 0x65, 0xbe, 0x65, 0x34, 0x8d, 0xbd, 0x1a, + 0xae, 0xa6, 0xbc, 0x9e, 0x8f, 0xee, 0x40, 0x65, 0x4c, 0xc7, 0xa7, 0x34, 0x92, 0xeb, 0x05, 0xb5, + 0x5e, 0xd6, 0x8c, 0x9e, 0x8f, 0x76, 0x60, 0x3d, 0x3e, 0xd9, 0x32, 0x9b, 0xc6, 0x5e, 0x05, 0xaf, + 0x49, 0xb2, 0xe7, 0xa3, 0x9b, 0x50, 0xf2, 0x46, 0xa1, 0xf7, 0xd2, 0x2a, 0x36, 0x8d, 0xbd, 0x22, + 0xd6, 0x84, 0xfd, 0x8f, 0x02, 0x6c, 0x76, 0x12, 0xdd, 0x7d, 0xa5, 0x04, 0x7d, 0x1f, 0x4a, 0x51, + 0x38, 0xa2, 0xdc, 0x32, 0x9a, 0xe6, 0xde, 0xc6, 0xfe, 0xfd, 0x56, 0xe2, 0x54, 0x6b, 0x41, 0xb2, + 0x85, 0xa5, 0x18, 0xd6, 0xd2, 0xe8, 0x63, 0xd8, 0x8a, 0xe8, 0x25, 0x25, 0x23, 0xea, 0xbb, 0xc4, + 0xf3, 0xc2, 0x69, 0x20, 0xb8, 0x55, 0x68, 0x9a, 0x7b, 0xd5, 0xfd, 0xdb, 0x73, 0x15, 0x38, 0x16, + 0x69, 0x6b, 0x89, 0xa7, 0x05, 0xcb, 0xc0, 0x8d, 0x28, 0xcf, 0xe4, 0xe8, 0x21, 0x6c, 0x8d, 0x08, + 0x17, 0xee, 0x74, 0xe2, 0x13, 0x41, 0x5d, 0x6d, 0xb8, 0xa9, 0x0c, 0xdf, 0x94, 0x0b, 0x27, 0x8a, + 0xdf, 0x51, 0x2e, 0xfc, 0xd6, 0x80, 0x92, 0x32, 0x04, 0xd5, 0xa1, 0x82, 0x07, 0x87, 0x5d, 0xd7, + 0x19, 0x38, 0xdd, 0xc6, 0x0d, 0xb4, 0x01, 0xa0, 0xc8, 0xc1, 0x0b, 0xa7, 0x8b, 0x1b, 0x46, 0x4a, + 0xb7, 0x0f, 0xfa, 0x3d, 0xa7, 0x51, 0x44, 0xb7, 0x60, 0x4b, 0xd1, 0xc3, 0xc1, 0xf3, 0xae, 0xe3, + 0xf6, 0xdb, 0xc7, 0xc3, 0x2e, 0x6e, 0x94, 0xec, 0x62, 0xb9, 0xd0, 0x28, 0xd8, 0xc5, 0xb2, 0xd9, + 0x30, 0x1f, 0x6a, 0x81, 0x7e, 0xdb, 0x69, 0x3f, 0xeb, 0xba, 0x27, 0xc7, 0x5d, 0x7c, 0xfc, 0xf0, + 0x96, 0x66, 0x0d, 0x0e, 0xba, 0xb8, 0x3d, 0xec, 0xba, 0x9d, 0x81, 0x33, 0xec, 0x3a, 0x43, 0xfb, + 0x37, 0x26, 0x6c, 0xa7, 0xe1, 0x19, 0x86, 0x2f, 0x69, 0xd0, 0xa7, 0x82, 0xf8, 0x44, 0x10, 0x74, + 0x06, 0xc8, 0x0b, 0x03, 0x11, 0x11, 0x4f, 0xb8, 0xc4, 0xf7, 0x23, 0xca, 0x79, 0x1c, 0xdc, 0xea, + 0xfe, 0x0f, 0x56, 0x04, 0x37, 0xb7, 0xbb, 0xd5, 0x89, 0xb7, 0xb6, 0x93, 0x9d, 0xdd, 0x40, 0x44, + 0x33, 0xbc, 0xe5, 0x2d, 0xf2, 0x51, 0x13, 0xaa, 0x3e, 0xe5, 0x5e, 0xc4, 0x26, 0x82, 0x85, 0x81, + 0x42, 0x46, 0x05, 0x67, 0x59, 0x12, 0x03, 0x6c, 0x4c, 0xce, 0x69, 0x0c, 0x0d, 0x4d, 0xa0, 0xf7, + 0xa0, 0x22, 0xe4, 0x91, 0xc3, 0xd9, 0x84, 0x2a, 0x74, 0x6c, 0xec, 0xdf, 0xbd, 0xca, 0x2c, 0x29, + 0x83, 0xe7, 0xe2, 0x68, 0x1b, 0xd6, 0xf8, 0x6c, 0x7c, 0x1a, 0x8e, 0xac, 0x92, 0x46, 0x9b, 0xa6, + 0x10, 0x82, 0x62, 0x40, 0xc6, 0xd4, 0x5a, 0x53, 0x5c, 0xf5, 0x8d, 0x76, 0xa1, 0xec, 0x53, 0x8f, + 0x8d, 0xc9, 0x88, 0x5b, 0xeb, 0x4d, 0x63, 0xaf, 0x8e, 0x53, 0x7a, 0xf7, 0x40, 0x46, 0x6f, 0x95, + 0xa3, 0xa8, 0x01, 0xe6, 0x4b, 0x3a, 0x53, 0x79, 0x50, 0xc4, 0xf2, 0x53, 0x7a, 0x71, 0x49, 0x46, + 0x53, 0x1a, 0x7b, 0xa8, 0x89, 0xf7, 0x0a, 0x8f, 0x0d, 0xfb, 0x5f, 0x06, 0xdc, 0x4c, 0xed, 0x3d, + 0xa2, 0xd1, 0x98, 0x71, 0xce, 0xc2, 0x80, 0xa3, 0xdb, 0x50, 0xa6, 0x01, 0x77, 0xc3, 0x60, 0xa4, + 0x35, 0x95, 0xf1, 0x3a, 0x0d, 0xf8, 0x20, 0x18, 0xcd, 0x90, 0x05, 0xeb, 0x93, 0x88, 0x5d, 0x12, + 0xa1, 0xf5, 0x95, 0x71, 0x42, 0xa2, 0x0f, 0x60, 0x8d, 0x78, 0x1e, 0xe5, 0x5c, 0x85, 0x6b, 0x63, + 0xff, 0xad, 0x15, 0x41, 0xc9, 0x1c, 0xd2, 0x6a, 0x2b, 0x61, 0x1c, 0x6f, 0xb2, 0x3f, 0x83, 0x35, + 0xcd, 0x41, 0x08, 0x36, 0x4e, 0x9c, 0xe7, 0xce, 0xe0, 0x85, 0xe3, 0xb6, 0x3b, 0x9d, 0xee, 0xf1, + 0x71, 0xe3, 0x06, 0xda, 0x84, 0x6a, 0xfb, 0x64, 0x38, 0x50, 0x8c, 0xa3, 0x61, 0xc3, 0x40, 0x3b, + 0xb0, 0xd9, 0x73, 0x3e, 0xed, 0x0d, 0xdb, 0xc3, 0xde, 0xc0, 0x71, 0x07, 0xce, 0xe1, 0x67, 0x8d, + 0xc2, 0x6e, 0xa1, 0x6c, 0xa0, 0x2d, 0xa8, 0xf7, 0xdb, 0xce, 0x49, 0xfb, 0x30, 0x91, 0x35, 0xed, + 0xdf, 0x99, 0x50, 0x57, 0xd7, 0xd1, 0x89, 0x98, 0xa0, 0x11, 0x23, 0xe8, 0x67, 0xaf, 0xc0, 0x58, + 0x6b, 0x6e, 0x77, 0x6e, 0xd3, 0x97, 0x80, 0xd6, 0x3b, 0x50, 0x14, 0x12, 0x1d, 0x85, 0xd7, 0x40, + 0x87, 0x92, 0xcc, 0x00, 0xc3, 0x5c, 0x09, 0x8c, 0x62, 0x06, 0x18, 0xdb, 0xb0, 0x46, 0xc6, 0x32, + 0xf1, 0x13, 0x10, 0x69, 0x4a, 0x16, 0x3a, 0x85, 0x34, 0x97, 0xf9, 0xdc, 0x5a, 0x6b, 0x9a, 0x7b, + 0x45, 0x5c, 0x56, 0x8c, 0x9e, 0xcf, 0xd1, 0x7d, 0xa8, 0xca, 0x2b, 0x9d, 0x10, 0x21, 0x68, 0x14, + 0x28, 0x40, 0x55, 0x30, 0xd0, 0x80, 0x1f, 0x69, 0x4e, 0x0e, 0x6e, 0x65, 0x85, 0x9e, 0xff, 0x35, + 0xdc, 0xfe, 0x60, 0x82, 0x95, 0x0f, 0xc0, 0x1c, 0x0e, 0x68, 0x03, 0x0a, 0x71, 0xf9, 0xae, 0xe0, + 0x02, 0xf3, 0xd1, 0xfb, 0xb9, 0x10, 0x7e, 0xeb, 0xaa, 0x10, 0xce, 0x35, 0xb4, 0x32, 0xd1, 0x7c, + 0x02, 0x1b, 0x3a, 0x12, 0x5e, 0x7c, 0x77, 0x96, 0xa9, 0xae, 0x76, 0xe7, 0x8a, 0xab, 0xc5, 0x75, + 0x91, 0x83, 0xc7, 0x6d, 0x28, 0xc7, 0xaf, 0x02, 0xb7, 0x8a, 0x4d, 0x73, 0xaf, 0x82, 0xd7, 0xf5, + 0xb3, 0xc0, 0xd1, 0x3d, 0x00, 0xc6, 0xdd, 0x24, 0x05, 0x4a, 0x2a, 0x05, 0x2a, 0x8c, 0x1f, 0x69, + 0x86, 0xfd, 0x57, 0x03, 0x8a, 0x2a, 0xd3, 0xef, 0x82, 0x95, 0x80, 0x58, 0x17, 0xcc, 0xa3, 0x2e, + 0xee, 0xf7, 0x8e, 0x8f, 0x7b, 0x03, 0xa7, 0x71, 0x03, 0x35, 0xa0, 0xf6, 0xb4, 0xdb, 0x19, 0xf4, + 0x93, 0xea, 0xaa, 0x60, 0x1b, 0x73, 0xfa, 0xdd, 0xfe, 0xd3, 0x2e, 0x6e, 0x14, 0xd0, 0x4d, 0x68, + 0x74, 0xda, 0x8e, 0xfb, 0x69, 0xaf, 0xfb, 0xc2, 0xed, 0xfc, 0xb8, 0xed, 0x38, 0xdd, 0xc3, 0x86, + 0x89, 0xee, 0xc1, 0xed, 0x94, 0xdb, 0x76, 0x0e, 0xdc, 0xa3, 0xc1, 0xf1, 0x30, 0x5d, 0x2e, 0xa2, + 0x1d, 0x78, 0x23, 0xd6, 0x93, 0xaf, 0xd3, 0x68, 0x1b, 0x50, 0x6e, 0x41, 0x97, 0xf9, 0x35, 0xfb, + 0x8f, 0xd5, 0x4c, 0x11, 0x38, 0xc8, 0x57, 0x3f, 0xfd, 0x90, 0x18, 0x99, 0x17, 0x10, 0x75, 0x61, + 0x5d, 0x3f, 0x9e, 0xc9, 0x63, 0xf5, 0xed, 0x15, 0x57, 0x93, 0x51, 0xd3, 0xd2, 0x6f, 0x5f, 0x9c, + 0x2b, 0xc9, 0x5e, 0xf4, 0x11, 0x54, 0x27, 0xf3, 0x5a, 0xa0, 0x40, 0x5f, 0xdd, 0x7f, 0xf3, 0xd5, + 0x15, 0x03, 0x67, 0xb7, 0xa0, 0x7d, 0x28, 0x27, 0xed, 0x82, 0xba, 0x86, 0xea, 0xfe, 0x76, 0x66, + 0xbb, 0xba, 0x2d, 0xbd, 0x8a, 0x53, 0x39, 0xf4, 0x21, 0x94, 0xe4, 0x3d, 0xea, 0xec, 0xa8, 0xee, + 0xbf, 0x7d, 0x8d, 0xe9, 0x52, 0x4b, 0x6c, 0xb8, 0xde, 0x27, 0x81, 0x71, 0x4a, 0x02, 0x77, 0xc4, + 0xb8, 0xb0, 0xd6, 0x35, 0x30, 0x4e, 0x49, 0x70, 0xc8, 0xb8, 0x40, 0x0e, 0x80, 0x47, 0x04, 0x3d, + 0x0f, 0x23, 0x46, 0x65, 0x06, 0x2d, 0x94, 0x92, 0xd5, 0x07, 0xa4, 0x1b, 0xf4, 0x29, 0x19, 0x0d, + 0xe8, 0x31, 0x58, 0x24, 0xf2, 0x2e, 0xd8, 0x25, 0x75, 0xc7, 0xe4, 0x3c, 0xa0, 0x62, 0xc4, 0x82, + 0x97, 0xf1, 0xd3, 0x5e, 0x51, 0x37, 0xb2, 0x1d, 0xaf, 0xf7, 0xd3, 0x65, 0xf5, 0xc2, 0xa3, 0x67, + 0xb0, 0x41, 0xfc, 0x31, 0x0b, 0x5c, 0x4e, 0x85, 0x60, 0xc1, 0x39, 0xb7, 0x40, 0xc5, 0xa7, 0xb9, + 0xc2, 0x9a, 0xb6, 0x14, 0x3c, 0x8e, 0xe5, 0x70, 0x9d, 0x64, 0x49, 0xf4, 0x0d, 0xa8, 0xb3, 0x40, + 0x44, 0xa1, 0x3b, 0xa6, 0x9c, 0xcb, 0x77, 0xb0, 0xaa, 0xd2, 0xb3, 0xa6, 0x98, 0x7d, 0xcd, 0x93, + 0x42, 0xe1, 0x34, 0x2b, 0x54, 0xd3, 0x42, 0x8a, 0x99, 0x08, 0x35, 0xa1, 0x42, 0x03, 0x2f, 0x9a, + 0x4d, 0x04, 0xf5, 0xad, 0xba, 0x4c, 0x1a, 0xd5, 0xc9, 0xcc, 0x99, 0xb2, 0xd0, 0x09, 0x72, 0xce, + 0xad, 0x0d, 0x15, 0x55, 0xf5, 0x8d, 0x08, 0x6c, 0xe9, 0x34, 0xce, 0x42, 0x65, 0x53, 0x45, 0xf6, + 0x7b, 0xd7, 0x44, 0x76, 0xa1, 0x38, 0xc4, 0xf1, 0x6d, 0x88, 0x05, 0x36, 0xfa, 0x29, 0xdc, 0x9e, + 0xf7, 0x8f, 0x6a, 0x95, 0xbb, 0xe3, 0xb8, 0x97, 0xb0, 0x1a, 0xea, 0xa8, 0xe6, 0x75, 0x3d, 0x07, + 0xde, 0xf1, 0x72, 0x7c, 0x9e, 0xb6, 0x32, 0xef, 0xc0, 0x4d, 0xe2, 0x09, 0x75, 0x85, 0x1a, 0xf7, + 0xae, 0x6a, 0xd8, 0xac, 0x2d, 0x75, 0x7f, 0x48, 0xaf, 0xc5, 0x09, 0xd2, 0x51, 0x35, 0x7c, 0x03, + 0x0a, 0xbd, 0x03, 0x0b, 0xe9, 0x32, 0xd8, 0x3b, 0x40, 0x9f, 0x40, 0x35, 0xae, 0x35, 0x07, 0xd2, + 0x22, 0x5f, 0x59, 0xf4, 0xe8, 0x1a, 0xe7, 0x8f, 0xe6, 0x3b, 0xb4, 0xdf, 0x59, 0x1d, 0xbb, 0x27, + 0x50, 0xcb, 0xe6, 0x64, 0xb6, 0x84, 0x57, 0x74, 0x09, 0x7f, 0x94, 0x2d, 0xe1, 0xb9, 0x76, 0x74, + 0xa1, 0xa3, 0xcd, 0x54, 0xf7, 0xdd, 0x4f, 0x00, 0xe6, 0xf9, 0xb2, 0x42, 0xe9, 0x77, 0xf2, 0x4a, + 0x77, 0x56, 0x28, 0x95, 0xfb, 0xb3, 0x2a, 0x3f, 0x87, 0xcd, 0x85, 0x0c, 0x59, 0xa1, 0xf7, 0xdd, + 0xbc, 0xde, 0x3b, 0xab, 0xf4, 0x6a, 0x25, 0xb3, 0xac, 0xee, 0x73, 0xb8, 0xb5, 0x12, 0x23, 0x2b, + 0x4e, 0x78, 0x9c, 0x3f, 0xc1, 0xbe, 0xfe, 0x2d, 0xca, 0x1e, 0xf4, 0x04, 0x1a, 0x8b, 0xf7, 0xb1, + 0xe2, 0x8c, 0xdc, 0xab, 0x59, 0xcb, 0xbe, 0x9a, 0x3f, 0xcf, 0x34, 0xca, 0xb9, 0x6c, 0x45, 0x07, + 0x70, 0x7f, 0xc2, 0x82, 0x24, 0xef, 0x5c, 0x32, 0x1a, 0xa5, 0x30, 0xa3, 0x01, 0x39, 0x1d, 0x51, + 0x3f, 0x6e, 0xde, 0xee, 0x4c, 0x58, 0x10, 0x67, 0x62, 0x7b, 0x34, 0x4a, 0x2f, 0x5f, 0x89, 0xd8, + 0xff, 0x2c, 0x40, 0x3d, 0x77, 0x03, 0xe8, 0xc9, 0xbc, 0xc4, 0xeb, 0x8e, 0xe8, 0x9b, 0x57, 0xdc, + 0xd5, 0xeb, 0xd5, 0xf6, 0xc2, 0x57, 0xab, 0xed, 0xe6, 0x6b, 0xd6, 0xf6, 0xfb, 0x50, 0x8d, 0xab, + 0xa7, 0x1a, 0x04, 0x75, 0xc3, 0x94, 0x14, 0x54, 0x39, 0x07, 0xee, 0x42, 0x79, 0x12, 0x72, 0xa6, + 0x9a, 0x7d, 0xf9, 0x60, 0x94, 0x70, 0x4a, 0xff, 0x9f, 0x72, 0xc2, 0xf6, 0x61, 0x6b, 0x09, 0x84, + 0x8b, 0x86, 0x1a, 0x4b, 0x86, 0x26, 0x3d, 0x5f, 0x21, 0x3f, 0x0c, 0xa4, 0xc6, 0x9b, 0x79, 0xe3, + 0xed, 0xdf, 0x1b, 0xb0, 0xb9, 0x30, 0x27, 0xca, 0x36, 0x3d, 0xee, 0x6b, 0xe3, 0x03, 0x12, 0x12, + 0xdd, 0x85, 0x0a, 0x67, 0xe7, 0x01, 0x11, 0xd3, 0x28, 0x41, 0xdb, 0x9c, 0x21, 0x7b, 0x48, 0xef, + 0x82, 0x30, 0xdd, 0x43, 0x9a, 0xba, 0x87, 0x54, 0x0c, 0xd9, 0xfb, 0x3c, 0x84, 0x06, 0xe3, 0x6d, + 0x16, 0xf9, 0x51, 0x38, 0x89, 0xfb, 0x40, 0x15, 0xe7, 0x32, 0x5e, 0xe2, 0xdb, 0xff, 0x36, 0x32, + 0xb8, 0xc5, 0xf4, 0x17, 0x53, 0xca, 0xc5, 0x30, 0xfc, 0x38, 0x64, 0x57, 0x35, 0x16, 0xf1, 0xcc, + 0x91, 0xf1, 0x5c, 0xce, 0x1c, 0x8e, 0x74, 0xfe, 0xca, 0x21, 0x7d, 0x71, 0xfa, 0x2f, 0x2e, 0x4f, + 0xff, 0x0f, 0xa0, 0xe6, 0x33, 0x3e, 0x19, 0x91, 0x99, 0x56, 0x5d, 0x8a, 0xc7, 0x3c, 0xcd, 0x53, + 0xea, 0x7f, 0xb4, 0x6a, 0x12, 0x5f, 0xbb, 0x66, 0x12, 0x5f, 0x9e, 0xc2, 0xed, 0x3f, 0x19, 0x70, + 0x37, 0x75, 0xb9, 0xeb, 0x33, 0x71, 0x7c, 0x41, 0x22, 0xea, 0xcf, 0xc7, 0x82, 0xd5, 0x8e, 0x2f, + 0x3a, 0x51, 0x58, 0x76, 0x62, 0xa5, 0x85, 0xe6, 0x97, 0xb7, 0xf0, 0x2f, 0x59, 0x0b, 0x3b, 0x24, + 0xf0, 0xe8, 0xe8, 0x6b, 0x7d, 0x35, 0xf6, 0x17, 0x05, 0x78, 0x73, 0x35, 0x8a, 0x30, 0xe5, 0x93, + 0x30, 0xe0, 0xf4, 0x0a, 0x93, 0x7f, 0x08, 0x95, 0xf4, 0xa8, 0x57, 0x54, 0xa0, 0xcc, 0xab, 0x89, + 0xe7, 0x1b, 0x64, 0xb6, 0xc9, 0xa9, 0x54, 0x75, 0x2b, 0xa6, 0x02, 0x78, 0x4a, 0xcb, 0xf3, 0xce, + 0x23, 0x12, 0x88, 0xd8, 0x23, 0x4d, 0x2c, 0xb9, 0x5b, 0x5a, 0x76, 0xf7, 0x1e, 0x80, 0x6e, 0xe4, + 0xdc, 0x69, 0xc4, 0xe2, 0x49, 0xbf, 0xa2, 0x39, 0x27, 0x11, 0x43, 0x1f, 0xc0, 0x1d, 0x69, 0x1f, + 0xf5, 0x04, 0xf5, 0x5d, 0x11, 0x4e, 0x98, 0x97, 0x4c, 0x19, 0xae, 0x2c, 0x45, 0xeb, 0x4a, 0xa1, + 0x95, 0x8a, 0x0c, 0xa5, 0x44, 0xfc, 0xb0, 0x3c, 0xa7, 0x33, 0xf4, 0x16, 0x94, 0xd4, 0x1f, 0x64, + 0x6a, 0x76, 0xab, 0xee, 0x6f, 0xce, 0x9d, 0x95, 0x28, 0xf4, 0xb1, 0x5e, 0xb5, 0x31, 0xec, 0x2c, + 0xc7, 0xf3, 0x90, 0x92, 0x4b, 0xfa, 0x5f, 0xa3, 0xd3, 0xfe, 0x09, 0x3c, 0xc8, 0xd4, 0x40, 0xfd, + 0xcc, 0x2c, 0x76, 0xa6, 0x57, 0x68, 0xcf, 0xc7, 0xa4, 0xb0, 0x10, 0x13, 0xfb, 0x6f, 0x06, 0x54, + 0x5f, 0x90, 0x97, 0xd3, 0xa4, 0x8d, 0x6c, 0x80, 0xc9, 0xd9, 0x79, 0xfc, 0x27, 0x9f, 0xfc, 0x94, + 0xd5, 0x4c, 0xb0, 0x31, 0xe5, 0x82, 0x8c, 0x27, 0x6a, 0x7f, 0x11, 0xcf, 0x19, 0xf2, 0x50, 0x15, + 0x49, 0x75, 0x89, 0x35, 0xac, 0x09, 0xf5, 0x17, 0x06, 0x99, 0x8d, 0x42, 0x92, 0xa0, 0x32, 0x21, + 0xf5, 0x8a, 0xef, 0xb3, 0xe0, 0x3c, 0xbe, 0xc0, 0x84, 0x94, 0x35, 0xf9, 0x82, 0xf0, 0x0b, 0x75, + 0x6d, 0x35, 0xac, 0xbe, 0x91, 0x0d, 0x35, 0x71, 0xc1, 0x22, 0xff, 0x88, 0x44, 0x32, 0x0e, 0xf1, + 0x4c, 0x9d, 0xe3, 0xd9, 0xbf, 0x86, 0xdd, 0x8c, 0x03, 0x49, 0x58, 0x92, 0xfe, 0xd0, 0x82, 0xf5, + 0x4b, 0x1a, 0xc9, 0x37, 0x4f, 0xf9, 0x54, 0xc7, 0x09, 0x29, 0xcf, 0x3b, 0x8b, 0xc2, 0x71, 0xec, + 0x92, 0xfa, 0x96, 0xbd, 0xa1, 0x08, 0xe3, 0xbf, 0xf5, 0x0a, 0x22, 0x94, 0xe7, 0x7b, 0x61, 0x20, + 0x68, 0x20, 0x14, 0x18, 0xd4, 0xa4, 0x5a, 0xc3, 0x39, 0x9e, 0xfd, 0x67, 0x03, 0xd0, 0xb2, 0x01, + 0xaf, 0x38, 0xf8, 0x23, 0x28, 0xa7, 0xfd, 0xaf, 0xce, 0x9b, 0xcc, 0xeb, 0x7f, 0xb5, 0x2b, 0x38, + 0xdd, 0x85, 0xde, 0x95, 0x1a, 0x94, 0x4c, 0x52, 0xa3, 0x6e, 0xad, 0xd4, 0x80, 0x53, 0x31, 0xfb, + 0xef, 0x06, 0xdc, 0x5f, 0xd6, 0xdd, 0x0b, 0x7c, 0xfa, 0xcb, 0xd7, 0x88, 0xd5, 0x57, 0x37, 0x79, + 0x1b, 0xd6, 0xc2, 0xb3, 0x33, 0x4e, 0x45, 0x1c, 0xdd, 0x98, 0x92, 0xb7, 0xc0, 0xd9, 0xaf, 0x68, + 0xfc, 0x1f, 0xb0, 0xfa, 0x5e, 0xc4, 0x48, 0x31, 0xc5, 0x88, 0xfd, 0x85, 0x01, 0x3b, 0x57, 0x78, + 0x81, 0x9e, 0x43, 0x39, 0x9e, 0xd6, 0x92, 0xa6, 0xea, 0xd1, 0xab, 0x6c, 0x54, 0x9b, 0x5a, 0x31, + 0x11, 0xf7, 0x57, 0xa9, 0x82, 0xdd, 0x33, 0xa8, 0xe7, 0x96, 0x56, 0xb4, 0x2b, 0x1f, 0xe6, 0xdb, + 0x95, 0xb7, 0xaf, 0x3d, 0x2c, 0x8d, 0xca, 0xbc, 0x7d, 0x79, 0x5a, 0xff, 0xbc, 0xda, 0x7a, 0xf4, + 0x7e, 0xb2, 0xf3, 0x74, 0x4d, 0x7d, 0x7d, 0xf7, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x53, 0x4d, + 0x30, 0x03, 0xc9, 0x17, 0x00, 0x00, } diff --git a/protocol/protobuf/communities.proto b/protocol/protobuf/communities.proto index 05ec64130..44d7bcc81 100644 --- a/protocol/protobuf/communities.proto +++ b/protocol/protobuf/communities.proto @@ -100,6 +100,9 @@ message CommunityDescription { repeated CommunityTokenMetadata community_tokens_metadata = 16; uint64 active_members_count = 17; string ID = 18; + + // key is hash ratchet key_id + seq_no + map privateData = 100; } message CommunityAdminSettings {