status-go/protocol/messenger_community_shard.go

129 lines
3.7 KiB
Go

package protocol
import (
"context"
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/proto"
"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/common"
"github.com/status-im/status-go/protocol/common/shard"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/transport"
v1protocol "github.com/status-im/status-go/protocol/v1"
)
func (m *Messenger) sendPublicCommunityShardInfo(community *communities.Community) error {
if !community.IsControlNode() {
return communities.ErrNotControlNode
}
publicShardInfo := &protobuf.PublicShardInfo{
Clock: community.Clock(),
CommunityId: community.ID(),
Shard: community.Shard().Protobuffer(),
ChainId: communities.CommunityDescriptionTokenOwnerChainID(community.Description()),
}
payload, err := proto.Marshal(publicShardInfo)
if err != nil {
return err
}
signature, err := crypto.Sign(crypto.Keccak256(payload), community.PrivateKey())
if err != nil {
return err
}
signedShardInfo := &protobuf.CommunityPublicShardInfo{
Signature: signature,
Payload: payload,
}
payload, err = proto.Marshal(signedShardInfo)
if err != nil {
return err
}
rawMessage := common.RawMessage{
Payload: payload,
Sender: community.PrivateKey(),
// we don't want to wrap in an encryption layer message
SkipEncryptionLayer: true,
MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_PUBLIC_SHARD_INFO,
PubsubTopic: shard.DefaultNonProtectedPubsubTopic(), // it must be sent always to default shard pubsub topic
}
chatName := transport.CommunityShardInfoTopic(community.IDString())
messageID, err := m.sender.SendPublic(context.Background(), chatName, rawMessage)
if err == nil {
m.logger.Debug("published public community shard info",
zap.String("communityID", community.IDString()),
zap.String("messageID", hexutil.Encode(messageID)),
)
}
return err
}
func (m *Messenger) HandleCommunityPublicShardInfo(state *ReceivedMessageState, a *protobuf.CommunityPublicShardInfo, statusMessage *v1protocol.StatusMessage) error {
publicShardInfo := &protobuf.PublicShardInfo{}
err := proto.Unmarshal(a.Payload, publicShardInfo)
if err != nil {
return err
}
logError := func(err error) {
m.logger.Error("HandleCommunityPublicShardInfo failed: ", zap.Error(err), zap.String("communityID", types.EncodeHex(publicShardInfo.CommunityId)))
}
err = m.verifyCommunitySignature(a.Payload, a.Signature, publicShardInfo.CommunityId, publicShardInfo.ChainId)
if err != nil {
logError(err)
return err
}
err = m.communitiesManager.SaveCommunityShard(publicShardInfo.CommunityId, shard.FromProtobuff(publicShardInfo.Shard), publicShardInfo.Clock)
if err != nil && err != communities.ErrOldShardInfo {
logError(err)
return err
}
return nil
}
func (m *Messenger) verifyCommunitySignature(payload, signature, communityID []byte, chainID uint64) error {
if len(signature) == 0 {
return errors.New("missing signature")
}
pubKey, err := crypto.SigToPub(crypto.Keccak256(payload), signature)
if err != nil {
return err
}
pubKeyStr := common.PubkeyToHex(pubKey)
var ownerPublicKey string
if chainID > 0 {
owner, err := m.communitiesManager.SafeGetSignerPubKey(chainID, types.EncodeHex(communityID))
if err != nil {
return err
}
ownerPublicKey = owner
} else {
communityPubkey, err := crypto.DecompressPubkey(communityID)
if err != nil {
return err
}
ownerPublicKey = common.PubkeyToHex(communityPubkey)
}
if pubKeyStr != ownerPublicKey {
return errors.New("signed not by a community owner")
}
return nil
}