whisper: clean up and integrate topics

This commit is contained in:
Péter Szilágyi 2015-04-13 12:16:51 +03:00
parent 7b501906db
commit 9a53390f49
7 changed files with 85 additions and 43 deletions

View File

@ -19,7 +19,7 @@ import (
type Envelope struct { type Envelope struct {
Expiry uint32 // Whisper protocol specifies int32, really should be int64 Expiry uint32 // Whisper protocol specifies int32, really should be int64
TTL uint32 // ^^^^^^ TTL uint32 // ^^^^^^
Topics [][]byte Topics []Topic
Data []byte Data []byte
Nonce uint32 Nonce uint32
@ -28,7 +28,7 @@ type Envelope struct {
// NewEnvelope wraps a Whisper message with expiration and destination data // NewEnvelope wraps a Whisper message with expiration and destination data
// included into an envelope for network forwarding. // included into an envelope for network forwarding.
func NewEnvelope(ttl time.Duration, topics [][]byte, msg *Message) *Envelope { func NewEnvelope(ttl time.Duration, topics []Topic, msg *Message) *Envelope {
return &Envelope{ return &Envelope{
Expiry: uint32(time.Now().Add(ttl).Unix()), Expiry: uint32(time.Now().Add(ttl).Unix()),
TTL: uint32(ttl.Seconds()), TTL: uint32(ttl.Seconds()),

View File

@ -5,6 +5,6 @@ import "crypto/ecdsa"
type Filter struct { type Filter struct {
To *ecdsa.PublicKey To *ecdsa.PublicKey
From *ecdsa.PublicKey From *ecdsa.PublicKey
Topics [][]byte Topics []Topic
Fn func(*Message) Fn func(*Message)
} }

View File

@ -75,8 +75,13 @@ func (self *Message) Wrap(pow time.Duration, options Options) (*Envelope, error)
return nil, err return nil, err
} }
} }
// Convert the user topic into whisper ones
topics := make([]Topic, len(options.Topics))
for i, topic := range options.Topics {
topics[i] = NewTopic(topic)
}
// Wrap the processed message, seal it and return // Wrap the processed message, seal it and return
envelope := NewEnvelope(options.TTL, options.Topics, self) envelope := NewEnvelope(options.TTL, topics, self)
envelope.Seal(pow) envelope.Seal(pow)
return envelope, nil return envelope, nil

35
whisper/topic.go Normal file
View File

@ -0,0 +1,35 @@
// Contains the Whisper protocol Topic element. For formal details please see
// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#topics.
package whisper
import "github.com/ethereum/go-ethereum/crypto"
// Topic represents a cryptographically secure, probabilistic partial
// classifications of a message, determined as the first (left) 4 bytes of the
// SHA3 hash of some arbitrary data given by the original author of the message.
type Topic [4]byte
// NewTopic creates a topic from the 4 byte prefix of the SHA3 hash of the data.
func NewTopic(data []byte) Topic {
prefix := [4]byte{}
copy(prefix[:], crypto.Sha3(data)[:4])
return Topic(prefix)
}
// String converts a topic byte array to a string representation.
func (self *Topic) String() string {
return string(self[:])
}
// TopicSet represents a hash set to check if a topic exists or not.
type TopicSet map[string]struct{}
// NewTopicSet creates a topic hash set from a slice of topics.
func NewTopicSet(topics []Topic) TopicSet {
set := make(map[string]struct{})
for _, topic := range topics {
set[topic.String()] = struct{}{}
}
return TopicSet(set)
}

38
whisper/topic_test.go Normal file
View File

@ -0,0 +1,38 @@
package whisper
import (
"bytes"
"testing"
)
var topicCreationTests = []struct {
data []byte
hash [4]byte
}{
{hash: [4]byte{0xc5, 0xd2, 0x46, 0x01}, data: nil},
{hash: [4]byte{0xc5, 0xd2, 0x46, 0x01}, data: []byte{}},
{hash: [4]byte{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte("test name")},
}
func TestTopicCreation(t *testing.T) {
for i, tt := range topicCreationTests {
topic := NewTopic(tt.data)
if bytes.Compare(topic[:], tt.hash[:]) != 0 {
t.Errorf("test %d: hash mismatch: have %v, want %v.", i, topic, tt.hash)
}
}
}
func TestTopicSetCreation(t *testing.T) {
topics := make([]Topic, len(topicCreationTests))
for i, tt := range topicCreationTests {
topics[i] = NewTopic(tt.data)
}
set := NewTopicSet(topics)
for i, tt := range topicCreationTests {
topic := NewTopic(tt.data)
if _, ok := set[topic.String()]; !ok {
t.Errorf("topic %d: not found in set", i)
}
}
}

View File

@ -1,36 +0,0 @@
package whisper
import "github.com/ethereum/go-ethereum/crypto"
func hashTopic(topic []byte) []byte {
return crypto.Sha3(topic)[:4]
}
// NOTE this isn't DRY, but I don't want to iterate twice.
// Returns a formatted topics byte slice.
// data: unformatted data (e.g., no hashes needed)
func Topics(data [][]byte) [][]byte {
d := make([][]byte, len(data))
for i, byts := range data {
d[i] = hashTopic(byts)
}
return d
}
func TopicsFromString(data ...string) [][]byte {
d := make([][]byte, len(data))
for i, str := range data {
d[i] = hashTopic([]byte(str))
}
return d
}
func bytesToMap(s [][]byte) map[string]struct{} {
m := make(map[string]struct{})
for _, topic := range s {
m[string(topic)] = struct{}{}
}
return m
}

View File

@ -119,7 +119,7 @@ func (self *Whisper) Watch(opts Filter) int {
return self.filters.Install(filter.Generic{ return self.filters.Install(filter.Generic{
Str1: string(crypto.FromECDSAPub(opts.To)), Str1: string(crypto.FromECDSAPub(opts.To)),
Str2: string(crypto.FromECDSAPub(opts.From)), Str2: string(crypto.FromECDSAPub(opts.From)),
Data: bytesToMap(opts.Topics), Data: NewTopicSet(opts.Topics),
Fn: func(data interface{}) { Fn: func(data interface{}) {
opts.Fn(data.(*Message)) opts.Fn(data.(*Message))
}, },
@ -272,9 +272,9 @@ func (self *Whisper) Protocol() p2p.Protocol {
return self.protocol return self.protocol
} }
func createFilter(message *Message, topics [][]byte, key *ecdsa.PrivateKey) filter.Filter { func createFilter(message *Message, topics []Topic, key *ecdsa.PrivateKey) filter.Filter {
return filter.Generic{ return filter.Generic{
Str1: string(crypto.FromECDSAPub(&key.PublicKey)), Str2: string(crypto.FromECDSAPub(message.Recover())), Str1: string(crypto.FromECDSAPub(&key.PublicKey)), Str2: string(crypto.FromECDSAPub(message.Recover())),
Data: bytesToMap(topics), Data: NewTopicSet(topics),
} }
} }