diff --git a/whisper/envelope.go b/whisper/envelope.go
index c51c6e60045896f8c5d89080a949a293f23966bd..93e3ea1a3f051c10302c21f9f0a6d5eae4de00d3 100644
--- a/whisper/envelope.go
+++ b/whisper/envelope.go
@@ -19,7 +19,7 @@ import (
 type Envelope struct {
 	Expiry uint32 // Whisper protocol specifies int32, really should be int64
 	TTL    uint32 // ^^^^^^
-	Topics [][]byte
+	Topics []Topic
 	Data   []byte
 	Nonce  uint32
 
@@ -28,7 +28,7 @@ type Envelope struct {
 
 // NewEnvelope wraps a Whisper message with expiration and destination data
 // 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{
 		Expiry: uint32(time.Now().Add(ttl).Unix()),
 		TTL:    uint32(ttl.Seconds()),
diff --git a/whisper/filter.go b/whisper/filter.go
index b33f2c1a259ca475e3cec773fee7b3a8979c6920..7258de3e79821c76b6c78795646795702b3e0c47 100644
--- a/whisper/filter.go
+++ b/whisper/filter.go
@@ -5,6 +5,6 @@ import "crypto/ecdsa"
 type Filter struct {
 	To     *ecdsa.PublicKey
 	From   *ecdsa.PublicKey
-	Topics [][]byte
+	Topics []Topic
 	Fn     func(*Message)
 }
diff --git a/whisper/message.go b/whisper/message.go
index 457cf6def525d61f53a841cf96f5b30c0d233d59..ad31aa592704f395c12268f629526f1555d21add 100644
--- a/whisper/message.go
+++ b/whisper/message.go
@@ -75,8 +75,13 @@ func (self *Message) Wrap(pow time.Duration, options Options) (*Envelope, error)
 			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
-	envelope := NewEnvelope(options.TTL, options.Topics, self)
+	envelope := NewEnvelope(options.TTL, topics, self)
 	envelope.Seal(pow)
 
 	return envelope, nil
diff --git a/whisper/topic.go b/whisper/topic.go
new file mode 100644
index 0000000000000000000000000000000000000000..10069c90208cca93f550207e06006e27b63aa4c5
--- /dev/null
+++ b/whisper/topic.go
@@ -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)
+}
diff --git a/whisper/topic_test.go b/whisper/topic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4626e2ae5543ff7a49185cbd3459448704a4a064
--- /dev/null
+++ b/whisper/topic_test.go
@@ -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)
+		}
+	}
+}
diff --git a/whisper/util.go b/whisper/util.go
deleted file mode 100644
index 7a222395fe9ee1bafa4eaa777f9e06c8799912b6..0000000000000000000000000000000000000000
--- a/whisper/util.go
+++ /dev/null
@@ -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
-}
diff --git a/whisper/whisper.go b/whisper/whisper.go
index ad29fe16a5fc5593aadddb1d6f9a9e1ce0074ada..d9affe09b18f667e4f8f82ece23341ffbef2c714 100644
--- a/whisper/whisper.go
+++ b/whisper/whisper.go
@@ -119,7 +119,7 @@ func (self *Whisper) Watch(opts Filter) int {
 	return self.filters.Install(filter.Generic{
 		Str1: string(crypto.FromECDSAPub(opts.To)),
 		Str2: string(crypto.FromECDSAPub(opts.From)),
-		Data: bytesToMap(opts.Topics),
+		Data: NewTopicSet(opts.Topics),
 		Fn: func(data interface{}) {
 			opts.Fn(data.(*Message))
 		},
@@ -272,9 +272,9 @@ func (self *Whisper) Protocol() p2p.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{
 		Str1: string(crypto.FromECDSAPub(&key.PublicKey)), Str2: string(crypto.FromECDSAPub(message.Recover())),
-		Data: bytesToMap(topics),
+		Data: NewTopicSet(topics),
 	}
 }