2023-10-24 10:43:18 +00:00
|
|
|
package protocol
|
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
import (
|
2024-02-15 19:13:12 +00:00
|
|
|
"bytes"
|
2024-02-17 18:07:20 +00:00
|
|
|
"context"
|
2023-11-09 18:59:01 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
crand "crypto/rand"
|
|
|
|
"errors"
|
2024-02-22 08:08:58 +00:00
|
|
|
"math/big"
|
2023-11-09 18:59:01 +00:00
|
|
|
"reflect"
|
2024-02-15 19:13:12 +00:00
|
|
|
"sort"
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2023-12-22 15:43:27 +00:00
|
|
|
"github.com/golang/protobuf/proto"
|
2024-02-15 19:13:12 +00:00
|
|
|
"go.uber.org/zap"
|
2023-11-09 18:59:01 +00:00
|
|
|
|
2024-02-22 08:08:58 +00:00
|
|
|
eth_common "github.com/ethereum/go-ethereum/common"
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
2024-02-07 09:30:56 +00:00
|
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
2023-11-09 18:59:01 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common"
|
2024-02-09 09:37:54 +00:00
|
|
|
"github.com/status-im/status-go/protocol/communities"
|
2024-02-15 19:13:12 +00:00
|
|
|
"github.com/status-im/status-go/protocol/identity"
|
2023-11-09 18:59:01 +00:00
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
2024-02-22 08:08:58 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/bigint"
|
|
|
|
w_common "github.com/status-im/status-go/services/wallet/common"
|
|
|
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
2023-11-09 18:59:01 +00:00
|
|
|
)
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2024-01-25 16:48:27 +00:00
|
|
|
var errorDecryptingPayloadEncryptionKey = errors.New("decrypting the payload encryption key resulted in no error and a nil key")
|
2024-02-22 08:08:58 +00:00
|
|
|
var errorConvertCollectibleTokenIDToInt = errors.New("failed to convert collectible token id to bigint")
|
|
|
|
var errorNoAccountPresentedForCollectible = errors.New("account holding the collectible is not presented in the profile showcase")
|
|
|
|
var errorDublicateAccountAddress = errors.New("duplicate account address")
|
|
|
|
var errorAccountVisibilityLowerThanCollectible = errors.New("account visibility lower than collectible")
|
|
|
|
|
2024-02-26 13:53:40 +00:00
|
|
|
func sortProfileEntyByOrder(slice interface{}, getOrder func(int) int) {
|
|
|
|
sort.Slice(slice, func(i, j int) bool {
|
|
|
|
return getOrder(j) > getOrder(i)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:08:58 +00:00
|
|
|
func toCollectibleUniqueID(contractAddress string, tokenID string, chainID uint64) (thirdparty.CollectibleUniqueID, error) {
|
|
|
|
tokenIDInt := new(big.Int)
|
|
|
|
tokenIDInt, isTokenIDOk := tokenIDInt.SetString(tokenID, 10)
|
|
|
|
if !isTokenIDOk {
|
|
|
|
return thirdparty.CollectibleUniqueID{}, errorConvertCollectibleTokenIDToInt
|
|
|
|
}
|
|
|
|
|
|
|
|
return thirdparty.CollectibleUniqueID{
|
|
|
|
ContractID: thirdparty.ContractID{
|
|
|
|
ChainID: w_common.ChainID(chainID),
|
|
|
|
Address: eth_common.HexToAddress(contractAddress),
|
|
|
|
},
|
|
|
|
TokenID: &bigint.BigInt{Int: tokenIDInt},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) fetchCollectibleOwner(contractAddress string, tokenID string, chainID uint64) ([]thirdparty.AccountBalance, error) {
|
|
|
|
collectibleID, err := toCollectibleUniqueID(contractAddress, tokenID, chainID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
balance, err := m.communitiesManager.GetCollectiblesManager().GetCollectibleOwnership(collectibleID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return balance, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) validateCollectiblesOwnership(accounts []*identity.ProfileShowcaseAccountPreference,
|
|
|
|
collectibles []*identity.ProfileShowcaseCollectiblePreference) error {
|
|
|
|
accountsMap := make(map[string]identity.ProfileShowcaseVisibility)
|
|
|
|
|
|
|
|
for _, accountProfile := range accounts {
|
|
|
|
if _, ok := accountsMap[accountProfile.Address]; ok {
|
|
|
|
return errorDublicateAccountAddress
|
|
|
|
}
|
|
|
|
accountsMap[accountProfile.Address] = accountProfile.ShowcaseVisibility
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, collectibleProfile := range collectibles {
|
|
|
|
balances, err := m.fetchCollectibleOwner(collectibleProfile.ContractAddress, collectibleProfile.TokenID,
|
|
|
|
collectibleProfile.ChainID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: ERC721 tokens can have only a single holder
|
|
|
|
// but ERC1155 which can be supported later can have more than one holder and balances > 1
|
|
|
|
found := false
|
|
|
|
for _, balance := range balances {
|
|
|
|
if accountShowcaseVisibility, ok := accountsMap[balance.Address.String()]; ok {
|
|
|
|
if accountShowcaseVisibility < collectibleProfile.ShowcaseVisibility {
|
|
|
|
return errorAccountVisibilityLowerThanCollectible
|
|
|
|
}
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
return errorNoAccountPresentedForCollectible
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2024-01-25 16:48:27 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) toProfileShowcaseCommunityProto(preferences []*identity.ProfileShowcaseCommunityPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseCommunity {
|
|
|
|
entries := []*protobuf.ProfileShowcaseCommunity{}
|
2023-11-09 18:59:01 +00:00
|
|
|
for _, preference := range preferences {
|
|
|
|
if preference.ShowcaseVisibility != visibility {
|
|
|
|
continue
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2023-11-09 18:59:01 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
entry := &protobuf.ProfileShowcaseCommunity{
|
2023-11-09 18:59:01 +00:00
|
|
|
CommunityId: preference.CommunityID,
|
|
|
|
Order: uint32(preference.Order),
|
2024-02-15 19:13:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.communitiesManager.GetByIDString(preference.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("failed to get community for profile entry ", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if community != nil && community.Encrypted() {
|
|
|
|
grant, _, err := m.communitiesManager.GetCommunityGrant(preference.CommunityID)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("failed to get community for profile entry ", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.Grant = grant
|
|
|
|
}
|
|
|
|
|
|
|
|
entries = append(entries, entry)
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) toProfileShowcaseAccountProto(preferences []*identity.ProfileShowcaseAccountPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseAccount {
|
|
|
|
entries := []*protobuf.ProfileShowcaseAccount{}
|
2023-11-09 18:59:01 +00:00
|
|
|
for _, preference := range preferences {
|
|
|
|
if preference.ShowcaseVisibility != visibility {
|
|
|
|
continue
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2023-11-09 18:59:01 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &protobuf.ProfileShowcaseAccount{
|
2023-11-09 18:59:01 +00:00
|
|
|
Address: preference.Address,
|
|
|
|
Name: preference.Name,
|
|
|
|
ColorId: preference.ColorID,
|
|
|
|
Emoji: preference.Emoji,
|
|
|
|
Order: uint32(preference.Order),
|
|
|
|
})
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) toProfileShowcaseCollectibleProto(preferences []*identity.ProfileShowcaseCollectiblePreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseCollectible {
|
|
|
|
entries := []*protobuf.ProfileShowcaseCollectible{}
|
2023-11-09 18:59:01 +00:00
|
|
|
for _, preference := range preferences {
|
|
|
|
if preference.ShowcaseVisibility != visibility {
|
|
|
|
continue
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2023-11-09 18:59:01 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &protobuf.ProfileShowcaseCollectible{
|
2024-01-25 16:48:27 +00:00
|
|
|
ContractAddress: preference.ContractAddress,
|
|
|
|
ChainId: preference.ChainID,
|
|
|
|
TokenId: preference.TokenID,
|
|
|
|
CommunityId: preference.CommunityID,
|
|
|
|
AccountAddress: preference.AccountAddress,
|
|
|
|
Order: uint32(preference.Order),
|
2023-11-09 18:59:01 +00:00
|
|
|
})
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) toProfileShowcaseVerifiedTokensProto(preferences []*identity.ProfileShowcaseVerifiedTokenPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseVerifiedToken {
|
|
|
|
entries := []*protobuf.ProfileShowcaseVerifiedToken{}
|
2023-11-09 18:59:01 +00:00
|
|
|
for _, preference := range preferences {
|
|
|
|
if preference.ShowcaseVisibility != visibility {
|
|
|
|
continue
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2023-11-09 18:59:01 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &protobuf.ProfileShowcaseVerifiedToken{
|
2023-11-09 18:59:01 +00:00
|
|
|
Symbol: preference.Symbol,
|
|
|
|
Order: uint32(preference.Order),
|
|
|
|
})
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2024-01-25 16:48:27 +00:00
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) toProfileShowcaseUnverifiedTokensProto(preferences []*identity.ProfileShowcaseUnverifiedTokenPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseUnverifiedToken {
|
|
|
|
entries := []*protobuf.ProfileShowcaseUnverifiedToken{}
|
2024-01-25 16:48:27 +00:00
|
|
|
for _, preference := range preferences {
|
|
|
|
if preference.ShowcaseVisibility != visibility {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &protobuf.ProfileShowcaseUnverifiedToken{
|
2024-01-25 16:48:27 +00:00
|
|
|
ContractAddress: preference.ContractAddress,
|
|
|
|
ChainId: preference.ChainID,
|
|
|
|
Order: uint32(preference.Order),
|
|
|
|
})
|
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 13:53:40 +00:00
|
|
|
func (m *Messenger) toProfileShowcaseSocialLinksProto(preferences []*identity.ProfileShowcaseSocialLinkPreference, visibility identity.ProfileShowcaseVisibility) []*protobuf.ProfileShowcaseSocialLink {
|
|
|
|
entries := []*protobuf.ProfileShowcaseSocialLink{}
|
|
|
|
for _, preference := range preferences {
|
|
|
|
if preference.ShowcaseVisibility != visibility {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
entries = append(entries, &protobuf.ProfileShowcaseSocialLink{
|
|
|
|
Text: preference.Text,
|
|
|
|
Url: preference.URL,
|
|
|
|
Order: uint32(preference.Order),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return entries
|
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) fromProfileShowcaseCommunityProto(senderPubKey *ecdsa.PublicKey, messages []*protobuf.ProfileShowcaseCommunity) []*identity.ProfileShowcaseCommunity {
|
|
|
|
entries := []*identity.ProfileShowcaseCommunity{}
|
|
|
|
for _, message := range messages {
|
|
|
|
entry := &identity.ProfileShowcaseCommunity{
|
|
|
|
CommunityID: message.CommunityId,
|
|
|
|
Order: int(message.Order),
|
|
|
|
MembershipStatus: identity.ProfileShowcaseMembershipStatusUnproven,
|
|
|
|
}
|
|
|
|
|
|
|
|
community, err := m.FetchCommunity(&FetchCommunityRequest{
|
|
|
|
CommunityKey: message.CommunityId,
|
|
|
|
Shard: nil,
|
|
|
|
TryDatabase: true,
|
|
|
|
WaitForResponse: true,
|
2023-11-09 18:59:01 +00:00
|
|
|
})
|
2024-02-15 19:13:12 +00:00
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("failed to fetch community for profile entry ", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if community != nil && community.Encrypted() {
|
|
|
|
grant, err := community.VerifyGrantSignature(message.Grant)
|
|
|
|
if err != nil {
|
|
|
|
m.logger.Warn("failed to verify grant signature ", zap.Error(err))
|
|
|
|
entry.MembershipStatus = identity.ProfileShowcaseMembershipStatusNotAMember
|
|
|
|
} else {
|
|
|
|
if grant != nil && bytes.Equal(grant.MemberId, crypto.CompressPubkey(senderPubKey)) {
|
|
|
|
entry.MembershipStatus = identity.ProfileShowcaseMembershipStatusProvenMember
|
|
|
|
} else { // Show as not a member if membership can't be proven
|
|
|
|
entry.MembershipStatus = identity.ProfileShowcaseMembershipStatusNotAMember
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if community != nil {
|
|
|
|
// Use member list as a proof for unecrypted communities
|
|
|
|
if community.HasMember(senderPubKey) {
|
|
|
|
entry.MembershipStatus = identity.ProfileShowcaseMembershipStatusProvenMember
|
|
|
|
} else {
|
|
|
|
entry.MembershipStatus = identity.ProfileShowcaseMembershipStatusNotAMember
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
entries = append(entries, entry)
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) fromProfileShowcaseAccountProto(messages []*protobuf.ProfileShowcaseAccount) []*identity.ProfileShowcaseAccount {
|
|
|
|
entries := []*identity.ProfileShowcaseAccount{}
|
2023-11-09 18:59:01 +00:00
|
|
|
for _, entry := range messages {
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &identity.ProfileShowcaseAccount{
|
2023-11-09 18:59:01 +00:00
|
|
|
Address: entry.Address,
|
|
|
|
Name: entry.Name,
|
|
|
|
ColorID: entry.ColorId,
|
|
|
|
Emoji: entry.Emoji,
|
|
|
|
Order: int(entry.Order),
|
|
|
|
})
|
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) fromProfileShowcaseCollectibleProto(messages []*protobuf.ProfileShowcaseCollectible) []*identity.ProfileShowcaseCollectible {
|
|
|
|
entries := []*identity.ProfileShowcaseCollectible{}
|
2024-02-22 08:08:58 +00:00
|
|
|
for _, message := range messages {
|
|
|
|
entry := &identity.ProfileShowcaseCollectible{
|
|
|
|
ContractAddress: message.ContractAddress,
|
|
|
|
ChainID: message.ChainId,
|
|
|
|
TokenID: message.TokenId,
|
|
|
|
CommunityID: message.CommunityId,
|
|
|
|
AccountAddress: message.AccountAddress,
|
|
|
|
Order: int(message.Order),
|
|
|
|
}
|
|
|
|
entries = append(entries, entry)
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) fromProfileShowcaseVerifiedTokenProto(messages []*protobuf.ProfileShowcaseVerifiedToken) []*identity.ProfileShowcaseVerifiedToken {
|
|
|
|
entries := []*identity.ProfileShowcaseVerifiedToken{}
|
2023-11-09 18:59:01 +00:00
|
|
|
for _, entry := range messages {
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &identity.ProfileShowcaseVerifiedToken{
|
2023-11-09 18:59:01 +00:00
|
|
|
Symbol: entry.Symbol,
|
|
|
|
Order: int(entry.Order),
|
|
|
|
})
|
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2024-01-25 16:48:27 +00:00
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) fromProfileShowcaseUnverifiedTokenProto(messages []*protobuf.ProfileShowcaseUnverifiedToken) []*identity.ProfileShowcaseUnverifiedToken {
|
|
|
|
entries := []*identity.ProfileShowcaseUnverifiedToken{}
|
2024-01-25 16:48:27 +00:00
|
|
|
for _, entry := range messages {
|
2024-02-15 19:13:12 +00:00
|
|
|
entries = append(entries, &identity.ProfileShowcaseUnverifiedToken{
|
2024-01-25 16:48:27 +00:00
|
|
|
ContractAddress: entry.ContractAddress,
|
|
|
|
ChainID: entry.ChainId,
|
|
|
|
Order: int(entry.Order),
|
|
|
|
})
|
|
|
|
}
|
2024-02-15 19:13:12 +00:00
|
|
|
return entries
|
2024-01-25 16:48:27 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 13:53:40 +00:00
|
|
|
func (m *Messenger) fromProfileShowcaseSocialLinkProto(messages []*protobuf.ProfileShowcaseSocialLink) []*identity.ProfileShowcaseSocialLink {
|
|
|
|
entries := []*identity.ProfileShowcaseSocialLink{}
|
|
|
|
for _, entry := range messages {
|
|
|
|
entries = append(entries, &identity.ProfileShowcaseSocialLink{
|
|
|
|
Text: entry.Text,
|
|
|
|
URL: entry.Url,
|
|
|
|
Order: int(entry.Order),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return entries
|
|
|
|
}
|
|
|
|
|
2024-02-17 18:07:20 +00:00
|
|
|
func (m *Messenger) SetProfileShowcasePreferences(preferences *identity.ProfileShowcasePreferences, sync bool) error {
|
|
|
|
clock, _ := m.getLastClockWithRelatedChat()
|
|
|
|
preferences.Clock = clock
|
|
|
|
return m.setProfileShowcasePreferences(preferences, sync)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) setProfileShowcasePreferences(preferences *identity.ProfileShowcasePreferences, sync bool) error {
|
2024-02-15 19:13:12 +00:00
|
|
|
err := identity.Validate(preferences)
|
2024-01-25 16:48:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:08:58 +00:00
|
|
|
err = m.validateCollectiblesOwnership(preferences.Accounts, preferences.Collectibles)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-01-25 16:48:27 +00:00
|
|
|
err = m.persistence.SaveProfileShowcasePreferences(preferences)
|
2023-10-24 10:43:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-17 18:07:20 +00:00
|
|
|
if sync {
|
|
|
|
err = m.syncProfileShowcasePreferences(context.Background(), m.dispatchMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 09:30:56 +00:00
|
|
|
return m.DispatchProfileShowcase()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DispatchProfileShowcase() error {
|
2024-02-17 18:07:20 +00:00
|
|
|
err := m.publishContactCode()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) GetProfileShowcasePreferences() (*identity.ProfileShowcasePreferences, error) {
|
2023-11-09 18:59:01 +00:00
|
|
|
return m.persistence.GetProfileShowcasePreferences()
|
|
|
|
}
|
2023-10-24 10:43:18 +00:00
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) GetProfileShowcaseForContact(contactID string) (*identity.ProfileShowcase, error) {
|
2023-11-09 18:59:01 +00:00
|
|
|
return m.persistence.GetProfileShowcaseForContact(contactID)
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) GetProfileShowcaseAccountsByAddress(address string) ([]*identity.ProfileShowcaseAccount, error) {
|
2024-01-25 13:29:29 +00:00
|
|
|
return m.persistence.GetProfileShowcaseAccountsByAddress(address)
|
|
|
|
}
|
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
func (m *Messenger) EncryptProfileShowcaseEntriesWithContactPubKeys(entries *protobuf.ProfileShowcaseEntries, contacts []*Contact) (*protobuf.ProfileShowcaseEntriesEncrypted, error) {
|
|
|
|
// Make AES key
|
|
|
|
AESKey := make([]byte, 32)
|
|
|
|
_, err := crand.Read(AESKey)
|
2023-10-24 10:43:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
// Encrypt showcase entries with the AES key
|
|
|
|
data, err := proto.Marshal(entries)
|
2023-10-24 10:43:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
encrypted, err := common.Encrypt(data, AESKey, crand.Reader)
|
2023-10-24 10:43:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
eAESKeys := [][]byte{}
|
|
|
|
// Sign for each contact
|
|
|
|
for _, contact := range contacts {
|
|
|
|
var pubK *ecdsa.PublicKey
|
|
|
|
var sharedKey []byte
|
|
|
|
var eAESKey []byte
|
|
|
|
|
|
|
|
pubK, err = contact.PublicKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Generate a Diffie-Helman (DH) between the sender private key and the recipient's public key
|
|
|
|
sharedKey, err = common.MakeECDHSharedKey(m.identity, pubK)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encrypt the main AES key with AES encryption using the DH key
|
|
|
|
eAESKey, err = common.Encrypt(AESKey, sharedKey, crand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
eAESKeys = append(eAESKeys, eAESKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &protobuf.ProfileShowcaseEntriesEncrypted{
|
|
|
|
EncryptedEntries: encrypted,
|
|
|
|
EncryptionKeys: eAESKeys,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DecryptProfileShowcaseEntriesWithPubKey(senderPubKey *ecdsa.PublicKey, encrypted *protobuf.ProfileShowcaseEntriesEncrypted) (*protobuf.ProfileShowcaseEntries, error) {
|
|
|
|
for _, eAESKey := range encrypted.EncryptionKeys {
|
|
|
|
// Generate a Diffie-Helman (DH) between the recipient's private key and the sender's public key
|
|
|
|
sharedKey, err := common.MakeECDHSharedKey(m.identity, senderPubKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt the main encryption AES key with AES encryption using the DH key
|
|
|
|
dAESKey, err := common.Decrypt(eAESKey, sharedKey)
|
|
|
|
if err != nil {
|
|
|
|
if err.Error() == ErrCipherMessageAutentificationFailed {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if dAESKey == nil {
|
2024-01-25 16:48:27 +00:00
|
|
|
return nil, errorDecryptingPayloadEncryptionKey
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt profile entries with the newly decrypted main encryption AES key
|
|
|
|
entriesData, err := common.Decrypt(encrypted.EncryptedEntries, dAESKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
entries := &protobuf.ProfileShowcaseEntries{}
|
|
|
|
err = proto.Unmarshal(entriesData, entries)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return empty if no matching key found
|
|
|
|
return &protobuf.ProfileShowcaseEntries{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) GetProfileShowcaseForSelfIdentity() (*protobuf.ProfileShowcase, error) {
|
|
|
|
preferences, err := m.GetProfileShowcasePreferences()
|
2023-10-24 10:43:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
forEveryone := &protobuf.ProfileShowcaseEntries{
|
2024-02-15 19:13:12 +00:00
|
|
|
Communities: m.toProfileShowcaseCommunityProto(preferences.Communities, identity.ProfileShowcaseVisibilityEveryone),
|
|
|
|
Accounts: m.toProfileShowcaseAccountProto(preferences.Accounts, identity.ProfileShowcaseVisibilityEveryone),
|
|
|
|
Collectibles: m.toProfileShowcaseCollectibleProto(preferences.Collectibles, identity.ProfileShowcaseVisibilityEveryone),
|
|
|
|
VerifiedTokens: m.toProfileShowcaseVerifiedTokensProto(preferences.VerifiedTokens, identity.ProfileShowcaseVisibilityEveryone),
|
|
|
|
UnverifiedTokens: m.toProfileShowcaseUnverifiedTokensProto(preferences.UnverifiedTokens, identity.ProfileShowcaseVisibilityEveryone),
|
2024-02-26 13:53:40 +00:00
|
|
|
SocialLinks: m.toProfileShowcaseSocialLinksProto(preferences.SocialLinks, identity.ProfileShowcaseVisibilityEveryone),
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
forContacts := &protobuf.ProfileShowcaseEntries{
|
2024-02-15 19:13:12 +00:00
|
|
|
Communities: m.toProfileShowcaseCommunityProto(preferences.Communities, identity.ProfileShowcaseVisibilityContacts),
|
|
|
|
Accounts: m.toProfileShowcaseAccountProto(preferences.Accounts, identity.ProfileShowcaseVisibilityContacts),
|
|
|
|
Collectibles: m.toProfileShowcaseCollectibleProto(preferences.Collectibles, identity.ProfileShowcaseVisibilityContacts),
|
|
|
|
VerifiedTokens: m.toProfileShowcaseVerifiedTokensProto(preferences.VerifiedTokens, identity.ProfileShowcaseVisibilityContacts),
|
|
|
|
UnverifiedTokens: m.toProfileShowcaseUnverifiedTokensProto(preferences.UnverifiedTokens, identity.ProfileShowcaseVisibilityContacts),
|
2024-02-26 13:53:40 +00:00
|
|
|
SocialLinks: m.toProfileShowcaseSocialLinksProto(preferences.SocialLinks, identity.ProfileShowcaseVisibilityContacts),
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
forIDVerifiedContacts := &protobuf.ProfileShowcaseEntries{
|
2024-02-15 19:13:12 +00:00
|
|
|
Communities: m.toProfileShowcaseCommunityProto(preferences.Communities, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
|
|
|
|
Accounts: m.toProfileShowcaseAccountProto(preferences.Accounts, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
|
|
|
|
Collectibles: m.toProfileShowcaseCollectibleProto(preferences.Collectibles, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
|
|
|
|
VerifiedTokens: m.toProfileShowcaseVerifiedTokensProto(preferences.VerifiedTokens, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
|
|
|
|
UnverifiedTokens: m.toProfileShowcaseUnverifiedTokensProto(preferences.UnverifiedTokens, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
|
2024-02-26 13:53:40 +00:00
|
|
|
SocialLinks: m.toProfileShowcaseSocialLinksProto(preferences.SocialLinks, identity.ProfileShowcaseVisibilityIDVerifiedContacts),
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mutualContacts := []*Contact{}
|
|
|
|
iDVerifiedContacts := []*Contact{}
|
|
|
|
|
|
|
|
m.allContacts.Range(func(_ string, contact *Contact) (shouldContinue bool) {
|
|
|
|
if contact.mutual() {
|
|
|
|
mutualContacts = append(mutualContacts, contact)
|
|
|
|
if contact.IsVerified() {
|
|
|
|
iDVerifiedContacts = append(iDVerifiedContacts, contact)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
forContactsEncrypted, err := m.EncryptProfileShowcaseEntriesWithContactPubKeys(forContacts, mutualContacts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
forIDVerifiedContactsEncrypted, err := m.EncryptProfileShowcaseEntriesWithContactPubKeys(forIDVerifiedContacts, iDVerifiedContacts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &protobuf.ProfileShowcase{
|
|
|
|
ForEveryone: forEveryone,
|
|
|
|
ForContacts: forContactsEncrypted,
|
|
|
|
ForIdVerifiedContacts: forIDVerifiedContactsEncrypted,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
func (m *Messenger) BuildProfileShowcaseFromIdentity(state *ReceivedMessageState, message *protobuf.ProfileShowcase) error {
|
2023-11-09 18:59:01 +00:00
|
|
|
senderPubKey := state.CurrentMessageState.PublicKey
|
|
|
|
contactID := state.CurrentMessageState.Contact.ID
|
|
|
|
|
2024-02-15 19:13:12 +00:00
|
|
|
communities := []*identity.ProfileShowcaseCommunity{}
|
|
|
|
accounts := []*identity.ProfileShowcaseAccount{}
|
|
|
|
collectibles := []*identity.ProfileShowcaseCollectible{}
|
|
|
|
verifiedTokens := []*identity.ProfileShowcaseVerifiedToken{}
|
|
|
|
unverifiedTokens := []*identity.ProfileShowcaseUnverifiedToken{}
|
2024-02-26 13:53:40 +00:00
|
|
|
socialLinks := []*identity.ProfileShowcaseSocialLink{}
|
2024-02-15 19:13:12 +00:00
|
|
|
|
|
|
|
communities = append(communities, m.fromProfileShowcaseCommunityProto(senderPubKey, message.ForEveryone.Communities)...)
|
|
|
|
accounts = append(accounts, m.fromProfileShowcaseAccountProto(message.ForEveryone.Accounts)...)
|
|
|
|
collectibles = append(collectibles, m.fromProfileShowcaseCollectibleProto(message.ForEveryone.Collectibles)...)
|
|
|
|
verifiedTokens = append(verifiedTokens, m.fromProfileShowcaseVerifiedTokenProto(message.ForEveryone.VerifiedTokens)...)
|
|
|
|
unverifiedTokens = append(unverifiedTokens, m.fromProfileShowcaseUnverifiedTokenProto(message.ForEveryone.UnverifiedTokens)...)
|
2024-02-26 13:53:40 +00:00
|
|
|
socialLinks = append(socialLinks, m.fromProfileShowcaseSocialLinkProto(message.ForEveryone.SocialLinks)...)
|
2024-02-15 19:13:12 +00:00
|
|
|
|
2023-11-09 18:59:01 +00:00
|
|
|
forContacts, err := m.DecryptProfileShowcaseEntriesWithPubKey(senderPubKey, message.ForContacts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if forContacts != nil {
|
2024-02-15 19:13:12 +00:00
|
|
|
communities = append(communities, m.fromProfileShowcaseCommunityProto(senderPubKey, forContacts.Communities)...)
|
|
|
|
accounts = append(accounts, m.fromProfileShowcaseAccountProto(forContacts.Accounts)...)
|
|
|
|
collectibles = append(collectibles, m.fromProfileShowcaseCollectibleProto(forContacts.Collectibles)...)
|
|
|
|
verifiedTokens = append(verifiedTokens, m.fromProfileShowcaseVerifiedTokenProto(forContacts.VerifiedTokens)...)
|
|
|
|
unverifiedTokens = append(unverifiedTokens, m.fromProfileShowcaseUnverifiedTokenProto(forContacts.UnverifiedTokens)...)
|
2024-02-26 13:53:40 +00:00
|
|
|
socialLinks = append(socialLinks, m.fromProfileShowcaseSocialLinkProto(forContacts.SocialLinks)...)
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
forIDVerifiedContacts, err := m.DecryptProfileShowcaseEntriesWithPubKey(senderPubKey, message.ForIdVerifiedContacts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if forIDVerifiedContacts != nil {
|
2024-02-15 19:13:12 +00:00
|
|
|
communities = append(communities, m.fromProfileShowcaseCommunityProto(senderPubKey, forIDVerifiedContacts.Communities)...)
|
|
|
|
accounts = append(accounts, m.fromProfileShowcaseAccountProto(forIDVerifiedContacts.Accounts)...)
|
|
|
|
collectibles = append(collectibles, m.fromProfileShowcaseCollectibleProto(forIDVerifiedContacts.Collectibles)...)
|
|
|
|
verifiedTokens = append(verifiedTokens, m.fromProfileShowcaseVerifiedTokenProto(forIDVerifiedContacts.VerifiedTokens)...)
|
|
|
|
unverifiedTokens = append(unverifiedTokens, m.fromProfileShowcaseUnverifiedTokenProto(forIDVerifiedContacts.UnverifiedTokens)...)
|
2024-02-26 13:53:40 +00:00
|
|
|
socialLinks = append(socialLinks, m.fromProfileShowcaseSocialLinkProto(forIDVerifiedContacts.SocialLinks)...)
|
2023-11-09 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2024-02-26 13:53:40 +00:00
|
|
|
sortProfileEntyByOrder(communities, func(i int) int { return communities[i].Order })
|
|
|
|
sortProfileEntyByOrder(accounts, func(i int) int { return accounts[i].Order })
|
|
|
|
sortProfileEntyByOrder(collectibles, func(i int) int { return collectibles[i].Order })
|
|
|
|
sortProfileEntyByOrder(verifiedTokens, func(i int) int { return verifiedTokens[i].Order })
|
|
|
|
sortProfileEntyByOrder(unverifiedTokens, func(i int) int { return unverifiedTokens[i].Order })
|
|
|
|
sortProfileEntyByOrder(socialLinks, func(i int) int { return socialLinks[i].Order })
|
|
|
|
|
|
|
|
newShowcase := &identity.ProfileShowcase{
|
|
|
|
ContactID: contactID,
|
|
|
|
Communities: communities,
|
|
|
|
Accounts: accounts,
|
|
|
|
Collectibles: collectibles,
|
|
|
|
VerifiedTokens: verifiedTokens,
|
|
|
|
UnverifiedTokens: unverifiedTokens,
|
|
|
|
SocialLinks: socialLinks,
|
|
|
|
}
|
2023-11-09 18:59:01 +00:00
|
|
|
|
|
|
|
oldShowcase, err := m.persistence.GetProfileShowcaseForContact(contactID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if reflect.DeepEqual(newShowcase, oldShowcase) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.ClearProfileShowcaseForContact(contactID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.persistence.SaveProfileShowcaseForContact(newShowcase)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
state.Response.AddProfileShowcase(newShowcase)
|
|
|
|
return nil
|
2023-10-24 10:43:18 +00:00
|
|
|
}
|
2024-02-07 09:30:56 +00:00
|
|
|
|
|
|
|
func (m *Messenger) UpdateProfileShowcaseWalletAccount(account *accounts.Account) error {
|
|
|
|
profileAccount, err := m.persistence.GetProfileShowcaseAccountPreference(account.Address.Hex())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if profileAccount == nil {
|
|
|
|
// No corresponding profile entry, exit
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
profileAccount.Name = account.Name
|
|
|
|
profileAccount.ColorID = string(account.ColorID)
|
|
|
|
profileAccount.Emoji = account.Emoji
|
|
|
|
|
|
|
|
err = m.persistence.SaveProfileShowcaseAccountPreference(profileAccount)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return m.DispatchProfileShowcase()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DeleteProfileShowcaseWalletAccount(account *accounts.Account) error {
|
2024-02-09 09:37:54 +00:00
|
|
|
deleted, err := m.persistence.DeleteProfileShowcaseAccountPreference(account.Address.Hex())
|
2024-02-07 09:30:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-09 09:37:54 +00:00
|
|
|
if deleted {
|
|
|
|
return m.DispatchProfileShowcase()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) DeleteProfileShowcaseCommunity(community *communities.Community) error {
|
|
|
|
deleted, err := m.persistence.DeleteProfileShowcaseCommunityPreference(community.IDString())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if deleted {
|
|
|
|
return m.DispatchProfileShowcase()
|
|
|
|
}
|
|
|
|
return nil
|
2024-02-07 09:30:56 +00:00
|
|
|
}
|
2024-02-17 18:07:20 +00:00
|
|
|
|
2024-02-26 13:53:40 +00:00
|
|
|
func (m *Messenger) saveProfileShowcasePreferencesProto(p *protobuf.SyncProfileShowcasePreferences, shouldSync bool) (*identity.ProfileShowcasePreferences, error) {
|
2024-02-28 11:36:13 +00:00
|
|
|
if p == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2024-02-17 18:07:20 +00:00
|
|
|
preferences := FromProfileShowcasePreferencesProto(p)
|
2024-02-26 13:53:40 +00:00
|
|
|
return preferences, m.setProfileShowcasePreferences(preferences, shouldSync)
|
2024-02-17 18:07:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) syncProfileShowcasePreferences(ctx context.Context, rawMessageHandler RawMessageHandler) error {
|
|
|
|
preferences, err := m.GetProfileShowcasePreferences()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
syncMessage := ToProfileShowcasePreferencesProto(preferences)
|
|
|
|
encodedMessage, err := proto.Marshal(syncMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, chat := m.getLastClockWithRelatedChat()
|
|
|
|
rawMessage := common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_SYNC_PROFILE_SHOWCASE_PREFERENCES,
|
|
|
|
ResendAutomatically: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = rawMessageHandler(ctx, rawMessage)
|
|
|
|
return err
|
|
|
|
}
|