Skip to content

Commit a76e46e

Browse files
authored
Merge pull request #16223 from gluk256/300-msg-serialiation
whisper: topics replaced by bloom filters in mailserver communication
2 parents 3ca3fff + ee75a90 commit a76e46e

File tree

9 files changed

+100
-68
lines changed

9 files changed

+100
-68
lines changed

cmd/wnode/main.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package main
2222
import (
2323
"bufio"
2424
"crypto/ecdsa"
25+
crand "crypto/rand"
2526
"crypto/sha512"
2627
"encoding/binary"
2728
"encoding/hex"
@@ -48,13 +49,15 @@ import (
4849
)
4950

5051
const quitCommand = "~Q"
52+
const entropySize = 32
5153

5254
// singletons
5355
var (
5456
server *p2p.Server
5557
shh *whisper.Whisper
5658
done chan struct{}
5759
mailServer mailserver.WMailServer
60+
entropy [entropySize]byte
5861

5962
input = bufio.NewReader(os.Stdin)
6063
)
@@ -274,6 +277,11 @@ func initialize() {
274277
TrustedNodes: peers,
275278
},
276279
}
280+
281+
_, err = crand.Read(entropy[:])
282+
if err != nil {
283+
utils.Fatalf("crypto/rand failed: %s", err)
284+
}
277285
}
278286

279287
func startServer() {
@@ -614,10 +622,10 @@ func writeMessageToFile(dir string, msg *whisper.ReceivedMessage) {
614622
}
615623

616624
func requestExpiredMessagesLoop() {
617-
var key, peerID []byte
625+
var key, peerID, bloom []byte
618626
var timeLow, timeUpp uint32
619627
var t string
620-
var xt, empty whisper.TopicType
628+
var xt whisper.TopicType
621629

622630
keyID, err := shh.AddSymKeyFromPassword(msPassword)
623631
if err != nil {
@@ -640,18 +648,19 @@ func requestExpiredMessagesLoop() {
640648
utils.Fatalf("Failed to parse the topic: %s", err)
641649
}
642650
xt = whisper.BytesToTopic(x)
651+
bloom = whisper.TopicToBloom(xt)
652+
obfuscateBloom(bloom)
653+
} else {
654+
bloom = whisper.MakeFullNodeBloom()
643655
}
644656
if timeUpp == 0 {
645657
timeUpp = 0xFFFFFFFF
646658
}
647659

648-
data := make([]byte, 8+whisper.TopicLength)
660+
data := make([]byte, 8, 8+whisper.BloomFilterSize)
649661
binary.BigEndian.PutUint32(data, timeLow)
650662
binary.BigEndian.PutUint32(data[4:], timeUpp)
651-
copy(data[8:], xt[:])
652-
if xt == empty {
653-
data = data[:8]
654-
}
663+
data = append(data, bloom...)
655664

656665
var params whisper.MessageParams
657666
params.PoW = *argServerPoW
@@ -685,3 +694,20 @@ func extractIDFromEnode(s string) []byte {
685694
}
686695
return n.ID[:]
687696
}
697+
698+
// obfuscateBloom adds 16 random bits to the the bloom
699+
// filter, in order to obfuscate the containing topics.
700+
// it does so deterministically within every session.
701+
// despite additional bits, it will match on average
702+
// 32000 times less messages than full node's bloom filter.
703+
func obfuscateBloom(bloom []byte) {
704+
const half = entropySize / 2
705+
for i := 0; i < half; i++ {
706+
x := int(entropy[i])
707+
if entropy[half+i] < 128 {
708+
x += 256
709+
}
710+
711+
bloom[x/8] = 1 << uint(x%8) // set the bit number X
712+
}
713+
}

whisper/mailserver/mailserver.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,16 @@ func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope)
107107
return
108108
}
109109

110-
ok, lower, upper, topic := s.validateRequest(peer.ID(), request)
110+
ok, lower, upper, bloom := s.validateRequest(peer.ID(), request)
111111
if ok {
112-
s.processRequest(peer, lower, upper, topic)
112+
s.processRequest(peer, lower, upper, bloom)
113113
}
114114
}
115115

116-
func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, topic whisper.TopicType) []*whisper.Envelope {
116+
func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) []*whisper.Envelope {
117117
ret := make([]*whisper.Envelope, 0)
118118
var err error
119119
var zero common.Hash
120-
var empty whisper.TopicType
121120
kl := NewDbKey(lower, zero)
122121
ku := NewDbKey(upper, zero)
123122
i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil)
@@ -130,7 +129,7 @@ func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, to
130129
log.Error(fmt.Sprintf("RLP decoding failed: %s", err))
131130
}
132131

