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:
parent
9cbfda69da
commit
1d3c618fb4
|
@ -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() {
|
||||
// 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)) {
|
||||
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 {
|
||||
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 description.Chats {
|
||||
if !channelHasTokenPermissions(channelID) {
|
||||
if !channelHasTokenPermissions(communityID, channelID, description.TokenPermissions) {
|
||||
channel.Members = map[string]*protobuf.CommunityMember{} // clean members
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hydrateChannelsMembers(communityID string, description *protobuf.CommunityDescription) {
|
||||
for channelID, channel := range description.Chats {
|
||||
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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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 (
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -501,10 +506,7 @@ func NewMessenger(
|
|||
pushNotificationClient: pushNotificationClient,
|
||||
pushNotificationServer: pushNotificationServer,
|
||||
communitiesManager: communitiesManager,
|
||||
communitiesKeyDistributor: &CommunitiesKeyDistributorImpl{
|
||||
sender: sender,
|
||||
encryptor: encryptionProtocol,
|
||||
},
|
||||
communitiesKeyDistributor: communitiesKeyDistributor,
|
||||
accountsManager: accountsManager,
|
||||
ensVerifier: ensVerifier,
|
||||
featureFlags: c.featureFlags,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -568,6 +568,8 @@ 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"`
|
||||
// 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:"-"`
|
||||
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue