feat: encrypt `CommunityDescription` fields

Extended `CommunityDescription` with a `privateData` map. This map
associates each hash ratchet `key_id` and `seq_no` with an encrypted
`CommunityDescription`. Each encrypted instance includes only data
requiring encryption.

closes: status-im/status-desktop#12851
closes: status-im/status-desktop#12852
closes: status-im/status-desktop#12853
This commit is contained in:
Patryk Osmaczko 2023-11-29 18:21:21 +01:00 committed by osmaczko
parent 9cbfda69da
commit 1d3c618fb4
22 changed files with 846 additions and 301 deletions

View File

@ -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 {

View File

@ -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
}

View File

@ -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")
}

View File

@ -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 (

View File

@ -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) {

View File

@ -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

View File

@ -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
}

View File

@ -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{}

View File

@ -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())

View File

@ -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) {

View File

@ -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
}

View File

@ -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()

View File

@ -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)

View File

@ -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")

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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()
}

View File

@ -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,
}

View File

@ -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<string, bytes> privateData = 100;
}
message CommunityAdminSettings {