github.com/status-im/status-go@v1.1.0/protocol/communities/community_bloom_filter.go (about)

     1  package communities
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"encoding/binary"
     6  	"errors"
     7  	"math/bits"
     8  
     9  	"github.com/bits-and-blooms/bloom/v3"
    10  
    11  	"github.com/status-im/status-go/eth-node/crypto"
    12  	"github.com/status-im/status-go/protocol/common"
    13  	"github.com/status-im/status-go/protocol/encryption"
    14  	"github.com/status-im/status-go/protocol/protobuf"
    15  )
    16  
    17  func generateBloomFiltersForChannels(description *protobuf.CommunityDescription, privateKey *ecdsa.PrivateKey) error {
    18  	for channelID, channel := range description.Chats {
    19  		if !channelEncrypted(ChatID(description.ID, channelID), description.TokenPermissions) {
    20  			continue
    21  		}
    22  
    23  		filter, err := generateBloomFilter(channel.Members, privateKey, channelID, description.Clock)
    24  		if err != nil {
    25  			return err
    26  		}
    27  
    28  		marshaledFilter, err := filter.MarshalBinary()
    29  		if err != nil {
    30  			return err
    31  		}
    32  
    33  		channel.MembersList = &protobuf.CommunityBloomFilter{
    34  			Data: marshaledFilter,
    35  			M:    uint64(filter.Cap()),
    36  			K:    uint64(filter.K()),
    37  		}
    38  	}
    39  
    40  	return nil
    41  }
    42  
    43  func nextPowerOfTwo(x int) uint {
    44  	return 1 << bits.Len(uint(x))
    45  }
    46  
    47  func max(x, y uint) uint {
    48  	if x > y {
    49  		return x
    50  	}
    51  	return y
    52  }
    53  
    54  func generateBloomFilter(members map[string]*protobuf.CommunityMember, privateKey *ecdsa.PrivateKey, channelID string, clock uint64) (*bloom.BloomFilter, error) {
    55  	membersCount := len(members)
    56  	if membersCount == 0 {
    57  		return nil, errors.New("invalid members count")
    58  	}
    59  
    60  	const falsePositiveRate = 0.001
    61  	numberOfItems := max(128, nextPowerOfTwo(membersCount)) // This makes it difficult to guess the exact number of members, even with knowledge of filter size and parameters.
    62  	filter := bloom.NewWithEstimates(numberOfItems, falsePositiveRate)
    63  
    64  	for pk := range members {
    65  		publicKey, err := common.HexToPubkey(pk)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  
    70  		value, err := bloomFilterValue(privateKey, publicKey, channelID, clock)
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  
    75  		filter.Add(value)
    76  	}
    77  
    78  	return filter, nil
    79  }
    80  
    81  func verifyMembershipWithBloomFilter(membersList *protobuf.CommunityBloomFilter, privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, channelID string, clock uint64) (bool, error) {
    82  	filter := bloom.New(uint(membersList.M), uint(membersList.K))
    83  	err := filter.UnmarshalBinary(membersList.Data)
    84  	if err != nil {
    85  		return false, err
    86  	}
    87  
    88  	value, err := bloomFilterValue(privateKey, publicKey, channelID, clock)
    89  	if err != nil {
    90  		return false, err
    91  	}
    92  
    93  	return filter.Test(value), nil
    94  }
    95  
    96  func bloomFilterValue(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, channelID string, clock uint64) ([]byte, error) {
    97  	sharedSecret, err := encryption.GenerateSharedKey(privateKey, publicKey)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	clockBytes := make([]byte, 8)
   103  	binary.LittleEndian.PutUint64(clockBytes, clock)
   104  
   105  	return crypto.Keccak256(sharedSecret, []byte(channelID), clockBytes), nil
   106  }