133-
if topic == empty || envelope.Topic == topic {
132+
if whisper.BloomFilterMatch(bloom, envelope.Bloom()) {
134133
if peer == nil {
135134
// used for test purposes
136135
ret = append(ret, &envelope)
@@ -152,22 +151,16 @@ func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, to
152151
return ret
153152
}
154153

155-
func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) (bool, uint32, uint32, whisper.TopicType) {
156-
var topic whisper.TopicType
154+
func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) (bool, uint32, uint32, []byte) {
157155
if s.pow > 0.0 && request.PoW() < s.pow {
158-
return false, 0, 0, topic
156+
return false, 0, 0, nil
159157
}
160158

161159
f := whisper.Filter{KeySym: s.key}
162160
decrypted := request.Open(&f)
163161
if decrypted == nil {
164162
log.Warn(fmt.Sprintf("Failed to decrypt p2p request"))
165-
return false, 0, 0, topic
166-
}
167-
168-
if len(decrypted.Payload) < 8 {
169-
log.Warn(fmt.Sprintf("Undersized p2p request"))
170-
return false, 0, 0, topic
163+
return false, 0, 0, nil
171164
}
172165

173166
src := crypto.FromECDSAPub(decrypted.Src)
@@ -179,15 +172,24 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope)
179172
// if !bytes.Equal(peerID, src) {
180173
if src == nil {
181174
log.Warn(fmt.Sprintf("Wrong signature of p2p request"))
182-
return false, 0, 0, topic
175+
return false, 0, 0, nil
183176
}
184177

185-
lower := binary.BigEndian.Uint32(decrypted.Payload[:4])
186-
upper := binary.BigEndian.Uint32(decrypted.Payload[4:8])
187-
188-
if len(decrypted.Payload) >= 8+whisper.TopicLength {
189-
topic = whisper.BytesToTopic(decrypted.Payload[8:])
178+
var bloom []byte
179+
payloadSize := len(decrypted.Payload)
180+
if payloadSize < 8 {
181+
log.Warn(fmt.Sprintf("Undersized p2p request"))
182+
return false, 0, 0, nil
183+
} else if payloadSize == 8 {
184+
bloom = whisper.MakeFullNodeBloom()
185+
} else if payloadSize < 8+whisper.BloomFilterSize {
186+
log.Warn(fmt.Sprintf("Undersized bloom filter in p2p request"))
187+
return false, 0, 0, nil
188+
} else {
189+
bloom = decrypted.Payload[8 : 8+whisper.BloomFilterSize]
190190
}
191191

192-
return true, lower, upper, topic
192+
lower := binary.BigEndian.Uint32(decrypted.Payload[:4])
193+
upper := binary.BigEndian.Uint32(decrypted.Payload[4:8])
194+
return true, lower, upper, bloom
193195
}

whisper/mailserver/server_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package mailserver
1818

1919
import (
20+
"bytes"
2021
"crypto/ecdsa"
2122
"encoding/binary"
2223
"io/ioutil"
@@ -61,7 +62,7 @@ func generateEnvelope(t *testing.T) *whisper.Envelope {
6162
h := crypto.Keccak256Hash([]byte("test sample data"))
6263
params := &whisper.MessageParams{
6364
KeySym: h[:],
64-
Topic: whisper.TopicType{},
65+
Topic: whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
6566
Payload: []byte("test payload"),
6667
PoW: powRequirement,
6768
WorkTime: 2,
@@ -121,6 +122,7 @@ func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) {
121122
upp: birth + 1,
122123
key: testPeerID,
123124
}
125+
124126
singleRequest(t, server, env, p, true)
125127

126128
p.low, p.upp = birth+1, 0xffffffff
@@ -131,14 +133,14 @@ func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) {
131133

132134
p.low = birth - 1
133135
p.upp = birth + 1
134-
p.topic[0]++
136+
p.topic[0] = 0xFF
135137
singleRequest(t, server, env, p, false)
136138
}
137139

138140
func singleRequest(t *testing.T, server *WMailServer, env *whisper.Envelope, p *ServerTestParams, expect bool) {
139141
request := createRequest(t, p)
140142
src := crypto.FromECDSAPub(&p.key.PublicKey)
141-
ok, lower, upper, topic := server.validateRequest(src, request)
143+
ok, lower, upper, bloom := server.validateRequest(src, request)
142144
if !ok {
143145
t.Fatalf("request validation failed, seed: %d.", seed)
144146
}
@@ -148,12 +150,13 @@ func singleRequest(t *testing.T, server *WMailServer, env *whisper.Envelope, p *
148150
if upper != p.upp {
149151
t.Fatalf("request validation failed (upper bound), seed: %d.", seed)
150152
}
151-
if topic != p.topic {
153+
expectedBloom := whisper.TopicToBloom(p.topic)
154+
if !bytes.Equal(bloom, expectedBloom) {
152155
t.Fatalf("request validation failed (topic), seed: %d.", seed)
153156
}
154157

155158
var exist bool
156-
mail := server.processRequest(nil, p.low, p.upp, p.topic)
159+
mail := server.processRequest(nil, p.low, p.upp, bloom)
157160
for _, msg := range mail {
158161
if msg.Hash() == env.Hash() {
159162
exist = true
@@ -166,18 +169,19 @@ func singleRequest(t *testing.T, server *WMailServer, env *whisper.Envelope, p *
166169
}
167170

168171
src[0]++
169-
ok, lower, upper, topic = server.validateRequest(src, request)
172+
ok, lower, upper, bloom = server.validateRequest(src, request)
170173
if !ok {
171174
// request should be valid regardless of signature
172175
t.Fatalf("request validation false negative, seed: %d (lower: %d, upper: %d).", seed, lower, upper)
173176
}
174177
}
175178

176179
func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
177-
data := make([]byte, 8+whisper.TopicLength)
180+
bloom := whisper.TopicToBloom(p.topic)
181+
data := make([]byte, 8)
178182
binary.BigEndian.PutUint32(data, p.low)
179183
binary.BigEndian.PutUint32(data[4:], p.upp)
180-
copy(data[8:], p.topic[:])
184+
data = append(data, bloom...)
181185

182186
key, err := shh.GetSymKey(keyID)
183187
if err != nil {

whisper/whisperv6/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const (
6060
aesKeyLength = 32 // in bytes
6161
aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize()
6262
keyIDSize = 32 // in bytes
63-
bloomFilterSize = 64 // in bytes
63+
BloomFilterSize = 64 // in bytes
6464
flagsLength = 1
6565

6666
EnvelopeHeaderLength = 20

whisper/whisperv6/envelope.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (e *Envelope) Bloom() []byte {
249249

250250
// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes)
251251
func TopicToBloom(topic TopicType) []byte {
252-
b := make([]byte, bloomFilterSize)
252+
b := make([]byte, BloomFilterSize)
253253
var index [3]int
254254
for j := 0; j < 3; j++ {
255255
index[j] = int(topic[j])

whisper/whisperv6/peer.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
5656
powRequirement: 0.0,
5757
known: set.New(),
5858
quit: make(chan struct{}),
59-
bloomFilter: makeFullNodeBloom(),
59+
bloomFilter: MakeFullNodeBloom(),
6060
fullNode: true,
6161
}
6262
}
@@ -120,7 +120,7 @@ func (peer *Peer) handshake() error {
120120
err = s.Decode(&bloom)
121121
if err == nil {
122122
sz := len(bloom)
123-
if sz != bloomFilterSize && sz != 0 {
123+
if sz != BloomFilterSize && sz != 0 {
124124
return fmt.Errorf("peer [%x] sent bad status message: wrong bloom filter size %d", peer.ID(), sz)
125125
}
126126
peer.setBloomFilter(bloom)
@@ -229,7 +229,7 @@ func (peer *Peer) notifyAboutBloomFilterChange(bloom []byte) error {
229229
func (peer *Peer) bloomMatch(env *Envelope) bool {
230230
peer.bloomMu.Lock()
231231
defer peer.bloomMu.Unlock()
232-
return peer.fullNode || bloomFilterMatch(peer.bloomFilter, env.Bloom())
232+
return peer.fullNode || BloomFilterMatch(peer.bloomFilter, env.Bloom())
233233
}
234234

235235
func (peer *Peer) setBloomFilter(bloom []byte) {
@@ -238,13 +238,13 @@ func (peer *Peer) setBloomFilter(bloom []byte) {
238238
peer.bloomFilter = bloom
239239
peer.fullNode = isFullNode(bloom)
240240
if peer.fullNode && peer.bloomFilter == nil {
241-
peer.bloomFilter = makeFullNodeBloom()
241+
peer.bloomFilter = MakeFullNodeBloom()
242242
}
243243
}
244244

245-
func makeFullNodeBloom() []byte {
246-
bloom := make([]byte, bloomFilterSize)
247-
for i := 0; i < bloomFilterSize; i++ {
245+
func MakeFullNodeBloom() []byte {
246+
bloom := make([]byte, BloomFilterSize)
247+
for i := 0; i < BloomFilterSize; i++ {
248248
bloom[i] = 0xFF
249249
}
250250
return bloom

whisper/whisperv6/peer_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func resetParams(t *testing.T) {
152152
}
153153

154154
func initBloom(t *testing.T) {
155-
masterBloomFilter = make([]byte, bloomFilterSize)
155+
masterBloomFilter = make([]byte, BloomFilterSize)
156156
_, err := mrand.Read(masterBloomFilter)
157157
if err != nil {
158158
t.Fatalf("rand failed: %s.", err)
@@ -164,7 +164,7 @@ func initBloom(t *testing.T) {
164164
masterBloomFilter[i] = 0xFF
165165
}
166166

167-
if !bloomFilterMatch(masterBloomFilter, msgBloom) {
167+
if !BloomFilterMatch(masterBloomFilter, msgBloom) {
168168
t.Fatalf("bloom mismatch on initBloom.")
169169
}
170170
}
@@ -178,7 +178,7 @@ func initialize(t *testing.T) {
178178

179179
for i := 0; i < NumNodes; i++ {
180180
var node TestNode
181-
b := make([]byte, bloomFilterSize)
181+
b := make([]byte, BloomFilterSize)
182182
copy(b, masterBloomFilter)
183183
node.shh = New(&DefaultConfig)
184184
node.shh.SetMinimumPoW(masterPow)

0 commit comments

Comments
 (0)