From 79789af2e7fce8807d21a8eedbf42d41a7c55848 Mon Sep 17 00:00:00 2001
From: gluk256 <gluk256@users.noreply.github.com>
Date: Sat, 29 Oct 2016 14:11:37 +0200
Subject: whisper: project restructured, version 5 introduced (#3022)

whisper: project restructured, version 5 introduced

This commits adds a draft version of the new shh v5 protocol.
The new version is not on by default, --shh still selects version 2.
---
 cmd/gethrpctest/main.go              |   2 +-
 cmd/utils/flags.go                   |   2 +-
 whisper/api.go                       | 413 --------------------
 whisper/doc.go                       |  32 --
 whisper/envelope.go                  | 147 --------
 whisper/envelope_test.go             | 158 --------
 whisper/filter.go                    | 132 -------
 whisper/filter_test.go               | 215 -----------
 whisper/main.go                      | 106 ------
 whisper/message.go                   | 156 --------
 whisper/message_test.go              | 159 --------
 whisper/peer.go                      | 175 ---------
 whisper/peer_test.go                 | 261 -------------
 whisper/shhapi/api.go                | 504 +++++++++++++++++++++++++
 whisper/shhapi/api_test.go           | 170 +++++++++
 whisper/topic.go                     | 140 -------
 whisper/topic_test.go                | 215 -----------
 whisper/whisper.go                   | 378 -------------------
 whisper/whisper_test.go              | 216 -----------
 whisper/whisperv2/api.go             | 413 ++++++++++++++++++++
 whisper/whisperv2/doc.go             |  32 ++
 whisper/whisperv2/envelope.go        | 147 ++++++++
 whisper/whisperv2/envelope_test.go   | 158 ++++++++
 whisper/whisperv2/filter.go          | 132 +++++++
 whisper/whisperv2/filter_test.go     | 215 +++++++++++
 whisper/whisperv2/main.go            | 106 ++++++
 whisper/whisperv2/message.go         | 156 ++++++++
 whisper/whisperv2/message_test.go    | 159 ++++++++
 whisper/whisperv2/peer.go            | 175 +++++++++
 whisper/whisperv2/peer_test.go       | 261 +++++++++++++
 whisper/whisperv2/topic.go           | 140 +++++++
 whisper/whisperv2/topic_test.go      | 215 +++++++++++
 whisper/whisperv2/whisper.go         | 378 +++++++++++++++++++
 whisper/whisperv2/whisper_test.go    | 216 +++++++++++
 whisper/whisperv5/benchmarks_test.go | 202 ++++++++++
 whisper/whisperv5/doc.go             |  87 +++++
 whisper/whisperv5/envelope.go        | 233 ++++++++++++
 whisper/whisperv5/filter.go          | 197 ++++++++++
 whisper/whisperv5/filter_test.go     | 707 +++++++++++++++++++++++++++++++++++
 whisper/whisperv5/message.go         | 378 +++++++++++++++++++
 whisper/whisperv5/message_test.go    | 306 +++++++++++++++
 whisper/whisperv5/peer.go            | 174 +++++++++
 whisper/whisperv5/peer_test.go       | 307 +++++++++++++++
 whisper/whisperv5/topic.go           |  70 ++++
 whisper/whisperv5/topic_test.go      | 136 +++++++
 whisper/whisperv5/whisper.go         | 585 +++++++++++++++++++++++++++++
 whisper/whisperv5/whisper_test.go    | 377 +++++++++++++++++++
 47 files changed, 7338 insertions(+), 2905 deletions(-)
 delete mode 100644 whisper/api.go
 delete mode 100644 whisper/doc.go
 delete mode 100644 whisper/envelope.go
 delete mode 100644 whisper/envelope_test.go
 delete mode 100644 whisper/filter.go
 delete mode 100644 whisper/filter_test.go
 delete mode 100644 whisper/main.go
 delete mode 100644 whisper/message.go
 delete mode 100644 whisper/message_test.go
 delete mode 100644 whisper/peer.go
 delete mode 100644 whisper/peer_test.go
 create mode 100644 whisper/shhapi/api.go
 create mode 100644 whisper/shhapi/api_test.go
 delete mode 100644 whisper/topic.go
 delete mode 100644 whisper/topic_test.go
 delete mode 100644 whisper/whisper.go
 delete mode 100644 whisper/whisper_test.go
 create mode 100644 whisper/whisperv2/api.go
 create mode 100644 whisper/whisperv2/doc.go
 create mode 100644 whisper/whisperv2/envelope.go
 create mode 100644 whisper/whisperv2/envelope_test.go
 create mode 100644 whisper/whisperv2/filter.go
 create mode 100644 whisper/whisperv2/filter_test.go
 create mode 100644 whisper/whisperv2/main.go
 create mode 100644 whisper/whisperv2/message.go
 create mode 100644 whisper/whisperv2/message_test.go
 create mode 100644 whisper/whisperv2/peer.go
 create mode 100644 whisper/whisperv2/peer_test.go
 create mode 100644 whisper/whisperv2/topic.go
 create mode 100644 whisper/whisperv2/topic_test.go
 create mode 100644 whisper/whisperv2/whisper.go
 create mode 100644 whisper/whisperv2/whisper_test.go
 create mode 100644 whisper/whisperv5/benchmarks_test.go
 create mode 100644 whisper/whisperv5/doc.go
 create mode 100644 whisper/whisperv5/envelope.go
 create mode 100644 whisper/whisperv5/filter.go
 create mode 100644 whisper/whisperv5/filter_test.go
 create mode 100644 whisper/whisperv5/message.go
 create mode 100644 whisper/whisperv5/message_test.go
 create mode 100644 whisper/whisperv5/peer.go
 create mode 100644 whisper/whisperv5/peer_test.go
 create mode 100644 whisper/whisperv5/topic.go
 create mode 100644 whisper/whisperv5/topic_test.go
 create mode 100644 whisper/whisperv5/whisper.go
 create mode 100644 whisper/whisperv5/whisper_test.go

diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go
index d0d6e1618..343aedc6f 100644
--- a/cmd/gethrpctest/main.go
+++ b/cmd/gethrpctest/main.go
@@ -31,7 +31,7 @@ import (
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/tests"
-	"github.com/ethereum/go-ethereum/whisper"
+	whisper "github.com/ethereum/go-ethereum/whisper/whisperv2"
 )
 
 const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 5e2dcf8c9..abfb4d1e7 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -48,7 +48,7 @@ import (
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/pow"
 	"github.com/ethereum/go-ethereum/rpc"
-	"github.com/ethereum/go-ethereum/whisper"
+	whisper "github.com/ethereum/go-ethereum/whisper/whisperv2"
 	"gopkg.in/urfave/cli.v1"
 )
 
diff --git a/whisper/api.go b/whisper/api.go
deleted file mode 100644
index d1f454a1f..000000000
--- a/whisper/api.go
+++ /dev/null
@@ -1,413 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"encoding/json"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/rpc"
-)
-
-// PublicWhisperAPI provides the whisper RPC service.
-type PublicWhisperAPI struct {
-	w *Whisper
-
-	messagesMu sync.RWMutex
-	messages   map[int]*whisperFilter
-}
-
-type whisperOfflineError struct{}
-
-func (e *whisperOfflineError) Error() string {
-	return "whisper is offline"
-}
-
-// whisperOffLineErr is returned when the node doesn't offer the shh service.
-var whisperOffLineErr = new(whisperOfflineError)
-
-// NewPublicWhisperAPI create a new RPC whisper service.
-func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
-	return &PublicWhisperAPI{w: w, messages: make(map[int]*whisperFilter)}
-}
-
-// Version returns the Whisper version this node offers.
-func (s *PublicWhisperAPI) Version() (*rpc.HexNumber, error) {
-	if s.w == nil {
-		return rpc.NewHexNumber(0), whisperOffLineErr
-	}
-	return rpc.NewHexNumber(s.w.Version()), nil
-}
-
-// HasIdentity checks if the the whisper node is configured with the private key
-// of the specified public pair.
-func (s *PublicWhisperAPI) HasIdentity(identity string) (bool, error) {
-	if s.w == nil {
-		return false, whisperOffLineErr
-	}
-	return s.w.HasIdentity(crypto.ToECDSAPub(common.FromHex(identity))), nil
-}
-
-// NewIdentity generates a new cryptographic identity for the client, and injects
-// it into the known identities for message decryption.
-func (s *PublicWhisperAPI) NewIdentity() (string, error) {
-	if s.w == nil {
-		return "", whisperOffLineErr
-	}
-
-	identity := s.w.NewIdentity()
-	return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil
-}
-
-type NewFilterArgs struct {
-	To     string
-	From   string
-	Topics [][][]byte
-}
-
-// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages.
-func (s *PublicWhisperAPI) NewFilter(args NewFilterArgs) (*rpc.HexNumber, error) {
-	if s.w == nil {
-		return nil, whisperOffLineErr
-	}
-
-	var id int
-	filter := Filter{
-		To:     crypto.ToECDSAPub(common.FromHex(args.To)),
-		From:   crypto.ToECDSAPub(common.FromHex(args.From)),
-		Topics: NewFilterTopics(args.Topics...),
-		Fn: func(message *Message) {
-			wmsg := NewWhisperMessage(message)
-			s.messagesMu.RLock() // Only read lock to the filter pool
-			defer s.messagesMu.RUnlock()
-			if s.messages[id] != nil {
-				s.messages[id].insert(wmsg)
-			}
-		},
-	}
-
-	id = s.w.Watch(filter)
-
-	s.messagesMu.Lock()
-	s.messages[id] = newWhisperFilter(id, s.w)
-	s.messagesMu.Unlock()
-
-	return rpc.NewHexNumber(id), nil
-}
-
-// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval.
-func (s *PublicWhisperAPI) GetFilterChanges(filterId rpc.HexNumber) []WhisperMessage {
-	s.messagesMu.RLock()
-	defer s.messagesMu.RUnlock()
-
-	if s.messages[filterId.Int()] != nil {
-		if changes := s.messages[filterId.Int()].retrieve(); changes != nil {
-			return changes
-		}
-	}
-	return returnWhisperMessages(nil)
-}
-
-// UninstallFilter disables and removes an existing filter.
-func (s *PublicWhisperAPI) UninstallFilter(filterId rpc.HexNumber) bool {
-	s.messagesMu.Lock()
-	defer s.messagesMu.Unlock()
-
-	if _, ok := s.messages[filterId.Int()]; ok {
-		delete(s.messages, filterId.Int())
-		return true
-	}
-	return false
-}
-
-// GetMessages retrieves all the known messages that match a specific filter.
-func (s *PublicWhisperAPI) GetMessages(filterId rpc.HexNumber) []WhisperMessage {
-	// Retrieve all the cached messages matching a specific, existing filter
-	s.messagesMu.RLock()
-	defer s.messagesMu.RUnlock()
-
-	var messages []*Message
-	if s.messages[filterId.Int()] != nil {
-		messages = s.messages[filterId.Int()].messages()
-	}
-
-	return returnWhisperMessages(messages)
-}
-
-// returnWhisperMessages converts aNhisper message to a RPC whisper message.
-func returnWhisperMessages(messages []*Message) []WhisperMessage {
-	msgs := make([]WhisperMessage, len(messages))
-	for i, msg := range messages {
-		msgs[i] = NewWhisperMessage(msg)
-	}
-	return msgs
-}
-
-type PostArgs struct {
-	From     string   `json:"from"`
-	To       string   `json:"to"`
-	Topics   [][]byte `json:"topics"`
-	Payload  string   `json:"payload"`
-	Priority int64    `json:"priority"`
-	TTL      int64    `json:"ttl"`
-}
-
-// Post injects a message into the whisper network for distribution.
-func (s *PublicWhisperAPI) Post(args PostArgs) (bool, error) {
-	if s.w == nil {
-		return false, whisperOffLineErr
-	}
-
-	// construct whisper message with transmission options
-	message := NewMessage(common.FromHex(args.Payload))
-	options := Options{
-		To:     crypto.ToECDSAPub(common.FromHex(args.To)),
-		TTL:    time.Duration(args.TTL) * time.Second,
-		Topics: NewTopics(args.Topics...),
-	}
-
-	// set sender identity
-	if len(args.From) > 0 {
-		if key := s.w.GetIdentity(crypto.ToECDSAPub(common.FromHex(args.From))); key != nil {
-			options.From = key
-		} else {
-			return false, fmt.Errorf("unknown identity to send from: %s", args.From)
-		}
-	}
-
-	// Wrap and send the message
-	pow := time.Duration(args.Priority) * time.Millisecond
-	envelope, err := message.Wrap(pow, options)
-	if err != nil {
-		return false, err
-	}
-
-	return true, s.w.Send(envelope)
-}
-
-// WhisperMessage is the RPC representation of a whisper message.
-type WhisperMessage struct {
-	ref *Message
-
-	Payload string `json:"payload"`
-	To      string `json:"to"`
-	From    string `json:"from"`
-	Sent    int64  `json:"sent"`
-	TTL     int64  `json:"ttl"`
-	Hash    string `json:"hash"`
-}
-
-func (args *PostArgs) UnmarshalJSON(data []byte) (err error) {
-	var obj struct {
-		From     string        `json:"from"`
-		To       string        `json:"to"`
-		Topics   []string      `json:"topics"`
-		Payload  string        `json:"payload"`
-		Priority rpc.HexNumber `json:"priority"`
-		TTL      rpc.HexNumber `json:"ttl"`
-	}
-
-	if err := json.Unmarshal(data, &obj); err != nil {
-		return err
-	}
-
-	args.From = obj.From
-	args.To = obj.To
-	args.Payload = obj.Payload
-	args.Priority = obj.Priority.Int64()
-	args.TTL = obj.TTL.Int64()
-
-	// decode topic strings
-	args.Topics = make([][]byte, len(obj.Topics))
-	for i, topic := range obj.Topics {
-		args.Topics[i] = common.FromHex(topic)
-	}
-
-	return nil
-}
-
-// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
-// JSON message blob into a WhisperFilterArgs structure.
-func (args *NewFilterArgs) UnmarshalJSON(b []byte) (err error) {
-	// Unmarshal the JSON message and sanity check
-	var obj struct {
-		To     interface{} `json:"to"`
-		From   interface{} `json:"from"`
-		Topics interface{} `json:"topics"`
-	}
-	if err := json.Unmarshal(b, &obj); err != nil {
-		return err
-	}
-
-	// Retrieve the simple data contents of the filter arguments
-	if obj.To == nil {
-		args.To = ""
-	} else {
-		argstr, ok := obj.To.(string)
-		if !ok {
-			return fmt.Errorf("to is not a string")
-		}
-		args.To = argstr
-	}
-	if obj.From == nil {
-		args.From = ""
-	} else {
-		argstr, ok := obj.From.(string)
-		if !ok {
-			return fmt.Errorf("from is not a string")
-		}
-		args.From = argstr
-	}
-	// Construct the nested topic array
-	if obj.Topics != nil {
-		// Make sure we have an actual topic array
-		list, ok := obj.Topics.([]interface{})
-		if !ok {
-			return fmt.Errorf("topics is not an array")
-		}
-		// Iterate over each topic and handle nil, string or array
-		topics := make([][]string, len(list))
-		for idx, field := range list {
-			switch value := field.(type) {
-			case nil:
-				topics[idx] = []string{}
-
-			case string:
-				topics[idx] = []string{value}
-
-			case []interface{}:
-				topics[idx] = make([]string, len(value))
-				for i, nested := range value {
-					switch value := nested.(type) {
-					case nil:
-						topics[idx][i] = ""
-
-					case string:
-						topics[idx][i] = value
-
-					default:
-						return fmt.Errorf("topic[%d][%d] is not a string", idx, i)
-					}
-				}
-			default:
-				return fmt.Errorf("topic[%d] not a string or array", idx)
-			}
-		}
-
-		topicsDecoded := make([][][]byte, len(topics))
-		for i, condition := range topics {
-			topicsDecoded[i] = make([][]byte, len(condition))
-			for j, topic := range condition {
-				topicsDecoded[i][j] = common.FromHex(topic)
-			}
-		}
-
-		args.Topics = topicsDecoded
-	}
-	return nil
-}
-
-// whisperFilter is the message cache matching a specific filter, accumulating
-// inbound messages until the are requested by the client.
-type whisperFilter struct {
-	id  int      // Filter identifier for old message retrieval
-	ref *Whisper // Whisper reference for old message retrieval
-
-	cache  []WhisperMessage         // Cache of messages not yet polled
-	skip   map[common.Hash]struct{} // List of retrieved messages to avoid duplication
-	update time.Time                // Time of the last message query
-
-	lock sync.RWMutex // Lock protecting the filter internals
-}
-
-// messages retrieves all the cached messages from the entire pool matching the
-// filter, resetting the filter's change buffer.
-func (w *whisperFilter) messages() []*Message {
-	w.lock.Lock()
-	defer w.lock.Unlock()
-
-	w.cache = nil
-	w.update = time.Now()
-
-	w.skip = make(map[common.Hash]struct{})
-	messages := w.ref.Messages(w.id)
-	for _, message := range messages {
-		w.skip[message.Hash] = struct{}{}
-	}
-	return messages
-}
-
-// insert injects a new batch of messages into the filter cache.
-func (w *whisperFilter) insert(messages ...WhisperMessage) {
-	w.lock.Lock()
-	defer w.lock.Unlock()
-
-	for _, message := range messages {
-		if _, ok := w.skip[message.ref.Hash]; !ok {
-			w.cache = append(w.cache, messages...)
-		}
-	}
-}
-
-// retrieve fetches all the cached messages from the filter.
-func (w *whisperFilter) retrieve() (messages []WhisperMessage) {
-	w.lock.Lock()
-	defer w.lock.Unlock()
-
-	messages, w.cache = w.cache, nil
-	w.update = time.Now()
-
-	return
-}
-
-// activity returns the last time instance when client requests were executed on
-// the filter.
-func (w *whisperFilter) activity() time.Time {
-	w.lock.RLock()
-	defer w.lock.RUnlock()
-
-	return w.update
-}
-
-// newWhisperFilter creates a new serialized, poll based whisper topic filter.
-func newWhisperFilter(id int, ref *Whisper) *whisperFilter {
-	return &whisperFilter{
-		id:  id,
-		ref: ref,
-
-		update: time.Now(),
-		skip:   make(map[common.Hash]struct{}),
-	}
-}
-
-// NewWhisperMessage converts an internal message into an API version.
-func NewWhisperMessage(message *Message) WhisperMessage {
-	return WhisperMessage{
-		ref: message,
-
-		Payload: common.ToHex(message.Payload),
-		From:    common.ToHex(crypto.FromECDSAPub(message.Recover())),
-		To:      common.ToHex(crypto.FromECDSAPub(message.To)),
-		Sent:    message.Sent.Unix(),
-		TTL:     int64(message.TTL / time.Second),
-		Hash:    common.ToHex(message.Hash.Bytes()),
-	}
-}
diff --git a/whisper/doc.go b/whisper/doc.go
deleted file mode 100644
index cfb0b5117..000000000
--- a/whisper/doc.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-/*
-Package whisper implements the Whisper PoC-1.
-
-(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec)
-
-Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
-As such it may be likened and compared to both, not dissimilar to the
-matter/energy duality (apologies to physicists for the blatant abuse of a
-fundamental and beautiful natural principle).
-
-Whisper is a pure identity-based messaging system. Whisper provides a low-level
-(non-application-specific) but easily-accessible API without being based upon
-or prejudiced by the low-level hardware attributes and characteristics,
-particularly the notion of singular endpoints.
-*/
-package whisper
diff --git a/whisper/envelope.go b/whisper/envelope.go
deleted file mode 100644
index 97d489b96..000000000
--- a/whisper/envelope.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-// Contains the Whisper protocol Envelope element. For formal details please see
-// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#envelopes.
-
-package whisper
-
-import (
-	"crypto/ecdsa"
-	"encoding/binary"
-	"fmt"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/crypto/ecies"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-// Envelope represents a clear-text data packet to transmit through the Whisper
-// network. Its contents may or may not be encrypted and signed.
-type Envelope struct {
-	Expiry uint32 // Whisper protocol specifies int32, really should be int64
-	TTL    uint32 // ^^^^^^
-	Topics []Topic
-	Data   []byte
-	Nonce  uint32
-
-	hash common.Hash // Cached hash of the envelope to avoid rehashing every time
-}
-
-// NewEnvelope wraps a Whisper message with expiration and destination data
-// included into an envelope for network forwarding.
-func NewEnvelope(ttl time.Duration, topics []Topic, msg *Message) *Envelope {
-	return &Envelope{
-		Expiry: uint32(time.Now().Add(ttl).Unix()),
-		TTL:    uint32(ttl.Seconds()),
-		Topics: topics,
-		Data:   msg.bytes(),
-		Nonce:  0,
-	}
-}
-
-// Seal closes the envelope by spending the requested amount of time as a proof
-// of work on hashing the data.
-func (self *Envelope) Seal(pow time.Duration) {
-	d := make([]byte, 64)
-	copy(d[:32], self.rlpWithoutNonce())
-
-	finish, bestBit := time.Now().Add(pow).UnixNano(), 0
-	for nonce := uint32(0); time.Now().UnixNano() < finish; {
-		for i := 0; i < 1024; i++ {
-			binary.BigEndian.PutUint32(d[60:], nonce)
-
-			firstBit := common.FirstBitSet(common.BigD(crypto.Keccak256(d)))
-			if firstBit > bestBit {
-				self.Nonce, bestBit = nonce, firstBit
-			}
-			nonce++
-		}
-	}
-}
-
-// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
-func (self *Envelope) rlpWithoutNonce() []byte {
-	enc, _ := rlp.EncodeToBytes([]interface{}{self.Expiry, self.TTL, self.Topics, self.Data})
-	return enc
-}
-
-// Open extracts the message contained within a potentially encrypted envelope.
-func (self *Envelope) Open(key *ecdsa.PrivateKey) (msg *Message, err error) {
-	// Split open the payload into a message construct
-	data := self.Data
-
-	message := &Message{
-		Flags: data[0],
-		Sent:  time.Unix(int64(self.Expiry-self.TTL), 0),
-		TTL:   time.Duration(self.TTL) * time.Second,
-		Hash:  self.Hash(),
-	}
-	data = data[1:]
-
-	if message.Flags&signatureFlag == signatureFlag {
-		if len(data) < signatureLength {
-			return nil, fmt.Errorf("unable to open envelope. First bit set but len(data) < len(signature)")
-		}
-		message.Signature, data = data[:signatureLength], data[signatureLength:]
-	}
-	message.Payload = data
-
-	// Decrypt the message, if requested
-	if key == nil {
-		return message, nil
-	}
-	err = message.decrypt(key)
-	switch err {
-	case nil:
-		return message, nil
-
-	case ecies.ErrInvalidPublicKey: // Payload isn't encrypted
-		return message, err
-
-	default:
-		return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
-	}
-}
-
-// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
-func (self *Envelope) Hash() common.Hash {
-	if (self.hash == common.Hash{}) {
-		enc, _ := rlp.EncodeToBytes(self)
-		self.hash = crypto.Keccak256Hash(enc)
-	}
-	return self.hash
-}
-
-// DecodeRLP decodes an Envelope from an RLP data stream.
-func (self *Envelope) DecodeRLP(s *rlp.Stream) error {
-	raw, err := s.Raw()
-	if err != nil {
-		return err
-	}
-	// The decoding of Envelope uses the struct fields but also needs
-	// to compute the hash of the whole RLP-encoded envelope. This
-	// type has the same structure as Envelope but is not an
-	// rlp.Decoder so we can reuse the Envelope struct definition.
-	type rlpenv Envelope
-	if err := rlp.DecodeBytes(raw, (*rlpenv)(self)); err != nil {
-		return err
-	}
-	self.hash = crypto.Keccak256Hash(raw)
-	return nil
-}
diff --git a/whisper/envelope_test.go b/whisper/envelope_test.go
deleted file mode 100644
index 3bfe52737..000000000
--- a/whisper/envelope_test.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"bytes"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/crypto/ecies"
-)
-
-func TestEnvelopeOpen(t *testing.T) {
-	payload := []byte("hello world")
-	message := NewMessage(payload)
-
-	envelope, err := message.Wrap(DefaultPoW, Options{})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	opened, err := envelope.Open(nil)
-	if err != nil {
-		t.Fatalf("failed to open envelope: %v", err)
-	}
-	if opened.Flags != message.Flags {
-		t.Fatalf("flags mismatch: have %d, want %d", opened.Flags, message.Flags)
-	}
-	if bytes.Compare(opened.Signature, message.Signature) != 0 {
-		t.Fatalf("signature mismatch: have 0x%x, want 0x%x", opened.Signature, message.Signature)
-	}
-	if bytes.Compare(opened.Payload, message.Payload) != 0 {
-		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, message.Payload)
-	}
-	if opened.Sent.Unix() != message.Sent.Unix() {
-		t.Fatalf("send time mismatch: have %d, want %d", opened.Sent, message.Sent)
-	}
-	if opened.TTL/time.Second != DefaultTTL/time.Second {
-		t.Fatalf("message TTL mismatch: have %v, want %v", opened.TTL, DefaultTTL)
-	}
-
-	if opened.Hash != envelope.Hash() {
-		t.Fatalf("message hash mismatch: have 0x%x, want 0x%x", opened.Hash, envelope.Hash())
-	}
-}
-
-func TestEnvelopeAnonymousOpenUntargeted(t *testing.T) {
-	payload := []byte("hello envelope")
-	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	opened, err := envelope.Open(nil)
-	if err != nil {
-		t.Fatalf("failed to open envelope: %v", err)
-	}
-	if opened.To != nil {
-		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
-	}
-	if bytes.Compare(opened.Payload, payload) != 0 {
-		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload)
-	}
-}
-
-func TestEnvelopeAnonymousOpenTargeted(t *testing.T) {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to generate test identity: %v", err)
-	}
-
-	payload := []byte("hello envelope")
-	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{
-		To: &key.PublicKey,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	opened, err := envelope.Open(nil)
-	if err != nil {
-		t.Fatalf("failed to open envelope: %v", err)
-	}
-	if opened.To != nil {
-		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
-	}
-	if bytes.Compare(opened.Payload, payload) == 0 {
-		t.Fatalf("payload match, should have been encrypted: 0x%x", opened.Payload)
-	}
-}
-
-func TestEnvelopeIdentifiedOpenUntargeted(t *testing.T) {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to generate test identity: %v", err)
-	}
-
-	payload := []byte("hello envelope")
-	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	opened, err := envelope.Open(key)
-	switch err {
-	case nil:
-		t.Fatalf("envelope opened with bad key: %v", opened)
-
-	case ecies.ErrInvalidPublicKey:
-		// Ok, key mismatch but opened
-
-	default:
-		t.Fatalf("failed to open envelope: %v", err)
-	}
-
-	if opened.To != nil {
-		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
-	}
-	if bytes.Compare(opened.Payload, payload) != 0 {
-		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload)
-	}
-}
-
-func TestEnvelopeIdentifiedOpenTargeted(t *testing.T) {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to generate test identity: %v", err)
-	}
-
-	payload := []byte("hello envelope")
-	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{
-		To: &key.PublicKey,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	opened, err := envelope.Open(key)
-	if err != nil {
-		t.Fatalf("failed to open envelope: %v", err)
-	}
-	if opened.To != nil {
-		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
-	}
-	if bytes.Compare(opened.Payload, payload) != 0 {
-		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload)
-	}
-}
diff --git a/whisper/filter.go b/whisper/filter.go
deleted file mode 100644
index 9f6d6b781..000000000
--- a/whisper/filter.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-// Contains the message filter for fine grained subscriptions.
-
-package whisper
-
-import (
-	"crypto/ecdsa"
-
-	"github.com/ethereum/go-ethereum/event/filter"
-)
-
-// Filter is used to subscribe to specific types of whisper messages.
-type Filter struct {
-	To     *ecdsa.PublicKey   // Recipient of the message
-	From   *ecdsa.PublicKey   // Sender of the message
-	Topics [][]Topic          // Topics to filter messages with
-	Fn     func(msg *Message) // Handler in case of a match
-}
-
-// NewFilterTopics creates a 2D topic array used by whisper.Filter from binary
-// data elements.
-func NewFilterTopics(data ...[][]byte) [][]Topic {
-	filter := make([][]Topic, len(data))
-	for i, condition := range data {
-		// Handle the special case of condition == [[]byte{}]
-		if len(condition) == 1 && len(condition[0]) == 0 {
-			filter[i] = []Topic{}
-			continue
-		}
-		// Otherwise flatten normally
-		filter[i] = NewTopics(condition...)
-	}
-	return filter
-}
-
-// NewFilterTopicsFlat creates a 2D topic array used by whisper.Filter from flat
-// binary data elements.
-func NewFilterTopicsFlat(data ...[]byte) [][]Topic {
-	filter := make([][]Topic, len(data))
-	for i, element := range data {
-		// Only add non-wildcard topics
-		filter[i] = make([]Topic, 0, 1)
-		if len(element) > 0 {
-			filter[i] = append(filter[i], NewTopic(element))
-		}
-	}
-	return filter
-}
-
-// NewFilterTopicsFromStrings creates a 2D topic array used by whisper.Filter
-// from textual data elements.
-func NewFilterTopicsFromStrings(data ...[]string) [][]Topic {
-	filter := make([][]Topic, len(data))
-	for i, condition := range data {
-		// Handle the special case of condition == [""]
-		if len(condition) == 1 && condition[0] == "" {
-			filter[i] = []Topic{}
-			continue
-		}
-		// Otherwise flatten normally
-		filter[i] = NewTopicsFromStrings(condition...)
-	}
-	return filter
-}
-
-// NewFilterTopicsFromStringsFlat creates a 2D topic array used by whisper.Filter from flat
-// binary data elements.
-func NewFilterTopicsFromStringsFlat(data ...string) [][]Topic {
-	filter := make([][]Topic, len(data))
-	for i, element := range data {
-		// Only add non-wildcard topics
-		filter[i] = make([]Topic, 0, 1)
-		if element != "" {
-			filter[i] = append(filter[i], NewTopicFromString(element))
-		}
-	}
-	return filter
-}
-
-// filterer is the internal, fully initialized filter ready to match inbound
-// messages to a variety of criteria.
-type filterer struct {
-	to      string                 // Recipient of the message
-	from    string                 // Sender of the message
-	matcher *topicMatcher          // Topics to filter messages with
-	fn      func(data interface{}) // Handler in case of a match
-}
-
-// Compare checks if the specified filter matches the current one.
-func (self filterer) Compare(f filter.Filter) bool {
-	filter := f.(filterer)
-
-	// Check the message sender and recipient
-	if len(self.to) > 0 && self.to != filter.to {
-		return false
-	}
-	if len(self.from) > 0 && self.from != filter.from {
-		return false
-	}
-	// Check the topic filtering
-	topics := make([]Topic, len(filter.matcher.conditions))
-	for i, group := range filter.matcher.conditions {
-		// Message should contain a single topic entry, extract
-		for topics[i], _ = range group {
-			break
-		}
-	}
-	if !self.matcher.Matches(topics) {
-		return false
-	}
-	return true
-}
-
-// Trigger is called when a filter successfully matches an inbound message.
-func (self filterer) Trigger(data interface{}) {
-	self.fn(data)
-}
diff --git a/whisper/filter_test.go b/whisper/filter_test.go
deleted file mode 100644
index b805b2baf..000000000
--- a/whisper/filter_test.go
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"bytes"
-
-	"testing"
-)
-
-var filterTopicsCreationTests = []struct {
-	topics [][]string
-	filter [][][4]byte
-}{
-	{ // Simple topic filter
-		topics: [][]string{
-			{"abc", "def", "ghi"},
-			{"def"},
-			{"ghi", "abc"},
-		},
-		filter: [][][4]byte{
-			{{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9}},
-			{{0x34, 0x60, 0x7c, 0x9b}},
-			{{0x21, 0x41, 0x7d, 0xf9}, {0x4e, 0x03, 0x65, 0x7a}},
-		},
-	},
-	{ // Wild-carded topic filter
-		topics: [][]string{
-			{"abc", "def", "ghi"},
-			{},
-			{""},
-			{"def"},
-		},
-		filter: [][][4]byte{
-			{{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9}},
-			{},
-			{},
-			{{0x34, 0x60, 0x7c, 0x9b}},
-		},
-	},
-}
-
-var filterTopicsCreationFlatTests = []struct {
-	topics []string
-	filter [][][4]byte
-}{
-	{ // Simple topic list
-		topics: []string{"abc", "def", "ghi"},
-		filter: [][][4]byte{
-			{{0x4e, 0x03, 0x65, 0x7a}},
-			{{0x34, 0x60, 0x7c, 0x9b}},
-			{{0x21, 0x41, 0x7d, 0xf9}},
-		},
-	},
-	{ // Wild-carded topic list
-		topics: []string{"abc", "", "ghi"},
-		filter: [][][4]byte{
-			{{0x4e, 0x03, 0x65, 0x7a}},
-			{},
-			{{0x21, 0x41, 0x7d, 0xf9}},
-		},
-	},
-}
-
-func TestFilterTopicsCreation(t *testing.T) {
-	// Check full filter creation
-	for i, tt := range filterTopicsCreationTests {
-		// Check the textual creation
-		filter := NewFilterTopicsFromStrings(tt.topics...)
-		if len(filter) != len(tt.topics) {
-			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
-			continue
-		}
-		for j, condition := range filter {
-			if len(condition) != len(tt.filter[j]) {
-				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
-				continue
-			}
-			for k := 0; k < len(condition); k++ {
-				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
-					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
-				}
-			}
-		}
-		// Check the binary creation
-		binary := make([][][]byte, len(tt.topics))
-		for j, condition := range tt.topics {
-			binary[j] = make([][]byte, len(condition))
-			for k, segment := range condition {
-				binary[j][k] = []byte(segment)
-			}
-		}
-		filter = NewFilterTopics(binary...)
-		if len(filter) != len(tt.topics) {
-			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
-			continue
-		}
-		for j, condition := range filter {
-			if len(condition) != len(tt.filter[j]) {
-				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
-				continue
-			}
-			for k := 0; k < len(condition); k++ {
-				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
-					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
-				}
-			}
-		}
-	}
-	// Check flat filter creation
-	for i, tt := range filterTopicsCreationFlatTests {
-		// Check the textual creation
-		filter := NewFilterTopicsFromStringsFlat(tt.topics...)
-		if len(filter) != len(tt.topics) {
-			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
-			continue
-		}
-		for j, condition := range filter {
-			if len(condition) != len(tt.filter[j]) {
-				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
-				continue
-			}
-			for k := 0; k < len(condition); k++ {
-				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
-					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
-				}
-			}
-		}
-		// Check the binary creation
-		binary := make([][]byte, len(tt.topics))
-		for j, topic := range tt.topics {
-			binary[j] = []byte(topic)
-		}
-		filter = NewFilterTopicsFlat(binary...)
-		if len(filter) != len(tt.topics) {
-			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
-			continue
-		}
-		for j, condition := range filter {
-			if len(condition) != len(tt.filter[j]) {
-				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
-				continue
-			}
-			for k := 0; k < len(condition); k++ {
-				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
-					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
-				}
-			}
-		}
-	}
-}
-
-var filterCompareTests = []struct {
-	matcher filterer
-	message filterer
-	match   bool
-}{
-	{ // Wild-card filter matching anything
-		matcher: filterer{to: "", from: "", matcher: newTopicMatcher()},
-		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		match:   true,
-	},
-	{ // Filter matching the to field
-		matcher: filterer{to: "to", from: "", matcher: newTopicMatcher()},
-		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		match:   true,
-	},
-	{ // Filter rejecting the to field
-		matcher: filterer{to: "to", from: "", matcher: newTopicMatcher()},
-		message: filterer{to: "", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		match:   false,
-	},
-	{ // Filter matching the from field
-		matcher: filterer{to: "", from: "from", matcher: newTopicMatcher()},
-		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		match:   true,
-	},
-	{ // Filter rejecting the from field
-		matcher: filterer{to: "", from: "from", matcher: newTopicMatcher()},
-		message: filterer{to: "to", from: "", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		match:   false,
-	},
-	{ // Filter matching the topic field
-		matcher: filterer{to: "", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		match:   true,
-	},
-	{ // Filter rejecting the topic field
-		matcher: filterer{to: "", from: "", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
-		message: filterer{to: "to", from: "from", matcher: newTopicMatcher()},
-		match:   false,
-	},
-}
-
-func TestFilterCompare(t *testing.T) {
-	for i, tt := range filterCompareTests {
-		if match := tt.matcher.Compare(tt.message); match != tt.match {
-			t.Errorf("test %d: match mismatch: have %v, want %v", i, match, tt.match)
-		}
-	}
-}
diff --git a/whisper/main.go b/whisper/main.go
deleted file mode 100644
index be4160489..000000000
--- a/whisper/main.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-// +build none
-
-// Contains a simple whisper peer setup and self messaging to allow playing
-// around with the protocol and API without a fancy client implementation.
-
-package main
-
-import (
-	"fmt"
-	"log"
-	"os"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/logger"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/nat"
-	"github.com/ethereum/go-ethereum/whisper"
-)
-
-func main() {
-	logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.InfoLevel))
-
-	// Generate the peer identity
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		fmt.Printf("Failed to generate peer key: %v.\n", err)
-		os.Exit(-1)
-	}
-	name := common.MakeName("whisper-go", "1.0")
-	shh := whisper.New()
-
-	// Create an Ethereum peer to communicate through
-	server := p2p.Server{
-		PrivateKey: key,
-		MaxPeers:   10,
-		Name:       name,
-		Protocols:  []p2p.Protocol{shh.Protocol()},
-		ListenAddr: ":30300",
-		NAT:        nat.Any(),
-	}
-	fmt.Println("Starting Ethereum peer...")
-	if err := server.Start(); err != nil {
-		fmt.Printf("Failed to start Ethereum peer: %v.\n", err)
-		os.Exit(1)
-	}
-
-	// Send a message to self to check that something works
-	payload := fmt.Sprintf("Hello world, this is %v. In case you're wondering, the time is %v", name, time.Now())
-	if err := selfSend(shh, []byte(payload)); err != nil {
-		fmt.Printf("Failed to self message: %v.\n", err)
-		os.Exit(-1)
-	}
-}
-
-// SendSelf wraps a payload into a Whisper envelope and forwards it to itself.
-func selfSend(shh *whisper.Whisper, payload []byte) error {
-	ok := make(chan struct{})
-
-	// Start watching for self messages, output any arrivals
-	id := shh.NewIdentity()
-	shh.Watch(whisper.Filter{
-		To: &id.PublicKey,
-		Fn: func(msg *whisper.Message) {
-			fmt.Printf("Message received: %s, signed with 0x%x.\n", string(msg.Payload), msg.Signature)
-			close(ok)
-		},
-	})
-	// Wrap the payload and encrypt it
-	msg := whisper.NewMessage(payload)
-	envelope, err := msg.Wrap(whisper.DefaultPoW, whisper.Options{
-		From: id,
-		To:   &id.PublicKey,
-		TTL:  whisper.DefaultTTL,
-	})
-	if err != nil {
-		return fmt.Errorf("failed to seal message: %v", err)
-	}
-	// Dump the message into the system and wait for it to pop back out
-	if err := shh.Send(envelope); err != nil {
-		return fmt.Errorf("failed to send self-message: %v", err)
-	}
-	select {
-	case <-ok:
-	case <-time.After(time.Second):
-		return fmt.Errorf("failed to receive message in time")
-	}
-	return nil
-}
diff --git a/whisper/message.go b/whisper/message.go
deleted file mode 100644
index f05b5d8b5..000000000
--- a/whisper/message.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-// Contains the Whisper protocol Message element. For formal details please see
-// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages.
-
-package whisper
-
-import (
-	"crypto/ecdsa"
-	"math/rand"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/logger"
-	"github.com/ethereum/go-ethereum/logger/glog"
-)
-
-// Message represents an end-user data packet to transmit through the Whisper
-// protocol. These are wrapped into Envelopes that need not be understood by
-// intermediate nodes, just forwarded.
-type Message struct {
-	Flags     byte // First bit is signature presence, rest reserved and should be random
-	Signature []byte
-	Payload   []byte
-
-	Sent time.Time     // Time when the message was posted into the network
-	TTL  time.Duration // Maximum time to live allowed for the message
-
-	To   *ecdsa.PublicKey // Message recipient (identity used to decode the message)
-	Hash common.Hash      // Message envelope hash to act as a unique id
-}
-
-// Options specifies the exact way a message should be wrapped into an Envelope.
-type Options struct {
-	From   *ecdsa.PrivateKey
-	To     *ecdsa.PublicKey
-	TTL    time.Duration
-	Topics []Topic
-}
-
-// NewMessage creates and initializes a non-signed, non-encrypted Whisper message.
-func NewMessage(payload []byte) *Message {
-	// Construct an initial flag set: no signature, rest random
-	flags := byte(rand.Intn(256))
-	flags &= ^signatureFlag
-
-	// Assemble and return the message
-	return &Message{
-		Flags:   flags,
-		Payload: payload,
-		Sent:    time.Now(),
-	}
-}
-
-// Wrap bundles the message into an Envelope to transmit over the network.
-//
-// pow (Proof Of Work) controls how much time to spend on hashing the message,
-// inherently controlling its priority through the network (smaller hash, bigger
-// priority).
-//
-// The user can control the amount of identity, privacy and encryption through
-// the options parameter as follows:
-//   - options.From == nil && options.To == nil: anonymous broadcast
-//   - options.From != nil && options.To == nil: signed broadcast (known sender)
-//   - options.From == nil && options.To != nil: encrypted anonymous message
-//   - options.From != nil && options.To != nil: encrypted signed message
-func (self *Message) Wrap(pow time.Duration, options Options) (*Envelope, error) {
-	// Use the default TTL if non was specified
-	if options.TTL == 0 {
-		options.TTL = DefaultTTL
-	}
-	self.TTL = options.TTL
-
-	// Sign and encrypt the message if requested
-	if options.From != nil {
-		if err := self.sign(options.From); err != nil {
-			return nil, err
-		}
-	}
-	if options.To != nil {
-		if err := self.encrypt(options.To); err != nil {
-			return nil, err
-		}
-	}
-	// Wrap the processed message, seal it and return
-	envelope := NewEnvelope(options.TTL, options.Topics, self)
-	envelope.Seal(pow)
-
-	return envelope, nil
-}
-
-// sign calculates and sets the cryptographic signature for the message , also
-// setting the sign flag.
-func (self *Message) sign(key *ecdsa.PrivateKey) (err error) {
-	self.Flags |= signatureFlag
-	self.Signature, err = crypto.Sign(self.hash(), key)
-	return
-}
-
-// Recover retrieves the public key of the message signer.
-func (self *Message) Recover() *ecdsa.PublicKey {
-	defer func() { recover() }() // in case of invalid signature
-
-	// Short circuit if no signature is present
-	if self.Signature == nil {
-		return nil
-	}
-	// Otherwise try and recover the signature
-	pub, err := crypto.SigToPub(self.hash(), self.Signature)
-	if err != nil {
-		glog.V(logger.Error).Infof("Could not get public key from signature: %v", err)
-		return nil
-	}
-	return pub
-}
-
-// encrypt encrypts a message payload with a public key.
-func (self *Message) encrypt(key *ecdsa.PublicKey) (err error) {
-	self.Payload, err = crypto.Encrypt(key, self.Payload)
-	return
-}
-
-// decrypt decrypts an encrypted payload with a private key.
-func (self *Message) decrypt(key *ecdsa.PrivateKey) error {
-	cleartext, err := crypto.Decrypt(key, self.Payload)
-	if err == nil {
-		self.Payload = cleartext
-	}
-	return err
-}
-
-// hash calculates the SHA3 checksum of the message flags and payload.
-func (self *Message) hash() []byte {
-	return crypto.Keccak256(append([]byte{self.Flags}, self.Payload...))
-}
-
-// bytes flattens the message contents (flags, signature and payload) into a
-// single binary blob.
-func (self *Message) bytes() []byte {
-	return append([]byte{self.Flags}, append(self.Signature, self.Payload...)...)
-}
diff --git a/whisper/message_test.go b/whisper/message_test.go
deleted file mode 100644
index 921c967a9..000000000
--- a/whisper/message_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"bytes"
-	"crypto/elliptic"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/crypto/secp256k1"
-)
-
-// Tests whether a message can be wrapped without any identity or encryption.
-func TestMessageSimpleWrap(t *testing.T) {
-	payload := []byte("hello world")
-
-	msg := NewMessage(payload)
-	if _, err := msg.Wrap(DefaultPoW, Options{}); err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if msg.Flags&signatureFlag != 0 {
-		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, 0)
-	}
-	if len(msg.Signature) != 0 {
-		t.Fatalf("signature found for simple wrapping: 0x%x", msg.Signature)
-	}
-	if bytes.Compare(msg.Payload, payload) != 0 {
-		t.Fatalf("payload mismatch after wrapping: have 0x%x, want 0x%x", msg.Payload, payload)
-	}
-	if msg.TTL/time.Second != DefaultTTL/time.Second {
-		t.Fatalf("message TTL mismatch: have %v, want %v", msg.TTL, DefaultTTL)
-	}
-}
-
-// Tests whether a message can be signed, and wrapped in plain-text.
-func TestMessageCleartextSignRecover(t *testing.T) {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to create crypto key: %v", err)
-	}
-	payload := []byte("hello world")
-
-	msg := NewMessage(payload)
-	if _, err := msg.Wrap(DefaultPoW, Options{
-		From: key,
-	}); err != nil {
-		t.Fatalf("failed to sign message: %v", err)
-	}
-	if msg.Flags&signatureFlag != signatureFlag {
-		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, signatureFlag)
-	}
-	if bytes.Compare(msg.Payload, payload) != 0 {
-		t.Fatalf("payload mismatch after signing: have 0x%x, want 0x%x", msg.Payload, payload)
-	}
-
-	pubKey := msg.Recover()
-	if pubKey == nil {
-		t.Fatalf("failed to recover public key")
-	}
-	p1 := elliptic.Marshal(secp256k1.S256(), key.PublicKey.X, key.PublicKey.Y)
-	p2 := elliptic.Marshal(secp256k1.S256(), pubKey.X, pubKey.Y)
-	if !bytes.Equal(p1, p2) {
-		t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1)
-	}
-}
-
-// Tests whether a message can be encrypted and decrypted using an anonymous
-// sender (i.e. no signature).
-func TestMessageAnonymousEncryptDecrypt(t *testing.T) {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to create recipient crypto key: %v", err)
-	}
-	payload := []byte("hello world")
-
-	msg := NewMessage(payload)
-	envelope, err := msg.Wrap(DefaultPoW, Options{
-		To: &key.PublicKey,
-	})
-	if err != nil {
-		t.Fatalf("failed to encrypt message: %v", err)
-	}
-	if msg.Flags&signatureFlag != 0 {
-		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, 0)
-	}
-	if len(msg.Signature) != 0 {
-		t.Fatalf("signature found for anonymous message: 0x%x", msg.Signature)
-	}
-
-	out, err := envelope.Open(key)
-	if err != nil {
-		t.Fatalf("failed to open encrypted message: %v", err)
-	}
-	if !bytes.Equal(out.Payload, payload) {
-		t.Errorf("payload mismatch: have 0x%x, want 0x%x", out.Payload, payload)
-	}
-}
-
-// Tests whether a message can be properly signed and encrypted.
-func TestMessageFullCrypto(t *testing.T) {
-	fromKey, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to create sender crypto key: %v", err)
-	}
-	toKey, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to create recipient crypto key: %v", err)
-	}
-
-	payload := []byte("hello world")
-	msg := NewMessage(payload)
-	envelope, err := msg.Wrap(DefaultPoW, Options{
-		From: fromKey,
-		To:   &toKey.PublicKey,
-	})
-	if err != nil {
-		t.Fatalf("failed to encrypt message: %v", err)
-	}
-	if msg.Flags&signatureFlag != signatureFlag {
-		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, signatureFlag)
-	}
-	if len(msg.Signature) == 0 {
-		t.Fatalf("no signature found for signed message")
-	}
-
-	out, err := envelope.Open(toKey)
-	if err != nil {
-		t.Fatalf("failed to open encrypted message: %v", err)
-	}
-	if !bytes.Equal(out.Payload, payload) {
-		t.Errorf("payload mismatch: have 0x%x, want 0x%x", out.Payload, payload)
-	}
-
-	pubKey := out.Recover()
-	if pubKey == nil {
-		t.Fatalf("failed to recover public key")
-	}
-	p1 := elliptic.Marshal(secp256k1.S256(), fromKey.PublicKey.X, fromKey.PublicKey.Y)
-	p2 := elliptic.Marshal(secp256k1.S256(), pubKey.X, pubKey.Y)
-	if !bytes.Equal(p1, p2) {
-		t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1)
-	}
-}
diff --git a/whisper/peer.go b/whisper/peer.go
deleted file mode 100644
index ee10e66e7..000000000
--- a/whisper/peer.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"fmt"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/logger"
-	"github.com/ethereum/go-ethereum/logger/glog"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rlp"
-	"gopkg.in/fatih/set.v0"
-)
-
-// peer represents a whisper protocol peer connection.
-type peer struct {
-	host *Whisper
-	peer *p2p.Peer
-	ws   p2p.MsgReadWriter
-
-	known *set.Set // Messages already known by the peer to avoid wasting bandwidth
-
-	quit chan struct{}
-}
-
-// newPeer creates a new whisper peer object, but does not run the handshake itself.
-func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *peer {
-	return &peer{
-		host:  host,
-		peer:  remote,
-		ws:    rw,
-		known: set.New(),
-		quit:  make(chan struct{}),
-	}
-}
-
-// start initiates the peer updater, periodically broadcasting the whisper packets
-// into the network.
-func (self *peer) start() {
-	go self.update()
-	glog.V(logger.Debug).Infof("%v: whisper started", self.peer)
-}
-
-// stop terminates the peer updater, stopping message forwarding to it.
-func (self *peer) stop() {
-	close(self.quit)
-	glog.V(logger.Debug).Infof("%v: whisper stopped", self.peer)
-}
-
-// handshake sends the protocol initiation status message to the remote peer and
-// verifies the remote status too.
-func (self *peer) handshake() error {
-	// Send the handshake status message asynchronously
-	errc := make(chan error, 1)
-	go func() {
-		errc <- p2p.SendItems(self.ws, statusCode, protocolVersion)
-	}()
-	// Fetch the remote status packet and verify protocol match
-	packet, err := self.ws.ReadMsg()
-	if err != nil {
-		return err
-	}
-	if packet.Code != statusCode {
-		return fmt.Errorf("peer sent %x before status packet", packet.Code)
-	}
-	s := rlp.NewStream(packet.Payload, uint64(packet.Size))
-	if _, err := s.List(); err != nil {
-		return fmt.Errorf("bad status message: %v", err)
-	}
-	peerVersion, err := s.Uint()
-	if err != nil {
-		return fmt.Errorf("bad status message: %v", err)
-	}
-	if peerVersion != protocolVersion {
-		return fmt.Errorf("protocol version mismatch %d != %d", peerVersion, protocolVersion)
-	}
-	// Wait until out own status is consumed too
-	if err := <-errc; err != nil {
-		return fmt.Errorf("failed to send status packet: %v", err)
-	}
-	return nil
-}
-
-// update executes periodic operations on the peer, including message transmission
-// and expiration.
-func (self *peer) update() {
-	// Start the tickers for the updates
-	expire := time.NewTicker(expirationCycle)
-	transmit := time.NewTicker(transmissionCycle)
-
-	// Loop and transmit until termination is requested
-	for {
-		select {
-		case <-expire.C:
-			self.expire()
-
-		case <-transmit.C:
-			if err := self.broadcast(); err != nil {
-				glog.V(logger.Info).Infof("%v: broadcast failed: %v", self.peer, err)
-				return
-			}
-
-		case <-self.quit:
-			return
-		}
-	}
-}
-
-// mark marks an envelope known to the peer so that it won't be sent back.
-func (self *peer) mark(envelope *Envelope) {
-	self.known.Add(envelope.Hash())
-}
-
-// marked checks if an envelope is already known to the remote peer.
-func (self *peer) marked(envelope *Envelope) bool {
-	return self.known.Has(envelope.Hash())
-}
-
-// expire iterates over all the known envelopes in the host and removes all
-// expired (unknown) ones from the known list.
-func (self *peer) expire() {
-	// Assemble the list of available envelopes
-	available := set.NewNonTS()
-	for _, envelope := range self.host.envelopes() {
-		available.Add(envelope.Hash())
-	}
-	// Cross reference availability with known status
-	unmark := make(map[common.Hash]struct{})
-	self.known.Each(func(v interface{}) bool {
-		if !available.Has(v.(common.Hash)) {
-			unmark[v.(common.Hash)] = struct{}{}
-		}
-		return true
-	})
-	// Dump all known but unavailable
-	for hash, _ := range unmark {
-		self.known.Remove(hash)
-	}
-}
-
-// broadcast iterates over the collection of envelopes and transmits yet unknown
-// ones over the network.
-func (self *peer) broadcast() error {
-	// Fetch the envelopes and collect the unknown ones
-	envelopes := self.host.envelopes()
-	transmit := make([]*Envelope, 0, len(envelopes))
-	for _, envelope := range envelopes {
-		if !self.marked(envelope) {
-			transmit = append(transmit, envelope)
-			self.mark(envelope)
-		}
-	}
-	// Transmit the unknown batch (potentially empty)
-	if err := p2p.Send(self.ws, messagesCode, transmit); err != nil {
-		return err
-	}
-	glog.V(logger.Detail).Infoln(self.peer, "broadcasted", len(transmit), "message(s)")
-	return nil
-}
diff --git a/whisper/peer_test.go b/whisper/peer_test.go
deleted file mode 100644
index 636bd8ca1..000000000
--- a/whisper/peer_test.go
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/discover"
-)
-
-type testPeer struct {
-	client *Whisper
-	stream *p2p.MsgPipeRW
-	termed chan struct{}
-}
-
-func startTestPeer() *testPeer {
-	// Create a simulated P2P remote peer and data streams to it
-	remote := p2p.NewPeer(discover.NodeID{}, "", nil)
-	tester, tested := p2p.MsgPipe()
-
-	// Create a whisper client and connect with it to the tester peer
-	client := New()
-	client.Start(nil)
-
-	termed := make(chan struct{})
-	go func() {
-		defer client.Stop()
-		defer close(termed)
-		defer tested.Close()
-
-		client.handlePeer(remote, tested)
-	}()
-
-	return &testPeer{
-		client: client,
-		stream: tester,
-		termed: termed,
-	}
-}
-
-func startTestPeerInited() (*testPeer, error) {
-	peer := startTestPeer()
-
-	if err := p2p.ExpectMsg(peer.stream, statusCode, []uint64{protocolVersion}); err != nil {
-		peer.stream.Close()
-		return nil, err
-	}
-	if err := p2p.SendItems(peer.stream, statusCode, protocolVersion); err != nil {
-		peer.stream.Close()
-		return nil, err
-	}
-	return peer, nil
-}
-
-func TestPeerStatusMessage(t *testing.T) {
-	tester := startTestPeer()
-
-	// Wait for the handshake status message and check it
-	if err := p2p.ExpectMsg(tester.stream, statusCode, []uint64{protocolVersion}); err != nil {
-		t.Fatalf("status message mismatch: %v", err)
-	}
-	// Terminate the node
-	tester.stream.Close()
-
-	select {
-	case <-tester.termed:
-	case <-time.After(time.Second):
-		t.Fatalf("local close timed out")
-	}
-}
-
-func TestPeerHandshakeFail(t *testing.T) {
-	tester := startTestPeer()
-
-	// Wait for and check the handshake
-	if err := p2p.ExpectMsg(tester.stream, statusCode, []uint64{protocolVersion}); err != nil {
-		t.Fatalf("status message mismatch: %v", err)
-	}
-	// Send an invalid handshake status and verify disconnect
-	if err := p2p.SendItems(tester.stream, messagesCode); err != nil {
-		t.Fatalf("failed to send malformed status: %v", err)
-	}
-	select {
-	case <-tester.termed:
-	case <-time.After(time.Second):
-		t.Fatalf("remote close timed out")
-	}
-}
-
-func TestPeerHandshakeSuccess(t *testing.T) {
-	tester := startTestPeer()
-
-	// Wait for and check the handshake
-	if err := p2p.ExpectMsg(tester.stream, statusCode, []uint64{protocolVersion}); err != nil {
-		t.Fatalf("status message mismatch: %v", err)
-	}
-	// Send a valid handshake status and make sure connection stays live
-	if err := p2p.SendItems(tester.stream, statusCode, protocolVersion); err != nil {
-		t.Fatalf("failed to send status: %v", err)
-	}
-	select {
-	case <-tester.termed:
-		t.Fatalf("valid handshake disconnected")
-
-	case <-time.After(100 * time.Millisecond):
-	}
-	// Clean up the test
-	tester.stream.Close()
-
-	select {
-	case <-tester.termed:
-	case <-time.After(time.Second):
-		t.Fatalf("local close timed out")
-	}
-}
-
-func TestPeerSend(t *testing.T) {
-	// Start a tester and execute the handshake
-	tester, err := startTestPeerInited()
-	if err != nil {
-		t.Fatalf("failed to start initialized peer: %v", err)
-	}
-	defer tester.stream.Close()
-
-	// Construct a message and inject into the tester
-	message := NewMessage([]byte("peer broadcast test message"))
-	envelope, err := message.Wrap(DefaultPoW, Options{
-		TTL: DefaultTTL,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if err := tester.client.Send(envelope); err != nil {
-		t.Fatalf("failed to send message: %v", err)
-	}
-	// Check that the message is eventually forwarded
-	payload := []interface{}{envelope}
-	if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil {
-		t.Fatalf("message mismatch: %v", err)
-	}
-	// Make sure that even with a re-insert, an empty batch is received
-	if err := tester.client.Send(envelope); err != nil {
-		t.Fatalf("failed to send message: %v", err)
-	}
-	if err := p2p.ExpectMsg(tester.stream, messagesCode, []interface{}{}); err != nil {
-		t.Fatalf("message mismatch: %v", err)
-	}
-}
-
-func TestPeerDeliver(t *testing.T) {
-	// Start a tester and execute the handshake
-	tester, err := startTestPeerInited()
-	if err != nil {
-		t.Fatalf("failed to start initialized peer: %v", err)
-	}
-	defer tester.stream.Close()
-
-	// Watch for all inbound messages
-	arrived := make(chan struct{}, 1)
-	tester.client.Watch(Filter{
-		Fn: func(message *Message) {
-			arrived <- struct{}{}
-		},
-	})
-	// Construct a message and deliver it to the tester peer
-	message := NewMessage([]byte("peer broadcast test message"))
-	envelope, err := message.Wrap(DefaultPoW, Options{
-		TTL: DefaultTTL,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if err := p2p.Send(tester.stream, messagesCode, []*Envelope{envelope}); err != nil {
-		t.Fatalf("failed to transfer message: %v", err)
-	}
-	// Check that the message is delivered upstream
-	select {
-	case <-arrived:
-	case <-time.After(time.Second):
-		t.Fatalf("message delivery timeout")
-	}
-	// Check that a resend is not delivered
-	if err := p2p.Send(tester.stream, messagesCode, []*Envelope{envelope}); err != nil {
-		t.Fatalf("failed to transfer message: %v", err)
-	}
-	select {
-	case <-time.After(2 * transmissionCycle):
-	case <-arrived:
-		t.Fatalf("repeating message arrived")
-	}
-}
-
-func TestPeerMessageExpiration(t *testing.T) {
-	// Start a tester and execute the handshake
-	tester, err := startTestPeerInited()
-	if err != nil {
-		t.Fatalf("failed to start initialized peer: %v", err)
-	}
-	defer tester.stream.Close()
-
-	// Fetch the peer instance for later inspection
-	tester.client.peerMu.RLock()
-	if peers := len(tester.client.peers); peers != 1 {
-		t.Fatalf("peer pool size mismatch: have %v, want %v", peers, 1)
-	}
-	var peer *peer
-	for peer, _ = range tester.client.peers {
-		break
-	}
-	tester.client.peerMu.RUnlock()
-
-	// Construct a message and pass it through the tester
-	message := NewMessage([]byte("peer test message"))
-	envelope, err := message.Wrap(DefaultPoW, Options{
-		TTL: time.Second,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if err := tester.client.Send(envelope); err != nil {
-		t.Fatalf("failed to send message: %v", err)
-	}
-	payload := []interface{}{envelope}
-	if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil {
-		// A premature empty message may have been broadcast, check the next too
-		if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil {
-			t.Fatalf("message mismatch: %v", err)
-		}
-	}
-	// Check that the message is inside the cache
-	if !peer.known.Has(envelope.Hash()) {
-		t.Fatalf("message not found in cache")
-	}
-	// Discard messages until expiration and check cache again
-	exp := time.Now().Add(time.Second + 2*expirationCycle + 100*time.Millisecond)
-	for time.Now().Before(exp) {
-		if err := p2p.ExpectMsg(tester.stream, messagesCode, []interface{}{}); err != nil {
-			t.Fatalf("message mismatch: %v", err)
-		}
-	}
-	if peer.known.Has(envelope.Hash()) {
-		t.Fatalf("message not expired from cache")
-	}
-}
diff --git a/whisper/shhapi/api.go b/whisper/shhapi/api.go
new file mode 100644
index 000000000..50a8eb34a
--- /dev/null
+++ b/whisper/shhapi/api.go
@@ -0,0 +1,504 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package shhapi
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	mathrand "math/rand"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/whisper/whisperv5"
+)
+
+var whisperOffLineErr = errors.New("whisper is offline")
+
+// PublicWhisperAPI provides the whisper RPC service.
+type PublicWhisperAPI struct {
+	whisper *whisperv5.Whisper
+}
+
+// NewPublicWhisperAPI create a new RPC whisper service.
+func NewPublicWhisperAPI() *PublicWhisperAPI {
+	w := whisperv5.NewWhisper(nil)
+	return &PublicWhisperAPI{whisper: w}
+}
+
+// APIs returns the RPC descriptors the Whisper implementation offers
+func APIs() []rpc.API {
+	return []rpc.API{
+		{
+			Namespace: whisperv5.ProtocolName,
+			Version:   whisperv5.ProtocolVersionStr,
+			Service:   NewPublicWhisperAPI(),
+			Public:    true,
+		},
+	}
+}
+
+// Version returns the Whisper version this node offers.
+func (api *PublicWhisperAPI) Version() (*rpc.HexNumber, error) {
+	if api.whisper == nil {
+		return rpc.NewHexNumber(0), whisperOffLineErr
+	}
+	return rpc.NewHexNumber(api.whisper.Version()), nil
+}
+
+// MarkPeerTrusted marks specific peer trusted, which will allow it
+// to send historic (expired) messages.
+func (api *PublicWhisperAPI) MarkPeerTrusted(peerID rpc.HexBytes) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+	return api.whisper.MarkPeerTrusted(peerID)
+}
+
+// RequestHistoricMessages requests the peer to deliver the old (expired) messages.
+// data contains parameters (time frame, payment details, etc.), required
+// by the remote email-like server. Whisper is not aware about the data format,
+// it will just forward the raw data to the server.
+func (api *PublicWhisperAPI) RequestHistoricMessages(peerID rpc.HexBytes, data rpc.HexBytes) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+	return api.whisper.RequestHistoricMessages(peerID, data)
+}
+
+// HasIdentity checks if the whisper node is configured with the private key
+// of the specified public pair.
+func (api *PublicWhisperAPI) HasIdentity(identity string) (bool, error) {
+	if api.whisper == nil {
+		return false, whisperOffLineErr
+	}
+	return api.whisper.HasIdentity(identity), nil
+}
+
+// DeleteIdentity deletes the specifies key if it exists.
+func (api *PublicWhisperAPI) DeleteIdentity(identity string) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+	api.whisper.DeleteIdentity(identity)
+	return nil
+}
+
+// NewIdentity generates a new cryptographic identity for the client, and injects
+// it into the known identities for message decryption.
+func (api *PublicWhisperAPI) NewIdentity() (string, error) {
+	if api.whisper == nil {
+		return "", whisperOffLineErr
+	}
+	identity := api.whisper.NewIdentity()
+	return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil
+}
+
+// GenerateSymKey generates a random symmetric key and stores it under
+// the 'name' id. Will be used in the future for session key exchange.
+func (api *PublicWhisperAPI) GenerateSymKey(name string) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+	return api.whisper.GenerateSymKey(name)
+}
+
+// AddSymKey stores the key under the 'name' id.
+func (api *PublicWhisperAPI) AddSymKey(name string, key []byte) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+	return api.whisper.AddSymKey(name, key)
+}
+
+// HasSymKey returns true if there is a key associated with the name string.
+// Otherwise returns false.
+func (api *PublicWhisperAPI) HasSymKey(name string) (bool, error) {
+	if api.whisper == nil {
+		return false, whisperOffLineErr
+	}
+	res := api.whisper.HasSymKey(name)
+	return res, nil
+}
+
+// DeleteSymKey deletes the key associated with the name string if it exists.
+func (api *PublicWhisperAPI) DeleteSymKey(name string) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+	api.whisper.DeleteSymKey(name)
+	return nil
+}
+
+// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages.
+// Returns the ID of the newly created Filter.
+func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (*rpc.HexNumber, error) {
+	if api.whisper == nil {
+		return nil, whisperOffLineErr
+	}
+
+	filter := whisperv5.Filter{
+		Src:       crypto.ToECDSAPub(args.From),
+		KeySym:    api.whisper.GetSymKey(args.KeyName),
+		PoW:       args.PoW,
+		Messages:  make(map[common.Hash]*whisperv5.ReceivedMessage),
+		AcceptP2P: args.AcceptP2P,
+	}
+
+	if len(filter.KeySym) > 0 {
+		filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
+	}
+
+	for _, t := range args.Topics {
+		filter.Topics = append(filter.Topics, t)
+	}
+
+	if len(args.Topics) == 0 {
+		info := "NewFilter: at least one topic must be specified"
+		glog.V(logger.Error).Infof(info)
+		return nil, errors.New(info)
+	}
+
+	if len(args.KeyName) != 0 && len(filter.KeySym) == 0 {
+		info := "NewFilter: key was not found by name: " + args.KeyName
+		glog.V(logger.Error).Infof(info)
+		return nil, errors.New(info)
+	}
+
+	if len(args.To) == 0 && len(filter.KeySym) == 0 {
+		info := "NewFilter: filter must contain either symmetric or asymmetric key"
+		glog.V(logger.Error).Infof(info)
+		return nil, errors.New(info)
+	}
+
+	if len(args.To) != 0 && len(filter.KeySym) != 0 {
+		info := "NewFilter: filter must not contain both symmetric and asymmetric key"
+		glog.V(logger.Error).Infof(info)
+		return nil, errors.New(info)
+	}
+
+	if len(args.To) > 0 {
+		dst := crypto.ToECDSAPub(args.To)
+		if !whisperv5.ValidatePublicKey(dst) {
+			info := "NewFilter: Invalid 'To' address"
+			glog.V(logger.Error).Infof(info)
+			return nil, errors.New(info)
+		}
+		filter.KeyAsym = api.whisper.GetIdentity(string(args.To))
+		if filter.KeyAsym == nil {
+			info := "NewFilter: non-existent identity provided"
+			glog.V(logger.Error).Infof(info)
+			return nil, errors.New(info)
+		}
+	}
+
+	if len(args.From) > 0 {
+		if !whisperv5.ValidatePublicKey(filter.Src) {
+			info := "NewFilter: Invalid 'From' address"
+			glog.V(logger.Error).Infof(info)
+			return nil, errors.New(info)
+		}
+	}
+
+	id := api.whisper.Watch(&filter)
+	return rpc.NewHexNumber(id), nil
+}
+
+// UninstallFilter disables and removes an existing filter.
+func (api *PublicWhisperAPI) UninstallFilter(filterId rpc.HexNumber) {
+	api.whisper.Unwatch(filterId.Int())
+}
+
+// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval.
+func (api *PublicWhisperAPI) GetFilterChanges(filterId rpc.HexNumber) []WhisperMessage {
+	f := api.whisper.GetFilter(filterId.Int())
+	if f != nil {
+		newMail := f.Retrieve()
+		return toWhisperMessages(newMail)
+	}
+	return toWhisperMessages(nil)
+}
+
+// GetMessages retrieves all the known messages that match a specific filter.
+func (api *PublicWhisperAPI) GetMessages(filterId rpc.HexNumber) []WhisperMessage {
+	all := api.whisper.Messages(filterId.Int())
+	return toWhisperMessages(all)
+}
+
+// toWhisperMessages converts a Whisper message to a RPC whisper message.
+func toWhisperMessages(messages []*whisperv5.ReceivedMessage) []WhisperMessage {
+	msgs := make([]WhisperMessage, len(messages))
+	for i, msg := range messages {
+		msgs[i] = NewWhisperMessage(msg)
+	}
+	return msgs
+}
+
+// Post creates a whisper message and injects it into the network for distribution.
+func (api *PublicWhisperAPI) Post(args PostArgs) error {
+	if api.whisper == nil {
+		return whisperOffLineErr
+	}
+
+	params := whisperv5.MessageParams{
+		TTL:      args.TTL,
+		Dst:      crypto.ToECDSAPub(args.To),
+		KeySym:   api.whisper.GetSymKey(args.KeyName),
+		Topic:    args.Topic,
+		Payload:  args.Payload,
+		Padding:  args.Padding,
+		WorkTime: args.WorkTime,
+		PoW:      args.PoW,
+	}
+
+	if len(args.From) > 0 {
+		pub := crypto.ToECDSAPub(args.From)
+		if !whisperv5.ValidatePublicKey(pub) {
+			info := "Post: Invalid 'From' address"
+			glog.V(logger.Error).Infof(info)
+			return errors.New(info)
+		}
+		params.Src = api.whisper.GetIdentity(string(args.From))
+		if params.Src == nil {
+			info := "Post: non-existent identity provided"
+			glog.V(logger.Error).Infof(info)
+			return errors.New(info)
+		}
+	}
+
+	filter := api.whisper.GetFilter(args.FilterID)
+	if filter == nil && args.FilterID > -1 {
+		info := fmt.Sprintf("Post: wrong filter id %d", args.FilterID)
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+
+	if filter != nil {
+		// get the missing fields from the filter
+		if params.KeySym == nil && filter.KeySym != nil {
+			params.KeySym = filter.KeySym
+		}
+		if params.Src == nil && filter.Src != nil {
+			params.Src = filter.KeyAsym
+		}
+		if (params.Topic == whisperv5.TopicType{}) {
+			sz := len(filter.Topics)
+			if sz < 1 {
+				info := fmt.Sprintf("Post: no topics in filter # %d", args.FilterID)
+				glog.V(logger.Error).Infof(info)
+				return errors.New(info)
+			} else if sz == 1 {
+				params.Topic = filter.Topics[0]
+			} else {
+				// choose randomly
+				rnd := mathrand.Intn(sz)
+				params.Topic = filter.Topics[rnd]
+			}
+		}
+	}
+
+	// validate
+	if len(args.KeyName) != 0 && len(params.KeySym) == 0 {
+		info := "Post: key was not found by name: " + args.KeyName
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+
+	if len(args.To) == 0 && len(args.KeyName) == 0 {
+		info := "Post: message must be encrypted either symmetrically or asymmetrically"
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+
+	if len(args.To) != 0 && len(args.KeyName) != 0 {
+		info := "Post: ambigous encryption method requested"
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+
+	if len(args.To) > 0 {
+		if !whisperv5.ValidatePublicKey(params.Dst) {
+			info := "Post: Invalid 'To' address"
+			glog.V(logger.Error).Infof(info)
+			return errors.New(info)
+		}
+	}
+
+	// encrypt and send
+	message := whisperv5.NewSentMessage(&params)
+	envelope, err := message.Wrap(&params)
+	if err != nil {
+		glog.V(logger.Error).Infof(err.Error())
+		return err
+	}
+	if len(envelope.Data) > whisperv5.MaxMessageLength {
+		info := "Post: message is too big"
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+	if (envelope.Topic == whisperv5.TopicType{} && envelope.IsSymmetric()) {
+		info := "Post: topic is missing for symmetric encryption"
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+
+	if args.PeerID != nil {
+		return api.whisper.SendP2PMessage(args.PeerID, envelope)
+	}
+
+	return api.whisper.Send(envelope)
+}
+
+type PostArgs struct {
+	TTL      uint32              `json:"ttl"`
+	From     rpc.HexBytes        `json:"from"`
+	To       rpc.HexBytes        `json:"to"`
+	KeyName  string              `json:"keyname"`
+	Topic    whisperv5.TopicType `json:"topic"`
+	Padding  rpc.HexBytes        `json:"padding"`
+	Payload  rpc.HexBytes        `json:"payload"`
+	WorkTime uint32              `json:"worktime"`
+	PoW      float64             `json:"pow"`
+	FilterID int                 `json:"filter"`
+	PeerID   rpc.HexBytes        `json:"directP2P"`
+}
+
+func (args *PostArgs) UnmarshalJSON(data []byte) (err error) {
+	var obj struct {
+		TTL      uint32              `json:"ttl"`
+		From     rpc.HexBytes        `json:"from"`
+		To       rpc.HexBytes        `json:"to"`
+		KeyName  string              `json:"keyname"`
+		Topic    whisperv5.TopicType `json:"topic"`
+		Payload  rpc.HexBytes        `json:"payload"`
+		Padding  rpc.HexBytes        `json:"padding"`
+		WorkTime uint32              `json:"worktime"`
+		PoW      float64             `json:"pow"`
+		FilterID rpc.HexBytes        `json:"filter"`
+		PeerID   rpc.HexBytes        `json:"directP2P"`
+	}
+
+	if err := json.Unmarshal(data, &obj); err != nil {
+		return err
+	}
+
+	args.TTL = obj.TTL
+	args.From = obj.From
+	args.To = obj.To
+	args.KeyName = obj.KeyName
+	args.Topic = obj.Topic
+	args.Payload = obj.Payload
+	args.Padding = obj.Padding
+	args.WorkTime = obj.WorkTime
+	args.PoW = obj.PoW
+	args.FilterID = -1
+	args.PeerID = obj.PeerID
+
+	if obj.FilterID != nil {
+		x := whisperv5.BytesToIntBigEndian(obj.FilterID)
+		args.FilterID = int(x)
+	}
+
+	return nil
+}
+
+type WhisperFilterArgs struct {
+	To        []byte
+	From      []byte
+	KeyName   string
+	PoW       float64
+	Topics    []whisperv5.TopicType
+	AcceptP2P bool
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
+// JSON message blob into a WhisperFilterArgs structure.
+func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
+	// Unmarshal the JSON message and sanity check
+	var obj struct {
+		To        rpc.HexBytes  `json:"to"`
+		From      rpc.HexBytes  `json:"from"`
+		KeyName   string        `json:"keyname"`
+		PoW       float64       `json:"pow"`
+		Topics    []interface{} `json:"topics"`
+		AcceptP2P bool          `json:"acceptP2P"`
+	}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return err
+	}
+
+	args.To = obj.To
+	args.From = obj.From
+	args.KeyName = obj.KeyName
+	args.PoW = obj.PoW
+	args.AcceptP2P = obj.AcceptP2P
+
+	// Construct the topic array
+	if obj.Topics != nil {
+		topics := make([]string, len(obj.Topics))
+		for i, field := range obj.Topics {
+			switch value := field.(type) {
+			case string:
+				topics[i] = value
+			case nil:
+				return fmt.Errorf("topic[%d] is empty", i)
+			default:
+				return fmt.Errorf("topic[%d] is not a string", i)
+			}
+		}
+		topicsDecoded := make([]whisperv5.TopicType, len(topics))
+		for j, s := range topics {
+			x := common.FromHex(s)
+			if x == nil || len(x) != whisperv5.TopicLength {
+				return fmt.Errorf("topic[%d] is invalid", j)
+			}
+			topicsDecoded[j] = whisperv5.BytesToTopic(x)
+		}
+		args.Topics = topicsDecoded
+	}
+
+	return nil
+}
+
+// WhisperMessage is the RPC representation of a whisper message.
+type WhisperMessage struct {
+	Payload string  `json:"payload"`
+	Padding string  `json:"padding"`
+	From    string  `json:"from"`
+	To      string  `json:"to"`
+	Sent    uint32  `json:"sent"`
+	TTL     uint32  `json:"ttl"`
+	PoW     float64 `json:"pow"`
+	Hash    string  `json:"hash"`
+}
+
+// NewWhisperMessage converts an internal message into an API version.
+func NewWhisperMessage(message *whisperv5.ReceivedMessage) WhisperMessage {
+	return WhisperMessage{
+		Payload: common.ToHex(message.Payload),
+		Padding: common.ToHex(message.Padding),
+		From:    common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())),
+		To:      common.ToHex(crypto.FromECDSAPub(message.Dst)),
+		Sent:    message.Sent,
+		TTL:     message.TTL,
+		PoW:     message.PoW,
+		Hash:    common.ToHex(message.EnvelopeHash.Bytes()),
+	}
+}
diff --git a/whisper/shhapi/api_test.go b/whisper/shhapi/api_test.go
new file mode 100644
index 000000000..8ce198595
--- /dev/null
+++ b/whisper/shhapi/api_test.go
@@ -0,0 +1,170 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package shhapi
+
+import (
+	"testing"
+
+	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/whisper/whisperv5"
+)
+
+func TestBasic(x *testing.T) {
+	var id string = "test"
+	api := NewPublicWhisperAPI()
+	if api == nil {
+		x.Errorf("failed to create API.")
+		return
+	}
+
+	ver, err := api.Version()
+	if err != nil {
+		x.Errorf("failed generateFilter: %s.", err)
+		return
+	}
+
+	if ver.Uint64() != whisperv5.ProtocolVersion {
+		x.Errorf("wrong version: %d.", ver.Uint64())
+		return
+	}
+
+	var hexnum rpc.HexNumber
+	mail := api.GetFilterChanges(hexnum)
+	if len(mail) != 0 {
+		x.Errorf("failed GetFilterChanges")
+		return
+	}
+
+	exist, err := api.HasIdentity(id)
+	if err != nil {
+		x.Errorf("failed 1 HasIdentity: %s.", err)
+		return
+	}
+	if exist {
+		x.Errorf("failed 2 HasIdentity: false positive.")
+		return
+	}
+
+	err = api.DeleteIdentity(id)
+	if err != nil {
+		x.Errorf("failed 3 DeleteIdentity: %s.", err)
+		return
+	}
+
+	pub, err := api.NewIdentity()
+	if err != nil {
+		x.Errorf("failed 4 NewIdentity: %s.", err)
+		return
+	}
+	if len(pub) == 0 {
+		x.Errorf("NewIdentity 5: empty")
+		return
+	}
+
+	exist, err = api.HasIdentity(pub)
+	if err != nil {
+		x.Errorf("failed 6 HasIdentity: %s.", err)
+		return
+	}
+	if !exist {
+		x.Errorf("failed 7 HasIdentity: false negative.")
+		return
+	}
+
+	err = api.DeleteIdentity(pub)
+	if err != nil {
+		x.Errorf("failed 8 DeleteIdentity: %s.", err)
+		return
+	}
+
+	exist, err = api.HasIdentity(pub)
+	if err != nil {
+		x.Errorf("failed 9 HasIdentity: %s.", err)
+		return
+	}
+	if exist {
+		x.Errorf("failed 10 HasIdentity: false positive.")
+		return
+	}
+
+	id = "arbitrary text"
+	id2 := "another arbitrary string"
+
+	exist, err = api.HasSymKey(id)
+	if err != nil {
+		x.Errorf("failed 11 HasSymKey: %s.", err)
+		return
+	}
+	if exist {
+		x.Errorf("failed 12 HasSymKey: false positive.")
+		return
+	}
+
+	err = api.GenerateSymKey(id)
+	if err != nil {
+		x.Errorf("failed 13 GenerateSymKey: %s.", err)
+		return
+	}
+
+	exist, err = api.HasSymKey(id)
+	if err != nil {
+		x.Errorf("failed 14 HasSymKey: %s.", err)
+		return
+	}
+	if !exist {
+		x.Errorf("failed 15 HasSymKey: false negative.")
+		return
+	}
+
+	err = api.AddSymKey(id, []byte("some stuff here"))
+	if err == nil {
+		x.Errorf("failed 16 AddSymKey: %s.", err)
+		return
+	}
+
+	err = api.AddSymKey(id2, []byte("some stuff here"))
+	if err != nil {
+		x.Errorf("failed 17 AddSymKey: %s.", err)
+		return
+	}
+
+	exist, err = api.HasSymKey(id2)
+	if err != nil {
+		x.Errorf("failed 18 HasSymKey: %s.", err)
+		return
+	}
+	if !exist {
+		x.Errorf("failed 19 HasSymKey: false negative.")
+		return
+	}
+
+	err = api.DeleteSymKey(id)
+	if err != nil {
+		x.Errorf("failed 20 DeleteSymKey: %s.", err)
+		return
+	}
+
+	exist, err = api.HasSymKey(id)
+	if err != nil {
+		x.Errorf("failed 21 HasSymKey: %s.", err)
+		return
+	}
+	if exist {
+		x.Errorf("failed 22 HasSymKey: false positive.")
+		return
+	}
+}
diff --git a/whisper/topic.go b/whisper/topic.go
deleted file mode 100644
index d37eb25ee..000000000
--- a/whisper/topic.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-// 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.
-//
-// Note, empty topics are considered the wildcard, and cannot be used in messages.
-func NewTopic(data []byte) Topic {
-	prefix := [4]byte{}
-	copy(prefix[:], crypto.Keccak256(data)[:4])
-	return Topic(prefix)
-}
-
-// NewTopics creates a list of topics from a list of binary data elements, by
-// iteratively calling NewTopic on each of them.
-func NewTopics(data ...[]byte) []Topic {
-	topics := make([]Topic, len(data))
-	for i, element := range data {
-		topics[i] = NewTopic(element)
-	}
-	return topics
-}
-
-// NewTopicFromString creates a topic using the binary data contents of the
-// specified string.
-func NewTopicFromString(data string) Topic {
-	return NewTopic([]byte(data))
-}
-
-// NewTopicsFromStrings creates a list of topics from a list of textual data
-// elements, by iteratively calling NewTopicFromString on each of them.
-func NewTopicsFromStrings(data ...string) []Topic {
-	topics := make([]Topic, len(data))
-	for i, element := range data {
-		topics[i] = NewTopicFromString(element)
-	}
-	return topics
-}
-
-// String converts a topic byte array to a string representation.
-func (self *Topic) String() string {
-	return string(self[:])
-}
-
-// topicMatcher is a filter expression to verify if a list of topics contained
-// in an arriving message matches some topic conditions. The topic matcher is
-// built up of a list of conditions, each of which must be satisfied by the
-// corresponding topic in the message. Each condition may require: a) an exact
-// topic match; b) a match from a set of topics; or c) a wild-card matching all.
-//
-// If a message contains more topics than required by the matcher, those beyond
-// the condition count are ignored and assumed to match.
-//
-// Consider the following sample topic matcher:
-//   sample := {
-//     {TopicA1, TopicA2, TopicA3},
-//     {TopicB},
-//     nil,
-//     {TopicD1, TopicD2}
-//   }
-// In order for a message to pass this filter, it should enumerate at least 4
-// topics, the first any of [TopicA1, TopicA2, TopicA3], the second mandatory
-// "TopicB", the third is ignored by the filter and the fourth either "TopicD1"
-// or "TopicD2". If the message contains further topics, the filter will match
-// them too.
-type topicMatcher struct {
-	conditions []map[Topic]struct{}
-}
-
-// newTopicMatcher create a topic matcher from a list of topic conditions.
-func newTopicMatcher(topics ...[]Topic) *topicMatcher {
-	matcher := make([]map[Topic]struct{}, len(topics))
-	for i, condition := range topics {
-		matcher[i] = make(map[Topic]struct{})
-		for _, topic := range condition {
-			matcher[i][topic] = struct{}{}
-		}
-	}
-	return &topicMatcher{conditions: matcher}
-}
-
-// newTopicMatcherFromBinary create a topic matcher from a list of binary conditions.
-func newTopicMatcherFromBinary(data ...[][]byte) *topicMatcher {
-	topics := make([][]Topic, len(data))
-	for i, condition := range data {
-		topics[i] = NewTopics(condition...)
-	}
-	return newTopicMatcher(topics...)
-}
-
-// newTopicMatcherFromStrings creates a topic matcher from a list of textual
-// conditions.
-func newTopicMatcherFromStrings(data ...[]string) *topicMatcher {
-	topics := make([][]Topic, len(data))
-	for i, condition := range data {
-		topics[i] = NewTopicsFromStrings(condition...)
-	}
-	return newTopicMatcher(topics...)
-}
-
-// Matches checks if a list of topics matches this particular condition set.
-func (self *topicMatcher) Matches(topics []Topic) bool {
-	// Mismatch if there aren't enough topics
-	if len(self.conditions) > len(topics) {
-		return false
-	}
-	// Check each topic condition for existence (skip wild-cards)
-	for i := 0; i < len(topics) && i < len(self.conditions); i++ {
-		if len(self.conditions[i]) > 0 {
-			if _, ok := self.conditions[i][topics[i]]; !ok {
-				return false
-			}
-		}
-	}
-	return true
-}
diff --git a/whisper/topic_test.go b/whisper/topic_test.go
deleted file mode 100644
index 9c45f6740..000000000
--- a/whisper/topic_test.go
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"bytes"
-	"testing"
-)
-
-var topicCreationTests = []struct {
-	data []byte
-	hash [4]byte
-}{
-	{hash: [4]byte{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte("test name")},
-	{hash: [4]byte{0xf2, 0x6e, 0x77, 0x79}, data: []byte("some other test")},
-}
-
-func TestTopicCreation(t *testing.T) {
-	// Create the topics individually
-	for i, tt := range topicCreationTests {
-		topic := NewTopic(tt.data)
-		if bytes.Compare(topic[:], tt.hash[:]) != 0 {
-			t.Errorf("binary test %d: hash mismatch: have %v, want %v.", i, topic, tt.hash)
-		}
-	}
-	for i, tt := range topicCreationTests {
-		topic := NewTopicFromString(string(tt.data))
-		if bytes.Compare(topic[:], tt.hash[:]) != 0 {
-			t.Errorf("textual test %d: hash mismatch: have %v, want %v.", i, topic, tt.hash)
-		}
-	}
-	// Create the topics in batches
-	binaryData := make([][]byte, len(topicCreationTests))
-	for i, tt := range topicCreationTests {
-		binaryData[i] = tt.data
-	}
-	textualData := make([]string, len(topicCreationTests))
-	for i, tt := range topicCreationTests {
-		textualData[i] = string(tt.data)
-	}
-
-	topics := NewTopics(binaryData...)
-	for i, tt := range topicCreationTests {
-		if bytes.Compare(topics[i][:], tt.hash[:]) != 0 {
-			t.Errorf("binary batch test %d: hash mismatch: have %v, want %v.", i, topics[i], tt.hash)
-		}
-	}
-	topics = NewTopicsFromStrings(textualData...)
-	for i, tt := range topicCreationTests {
-		if bytes.Compare(topics[i][:], tt.hash[:]) != 0 {
-			t.Errorf("textual batch test %d: hash mismatch: have %v, want %v.", i, topics[i], tt.hash)
-		}
-	}
-}
-
-var topicMatcherCreationTest = struct {
-	binary  [][][]byte
-	textual [][]string
-	matcher []map[[4]byte]struct{}
-}{
-	binary: [][][]byte{
-		[][]byte{},
-		[][]byte{
-			[]byte("Topic A"),
-		},
-		[][]byte{
-			[]byte("Topic B1"),
-			[]byte("Topic B2"),
-			[]byte("Topic B3"),
-		},
-	},
-	textual: [][]string{
-		[]string{},
-		[]string{"Topic A"},
-		[]string{"Topic B1", "Topic B2", "Topic B3"},
-	},
-	matcher: []map[[4]byte]struct{}{
-		map[[4]byte]struct{}{},
-		map[[4]byte]struct{}{
-			[4]byte{0x25, 0xfc, 0x95, 0x66}: struct{}{},
-		},
-		map[[4]byte]struct{}{
-			[4]byte{0x93, 0x6d, 0xec, 0x09}: struct{}{},
-			[4]byte{0x25, 0x23, 0x34, 0xd3}: struct{}{},
-			[4]byte{0x6b, 0xc2, 0x73, 0xd1}: struct{}{},
-		},
-	},
-}
-
-func TestTopicMatcherCreation(t *testing.T) {
-	test := topicMatcherCreationTest
-
-	matcher := newTopicMatcherFromBinary(test.binary...)
-	for i, cond := range matcher.conditions {
-		for topic, _ := range cond {
-			if _, ok := test.matcher[i][topic]; !ok {
-				t.Errorf("condition %d; extra topic found: 0x%x", i, topic[:])
-			}
-		}
-	}
-	for i, cond := range test.matcher {
-		for topic, _ := range cond {
-			if _, ok := matcher.conditions[i][topic]; !ok {
-				t.Errorf("condition %d; topic not found: 0x%x", i, topic[:])
-			}
-		}
-	}
-
-	matcher = newTopicMatcherFromStrings(test.textual...)
-	for i, cond := range matcher.conditions {
-		for topic, _ := range cond {
-			if _, ok := test.matcher[i][topic]; !ok {
-				t.Errorf("condition %d; extra topic found: 0x%x", i, topic[:])
-			}
-		}
-	}
-	for i, cond := range test.matcher {
-		for topic, _ := range cond {
-			if _, ok := matcher.conditions[i][topic]; !ok {
-				t.Errorf("condition %d; topic not found: 0x%x", i, topic[:])
-			}
-		}
-	}
-}
-
-var topicMatcherTests = []struct {
-	filter [][]string
-	topics []string
-	match  bool
-}{
-	// Empty topic matcher should match everything
-	{
-		filter: [][]string{},
-		topics: []string{},
-		match:  true,
-	},
-	{
-		filter: [][]string{},
-		topics: []string{"a", "b", "c"},
-		match:  true,
-	},
-	// Fixed topic matcher should match strictly, but only prefix
-	{
-		filter: [][]string{[]string{"a"}, []string{"b"}},
-		topics: []string{"a"},
-		match:  false,
-	},
-	{
-		filter: [][]string{[]string{"a"}, []string{"b"}},
-		topics: []string{"a", "b"},
-		match:  true,
-	},
-	{
-		filter: [][]string{[]string{"a"}, []string{"b"}},
-		topics: []string{"a", "b", "c"},
-		match:  true,
-	},
-	// Multi-matcher should match any from a sub-group
-	{
-		filter: [][]string{[]string{"a1", "a2"}},
-		topics: []string{"a"},
-		match:  false,
-	},
-	{
-		filter: [][]string{[]string{"a1", "a2"}},
-		topics: []string{"a1"},
-		match:  true,
-	},
-	{
-		filter: [][]string{[]string{"a1", "a2"}},
-		topics: []string{"a2"},
-		match:  true,
-	},
-	// Wild-card condition should match anything
-	{
-		filter: [][]string{[]string{}, []string{"b"}},
-		topics: []string{"a"},
-		match:  false,
-	},
-	{
-		filter: [][]string{[]string{}, []string{"b"}},
-		topics: []string{"a", "b"},
-		match:  true,
-	},
-	{
-		filter: [][]string{[]string{}, []string{"b"}},
-		topics: []string{"b", "b"},
-		match:  true,
-	},
-}
-
-func TestTopicMatcher(t *testing.T) {
-	for i, tt := range topicMatcherTests {
-		topics := NewTopicsFromStrings(tt.topics...)
-
-		matcher := newTopicMatcherFromStrings(tt.filter...)
-		if match := matcher.Matches(topics); match != tt.match {
-			t.Errorf("test %d: match mismatch: have %v, want %v", i, match, tt.match)
-		}
-	}
-}
diff --git a/whisper/whisper.go b/whisper/whisper.go
deleted file mode 100644
index 0a49c1000..000000000
--- a/whisper/whisper.go
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"crypto/ecdsa"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/crypto/ecies"
-	"github.com/ethereum/go-ethereum/event/filter"
-	"github.com/ethereum/go-ethereum/logger"
-	"github.com/ethereum/go-ethereum/logger/glog"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rpc"
-
-	"gopkg.in/fatih/set.v0"
-)
-
-const (
-	statusCode   = 0x00
-	messagesCode = 0x01
-
-	protocolVersion uint64 = 0x02
-	protocolName           = "shh"
-
-	signatureFlag   = byte(1 << 7)
-	signatureLength = 65
-
-	expirationCycle   = 800 * time.Millisecond
-	transmissionCycle = 300 * time.Millisecond
-)
-
-const (
-	DefaultTTL = 50 * time.Second
-	DefaultPoW = 50 * time.Millisecond
-)
-
-type MessageEvent struct {
-	To      *ecdsa.PrivateKey
-	From    *ecdsa.PublicKey
-	Message *Message
-}
-
-// Whisper represents a dark communication interface through the Ethereum
-// network, using its very own P2P communication layer.
-type Whisper struct {
-	protocol p2p.Protocol
-	filters  *filter.Filters
-
-	keys map[string]*ecdsa.PrivateKey
-
-	messages    map[common.Hash]*Envelope // Pool of messages currently tracked by this node
-	expirations map[uint32]*set.SetNonTS  // Message expiration pool (TODO: something lighter)
-	poolMu      sync.RWMutex              // Mutex to sync the message and expiration pools
-
-	peers  map[*peer]struct{} // Set of currently active peers
-	peerMu sync.RWMutex       // Mutex to sync the active peer set
-
-	quit chan struct{}
-}
-
-// New creates a Whisper client ready to communicate through the Ethereum P2P
-// network.
-func New() *Whisper {
-	whisper := &Whisper{
-		filters:     filter.New(),
-		keys:        make(map[string]*ecdsa.PrivateKey),
-		messages:    make(map[common.Hash]*Envelope),
-		expirations: make(map[uint32]*set.SetNonTS),
-		peers:       make(map[*peer]struct{}),
-		quit:        make(chan struct{}),
-	}
-	whisper.filters.Start()
-
-	// p2p whisper sub protocol handler
-	whisper.protocol = p2p.Protocol{
-		Name:    protocolName,
-		Version: uint(protocolVersion),
-		Length:  2,
-		Run:     whisper.handlePeer,
-	}
-
-	return whisper
-}
-
-// APIs returns the RPC descriptors the Whisper implementation offers
-func (s *Whisper) APIs() []rpc.API {
-	return []rpc.API{
-		{
-			Namespace: "shh",
-			Version:   "1.0",
-			Service:   NewPublicWhisperAPI(s),
-			Public:    true,
-		},
-	}
-}
-
-// Protocols returns the whisper sub-protocols ran by this particular client.
-func (self *Whisper) Protocols() []p2p.Protocol {
-	return []p2p.Protocol{self.protocol}
-}
-
-// Version returns the whisper sub-protocols version number.
-func (self *Whisper) Version() uint {
-	return self.protocol.Version
-}
-
-// NewIdentity generates a new cryptographic identity for the client, and injects
-// it into the known identities for message decryption.
-func (self *Whisper) NewIdentity() *ecdsa.PrivateKey {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		panic(err)
-	}
-	self.keys[string(crypto.FromECDSAPub(&key.PublicKey))] = key
-
-	return key
-}
-
-// HasIdentity checks if the the whisper node is configured with the private key
-// of the specified public pair.
-func (self *Whisper) HasIdentity(key *ecdsa.PublicKey) bool {
-	return self.keys[string(crypto.FromECDSAPub(key))] != nil
-}
-
-// GetIdentity retrieves the private key of the specified public identity.
-func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
-	return self.keys[string(crypto.FromECDSAPub(key))]
-}
-
-// Watch installs a new message handler to run in case a matching packet arrives
-// from the whisper network.
-func (self *Whisper) Watch(options Filter) int {
-	filter := filterer{
-		to:      string(crypto.FromECDSAPub(options.To)),
-		from:    string(crypto.FromECDSAPub(options.From)),
-		matcher: newTopicMatcher(options.Topics...),
-		fn: func(data interface{}) {
-			options.Fn(data.(*Message))
-		},
-	}
-	return self.filters.Install(filter)
-}
-
-// Unwatch removes an installed message handler.
-func (self *Whisper) Unwatch(id int) {
-	self.filters.Uninstall(id)
-}
-
-// Send injects a message into the whisper send queue, to be distributed in the
-// network in the coming cycles.
-func (self *Whisper) Send(envelope *Envelope) error {
-	return self.add(envelope)
-}
-
-// Start implements node.Service, starting the background data propagation thread
-// of the Whisper protocol.
-func (self *Whisper) Start(*p2p.Server) error {
-	glog.V(logger.Info).Infoln("Whisper started")
-	go self.update()
-	return nil
-}
-
-// Stop implements node.Service, stopping the background data propagation thread
-// of the Whisper protocol.
-func (self *Whisper) Stop() error {
-	close(self.quit)
-	glog.V(logger.Info).Infoln("Whisper stopped")
-	return nil
-}
-
-// Messages retrieves all the currently pooled messages matching a filter id.
-func (self *Whisper) Messages(id int) []*Message {
-	messages := make([]*Message, 0)
-	if filter := self.filters.Get(id); filter != nil {
-		for _, envelope := range self.messages {
-			if message := self.open(envelope); message != nil {
-				if self.filters.Match(filter, createFilter(message, envelope.Topics)) {
-					messages = append(messages, message)
-				}
-			}
-		}
-	}
-	return messages
-}
-
-// handlePeer is called by the underlying P2P layer when the whisper sub-protocol
-// connection is negotiated.
-func (self *Whisper) handlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
-	// Create the new peer and start tracking it
-	whisperPeer := newPeer(self, peer, rw)
-
-	self.peerMu.Lock()
-	self.peers[whisperPeer] = struct{}{}
-	self.peerMu.Unlock()
-
-	defer func() {
-		self.peerMu.Lock()
-		delete(self.peers, whisperPeer)
-		self.peerMu.Unlock()
-	}()
-
-	// Run the peer handshake and state updates
-	if err := whisperPeer.handshake(); err != nil {
-		return err
-	}
-	whisperPeer.start()
-	defer whisperPeer.stop()
-
-	// Read and process inbound messages directly to merge into client-global state
-	for {
-		// Fetch the next packet and decode the contained envelopes
-		packet, err := rw.ReadMsg()
-		if err != nil {
-			return err
-		}
-		var envelopes []*Envelope
-		if err := packet.Decode(&envelopes); err != nil {
-			glog.V(logger.Info).Infof("%v: failed to decode envelope: %v", peer, err)
-			continue
-		}
-		// Inject all envelopes into the internal pool
-		for _, envelope := range envelopes {
-			if err := self.add(envelope); err != nil {
-				// TODO Punish peer here. Invalid envelope.
-				glog.V(logger.Debug).Infof("%v: failed to pool envelope: %v", peer, err)
-			}
-			whisperPeer.mark(envelope)
-		}
-	}
-}
-
-// add inserts a new envelope into the message pool to be distributed within the
-// whisper network. It also inserts the envelope into the expiration pool at the
-// appropriate time-stamp.
-func (self *Whisper) add(envelope *Envelope) error {
-	self.poolMu.Lock()
-	defer self.poolMu.Unlock()
-
-	// short circuit when a received envelope has already expired
-	if envelope.Expiry < uint32(time.Now().Unix()) {
-		return nil
-	}
-
-	// Insert the message into the tracked pool
-	hash := envelope.Hash()
-	if _, ok := self.messages[hash]; ok {
-		glog.V(logger.Detail).Infof("whisper envelope already cached: %x\n", envelope)
-		return nil
-	}
-	self.messages[hash] = envelope
-
-	// Insert the message into the expiration pool for later removal
-	if self.expirations[envelope.Expiry] == nil {
-		self.expirations[envelope.Expiry] = set.NewNonTS()
-	}
-	if !self.expirations[envelope.Expiry].Has(hash) {
-		self.expirations[envelope.Expiry].Add(hash)
-
-		// Notify the local node of a message arrival
-		go self.postEvent(envelope)
-	}
-	glog.V(logger.Detail).Infof("cached whisper envelope %x\n", envelope)
-	return nil
-}
-
-// postEvent opens an envelope with the configured identities and delivers the
-// message upstream from application processing.
-func (self *Whisper) postEvent(envelope *Envelope) {
-	if message := self.open(envelope); message != nil {
-		self.filters.Notify(createFilter(message, envelope.Topics), message)
-	}
-}
-
-// open tries to decrypt a whisper envelope with all the configured identities,
-// returning the decrypted message and the key used to achieve it. If not keys
-// are configured, open will return the payload as if non encrypted.
-func (self *Whisper) open(envelope *Envelope) *Message {
-	// Short circuit if no identity is set, and assume clear-text
-	if len(self.keys) == 0 {
-		if message, err := envelope.Open(nil); err == nil {
-			return message
-		}
-	}
-	// Iterate over the keys and try to decrypt the message
-	for _, key := range self.keys {
-		message, err := envelope.Open(key)
-		if err == nil {
-			message.To = &key.PublicKey
-			return message
-		} else if err == ecies.ErrInvalidPublicKey {
-			return message
-		}
-	}
-	// Failed to decrypt, don't return anything
-	return nil
-}
-
-// createFilter creates a message filter to check against installed handlers.
-func createFilter(message *Message, topics []Topic) filter.Filter {
-	matcher := make([][]Topic, len(topics))
-	for i, topic := range topics {
-		matcher[i] = []Topic{topic}
-	}
-	return filterer{
-		to:      string(crypto.FromECDSAPub(message.To)),
-		from:    string(crypto.FromECDSAPub(message.Recover())),
-		matcher: newTopicMatcher(matcher...),
-	}
-}
-
-// update loops until the lifetime of the whisper node, updating its internal
-// state by expiring stale messages from the pool.
-func (self *Whisper) update() {
-	// Start a ticker to check for expirations
-	expire := time.NewTicker(expirationCycle)
-
-	// Repeat updates until termination is requested
-	for {
-		select {
-		case <-expire.C:
-			self.expire()
-
-		case <-self.quit:
-			return
-		}
-	}
-}
-
-// expire iterates over all the expiration timestamps, removing all stale
-// messages from the pools.
-func (self *Whisper) expire() {
-	self.poolMu.Lock()
-	defer self.poolMu.Unlock()
-
-	now := uint32(time.Now().Unix())
-	for then, hashSet := range self.expirations {
-		// Short circuit if a future time
-		if then > now {
-			continue
-		}
-		// Dump all expired messages and remove timestamp
-		hashSet.Each(func(v interface{}) bool {
-			delete(self.messages, v.(common.Hash))
-			return true
-		})
-		self.expirations[then].Clear()
-	}
-}
-
-// envelopes retrieves all the messages currently pooled by the node.
-func (self *Whisper) envelopes() []*Envelope {
-	self.poolMu.RLock()
-	defer self.poolMu.RUnlock()
-
-	envelopes := make([]*Envelope, 0, len(self.messages))
-	for _, envelope := range self.messages {
-		envelopes = append(envelopes, envelope)
-	}
-	return envelopes
-}
diff --git a/whisper/whisper_test.go b/whisper/whisper_test.go
deleted file mode 100644
index 094df373c..000000000
--- a/whisper/whisper_test.go
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-
-package whisper
-
-import (
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/discover"
-)
-
-func startTestCluster(n int) []*Whisper {
-	// Create the batch of simulated peers
-	nodes := make([]*p2p.Peer, n)
-	for i := 0; i < n; i++ {
-		nodes[i] = p2p.NewPeer(discover.NodeID{}, "", nil)
-	}
-	whispers := make([]*Whisper, n)
-	for i := 0; i < n; i++ {
-		whispers[i] = New()
-		whispers[i].Start(nil)
-	}
-	// Wire all the peers to the root one
-	for i := 1; i < n; i++ {
-		src, dst := p2p.MsgPipe()
-
-		go whispers[0].handlePeer(nodes[i], src)
-		go whispers[i].handlePeer(nodes[0], dst)
-	}
-	return whispers
-}
-
-func TestSelfMessage(t *testing.T) {
-	// Start the single node cluster
-	client := startTestCluster(1)[0]
-
-	// Start watching for self messages, signal any arrivals
-	self := client.NewIdentity()
-	done := make(chan struct{})
-
-	client.Watch(Filter{
-		To: &self.PublicKey,
-		Fn: func(msg *Message) {
-			close(done)
-		},
-	})
-	// Send a dummy message to oneself
-	msg := NewMessage([]byte("self whisper"))
-	envelope, err := msg.Wrap(DefaultPoW, Options{
-		From: self,
-		To:   &self.PublicKey,
-		TTL:  DefaultTTL,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	// Dump the message into the system and wait for it to pop back out
-	if err := client.Send(envelope); err != nil {
-		t.Fatalf("failed to send self-message: %v", err)
-	}
-	select {
-	case <-done:
-	case <-time.After(time.Second):
-		t.Fatalf("self-message receive timeout")
-	}
-}
-
-func TestDirectMessage(t *testing.T) {
-	// Start the sender-recipient cluster
-	cluster := startTestCluster(2)
-
-	sender := cluster[0]
-	senderId := sender.NewIdentity()
-
-	recipient := cluster[1]
-	recipientId := recipient.NewIdentity()
-
-	// Watch for arriving messages on the recipient
-	done := make(chan struct{})
-	recipient.Watch(Filter{
-		To: &recipientId.PublicKey,
-		Fn: func(msg *Message) {
-			close(done)
-		},
-	})
-	// Send a dummy message from the sender
-	msg := NewMessage([]byte("direct whisper"))
-	envelope, err := msg.Wrap(DefaultPoW, Options{
-		From: senderId,
-		To:   &recipientId.PublicKey,
-		TTL:  DefaultTTL,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if err := sender.Send(envelope); err != nil {
-		t.Fatalf("failed to send direct message: %v", err)
-	}
-	// Wait for an arrival or a timeout
-	select {
-	case <-done:
-	case <-time.After(time.Second):
-		t.Fatalf("direct message receive timeout")
-	}
-}
-
-func TestAnonymousBroadcast(t *testing.T) {
-	testBroadcast(true, t)
-}
-
-func TestIdentifiedBroadcast(t *testing.T) {
-	testBroadcast(false, t)
-}
-
-func testBroadcast(anonymous bool, t *testing.T) {
-	// Start the single sender multi recipient cluster
-	cluster := startTestCluster(3)
-
-	sender := cluster[1]
-	targets := cluster[1:]
-	for _, target := range targets {
-		if !anonymous {
-			target.NewIdentity()
-		}
-	}
-	// Watch for arriving messages on the recipients
-	dones := make([]chan struct{}, len(targets))
-	for i := 0; i < len(targets); i++ {
-		done := make(chan struct{}) // need for the closure
-		dones[i] = done
-
-		targets[i].Watch(Filter{
-			Topics: NewFilterTopicsFromStringsFlat("broadcast topic"),
-			Fn: func(msg *Message) {
-				close(done)
-			},
-		})
-	}
-	// Send a dummy message from the sender
-	msg := NewMessage([]byte("broadcast whisper"))
-	envelope, err := msg.Wrap(DefaultPoW, Options{
-		Topics: NewTopicsFromStrings("broadcast topic"),
-		TTL:    DefaultTTL,
-	})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if err := sender.Send(envelope); err != nil {
-		t.Fatalf("failed to send broadcast message: %v", err)
-	}
-	// Wait for an arrival on each recipient, or timeouts
-	timeout := time.After(time.Second)
-	for _, done := range dones {
-		select {
-		case <-done:
-		case <-timeout:
-			t.Fatalf("broadcast message receive timeout")
-		}
-	}
-}
-
-func TestMessageExpiration(t *testing.T) {
-	// Start the single node cluster and inject a dummy message
-	node := startTestCluster(1)[0]
-
-	message := NewMessage([]byte("expiring message"))
-	envelope, err := message.Wrap(DefaultPoW, Options{TTL: time.Second})
-	if err != nil {
-		t.Fatalf("failed to wrap message: %v", err)
-	}
-	if err := node.Send(envelope); err != nil {
-		t.Fatalf("failed to inject message: %v", err)
-	}
-	// Check that the message is inside the cache
-	node.poolMu.RLock()
-	_, found := node.messages[envelope.Hash()]
-	node.poolMu.RUnlock()
-
-	if !found {
-		t.Fatalf("message not found in cache")
-	}
-	// Wait for expiration and check cache again
-	time.Sleep(time.Second)         // wait for expiration
-	time.Sleep(2 * expirationCycle) // wait for cleanup cycle
-
-	node.poolMu.RLock()
-	_, found = node.messages[envelope.Hash()]
-	node.poolMu.RUnlock()
-	if found {
-		t.Fatalf("message not expired from cache")
-	}
-
-	// Check that adding an expired envelope doesn't do anything.
-	node.add(envelope)
-	node.poolMu.RLock()
-	_, found = node.messages[envelope.Hash()]
-	node.poolMu.RUnlock()
-	if found {
-		t.Fatalf("message was added to cache")
-	}
-}
diff --git a/whisper/whisperv2/api.go b/whisper/whisperv2/api.go
new file mode 100644
index 000000000..9c9c6a84c
--- /dev/null
+++ b/whisper/whisperv2/api.go
@@ -0,0 +1,413 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"encoding/json"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/rpc"
+)
+
+// PublicWhisperAPI provides the whisper RPC service.
+type PublicWhisperAPI struct {
+	w *Whisper
+
+	messagesMu sync.RWMutex
+	messages   map[int]*whisperFilter
+}
+
+type whisperOfflineError struct{}
+
+func (e *whisperOfflineError) Error() string {
+	return "whisper is offline"
+}
+
+// whisperOffLineErr is returned when the node doesn't offer the shh service.
+var whisperOffLineErr = new(whisperOfflineError)
+
+// NewPublicWhisperAPI create a new RPC whisper service.
+func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
+	return &PublicWhisperAPI{w: w, messages: make(map[int]*whisperFilter)}
+}
+
+// Version returns the Whisper version this node offers.
+func (s *PublicWhisperAPI) Version() (*rpc.HexNumber, error) {
+	if s.w == nil {
+		return rpc.NewHexNumber(0), whisperOffLineErr
+	}
+	return rpc.NewHexNumber(s.w.Version()), nil
+}
+
+// HasIdentity checks if the the whisper node is configured with the private key
+// of the specified public pair.
+func (s *PublicWhisperAPI) HasIdentity(identity string) (bool, error) {
+	if s.w == nil {
+		return false, whisperOffLineErr
+	}
+	return s.w.HasIdentity(crypto.ToECDSAPub(common.FromHex(identity))), nil
+}
+
+// NewIdentity generates a new cryptographic identity for the client, and injects
+// it into the known identities for message decryption.
+func (s *PublicWhisperAPI) NewIdentity() (string, error) {
+	if s.w == nil {
+		return "", whisperOffLineErr
+	}
+
+	identity := s.w.NewIdentity()
+	return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil
+}
+
+type NewFilterArgs struct {
+	To     string
+	From   string
+	Topics [][][]byte
+}
+
+// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages.
+func (s *PublicWhisperAPI) NewFilter(args NewFilterArgs) (*rpc.HexNumber, error) {
+	if s.w == nil {
+		return nil, whisperOffLineErr
+	}
+
+	var id int
+	filter := Filter{
+		To:     crypto.ToECDSAPub(common.FromHex(args.To)),
+		From:   crypto.ToECDSAPub(common.FromHex(args.From)),
+		Topics: NewFilterTopics(args.Topics...),
+		Fn: func(message *Message) {
+			wmsg := NewWhisperMessage(message)
+			s.messagesMu.RLock() // Only read lock to the filter pool
+			defer s.messagesMu.RUnlock()
+			if s.messages[id] != nil {
+				s.messages[id].insert(wmsg)
+			}
+		},
+	}
+
+	id = s.w.Watch(filter)
+
+	s.messagesMu.Lock()
+	s.messages[id] = newWhisperFilter(id, s.w)
+	s.messagesMu.Unlock()
+
+	return rpc.NewHexNumber(id), nil
+}
+
+// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval.
+func (s *PublicWhisperAPI) GetFilterChanges(filterId rpc.HexNumber) []WhisperMessage {
+	s.messagesMu.RLock()
+	defer s.messagesMu.RUnlock()
+
+	if s.messages[filterId.Int()] != nil {
+		if changes := s.messages[filterId.Int()].retrieve(); changes != nil {
+			return changes
+		}
+	}
+	return returnWhisperMessages(nil)
+}
+
+// UninstallFilter disables and removes an existing filter.
+func (s *PublicWhisperAPI) UninstallFilter(filterId rpc.HexNumber) bool {
+	s.messagesMu.Lock()
+	defer s.messagesMu.Unlock()
+
+	if _, ok := s.messages[filterId.Int()]; ok {
+		delete(s.messages, filterId.Int())
+		return true
+	}
+	return false
+}
+
+// GetMessages retrieves all the known messages that match a specific filter.
+func (s *PublicWhisperAPI) GetMessages(filterId rpc.HexNumber) []WhisperMessage {
+	// Retrieve all the cached messages matching a specific, existing filter
+	s.messagesMu.RLock()
+	defer s.messagesMu.RUnlock()
+
+	var messages []*Message
+	if s.messages[filterId.Int()] != nil {
+		messages = s.messages[filterId.Int()].messages()
+	}
+
+	return returnWhisperMessages(messages)
+}
+
+// returnWhisperMessages converts aNhisper message to a RPC whisper message.
+func returnWhisperMessages(messages []*Message) []WhisperMessage {
+	msgs := make([]WhisperMessage, len(messages))
+	for i, msg := range messages {
+		msgs[i] = NewWhisperMessage(msg)
+	}
+	return msgs
+}
+
+type PostArgs struct {
+	From     string   `json:"from"`
+	To       string   `json:"to"`
+	Topics   [][]byte `json:"topics"`
+	Payload  string   `json:"payload"`
+	Priority int64    `json:"priority"`
+	TTL      int64    `json:"ttl"`
+}
+
+// Post injects a message into the whisper network for distribution.
+func (s *PublicWhisperAPI) Post(args PostArgs) (bool, error) {
+	if s.w == nil {
+		return false, whisperOffLineErr
+	}
+
+	// construct whisper message with transmission options
+	message := NewMessage(common.FromHex(args.Payload))
+	options := Options{
+		To:     crypto.ToECDSAPub(common.FromHex(args.To)),
+		TTL:    time.Duration(args.TTL) * time.Second,
+		Topics: NewTopics(args.Topics...),
+	}
+
+	// set sender identity
+	if len(args.From) > 0 {
+		if key := s.w.GetIdentity(crypto.ToECDSAPub(common.FromHex(args.From))); key != nil {
+			options.From = key
+		} else {
+			return false, fmt.Errorf("unknown identity to send from: %s", args.From)
+		}
+	}
+
+	// Wrap and send the message
+	pow := time.Duration(args.Priority) * time.Millisecond
+	envelope, err := message.Wrap(pow, options)
+	if err != nil {
+		return false, err
+	}
+
+	return true, s.w.Send(envelope)
+}
+
+// WhisperMessage is the RPC representation of a whisper message.
+type WhisperMessage struct {
+	ref *Message
+
+	Payload string `json:"payload"`
+	To      string `json:"to"`
+	From    string `json:"from"`
+	Sent    int64  `json:"sent"`
+	TTL     int64  `json:"ttl"`
+	Hash    string `json:"hash"`
+}
+
+func (args *PostArgs) UnmarshalJSON(data []byte) (err error) {
+	var obj struct {
+		From     string        `json:"from"`
+		To       string        `json:"to"`
+		Topics   []string      `json:"topics"`
+		Payload  string        `json:"payload"`
+		Priority rpc.HexNumber `json:"priority"`
+		TTL      rpc.HexNumber `json:"ttl"`
+	}
+
+	if err := json.Unmarshal(data, &obj); err != nil {
+		return err
+	}
+
+	args.From = obj.From
+	args.To = obj.To
+	args.Payload = obj.Payload
+	args.Priority = obj.Priority.Int64()
+	args.TTL = obj.TTL.Int64()
+
+	// decode topic strings
+	args.Topics = make([][]byte, len(obj.Topics))
+	for i, topic := range obj.Topics {
+		args.Topics[i] = common.FromHex(topic)
+	}
+
+	return nil
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
+// JSON message blob into a WhisperFilterArgs structure.
+func (args *NewFilterArgs) UnmarshalJSON(b []byte) (err error) {
+	// Unmarshal the JSON message and sanity check
+	var obj struct {
+		To     interface{} `json:"to"`
+		From   interface{} `json:"from"`
+		Topics interface{} `json:"topics"`
+	}
+	if err := json.Unmarshal(b, &obj); err != nil {
+		return err
+	}
+
+	// Retrieve the simple data contents of the filter arguments
+	if obj.To == nil {
+		args.To = ""
+	} else {
+		argstr, ok := obj.To.(string)
+		if !ok {
+			return fmt.Errorf("to is not a string")
+		}
+		args.To = argstr
+	}
+	if obj.From == nil {
+		args.From = ""
+	} else {
+		argstr, ok := obj.From.(string)
+		if !ok {
+			return fmt.Errorf("from is not a string")
+		}
+		args.From = argstr
+	}
+	// Construct the nested topic array
+	if obj.Topics != nil {
+		// Make sure we have an actual topic array
+		list, ok := obj.Topics.([]interface{})
+		if !ok {
+			return fmt.Errorf("topics is not an array")
+		}
+		// Iterate over each topic and handle nil, string or array
+		topics := make([][]string, len(list))
+		for idx, field := range list {
+			switch value := field.(type) {
+			case nil:
+				topics[idx] = []string{}
+
+			case string:
+				topics[idx] = []string{value}
+
+			case []interface{}:
+				topics[idx] = make([]string, len(value))
+				for i, nested := range value {
+					switch value := nested.(type) {
+					case nil:
+						topics[idx][i] = ""
+
+					case string:
+						topics[idx][i] = value
+
+					default:
+						return fmt.Errorf("topic[%d][%d] is not a string", idx, i)
+					}
+				}
+			default:
+				return fmt.Errorf("topic[%d] not a string or array", idx)
+			}
+		}
+
+		topicsDecoded := make([][][]byte, len(topics))
+		for i, condition := range topics {
+			topicsDecoded[i] = make([][]byte, len(condition))
+			for j, topic := range condition {
+				topicsDecoded[i][j] = common.FromHex(topic)
+			}
+		}
+
+		args.Topics = topicsDecoded
+	}
+	return nil
+}
+
+// whisperFilter is the message cache matching a specific filter, accumulating
+// inbound messages until the are requested by the client.
+type whisperFilter struct {
+	id  int      // Filter identifier for old message retrieval
+	ref *Whisper // Whisper reference for old message retrieval
+
+	cache  []WhisperMessage         // Cache of messages not yet polled
+	skip   map[common.Hash]struct{} // List of retrieved messages to avoid duplication
+	update time.Time                // Time of the last message query
+
+	lock sync.RWMutex // Lock protecting the filter internals
+}
+
+// messages retrieves all the cached messages from the entire pool matching the
+// filter, resetting the filter's change buffer.
+func (w *whisperFilter) messages() []*Message {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+
+	w.cache = nil
+	w.update = time.Now()
+
+	w.skip = make(map[common.Hash]struct{})
+	messages := w.ref.Messages(w.id)
+	for _, message := range messages {
+		w.skip[message.Hash] = struct{}{}
+	}
+	return messages
+}
+
+// insert injects a new batch of messages into the filter cache.
+func (w *whisperFilter) insert(messages ...WhisperMessage) {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+
+	for _, message := range messages {
+		if _, ok := w.skip[message.ref.Hash]; !ok {
+			w.cache = append(w.cache, messages...)
+		}
+	}
+}
+
+// retrieve fetches all the cached messages from the filter.
+func (w *whisperFilter) retrieve() (messages []WhisperMessage) {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+
+	messages, w.cache = w.cache, nil
+	w.update = time.Now()
+
+	return
+}
+
+// activity returns the last time instance when client requests were executed on
+// the filter.
+func (w *whisperFilter) activity() time.Time {
+	w.lock.RLock()
+	defer w.lock.RUnlock()
+
+	return w.update
+}
+
+// newWhisperFilter creates a new serialized, poll based whisper topic filter.
+func newWhisperFilter(id int, ref *Whisper) *whisperFilter {
+	return &whisperFilter{
+		id:  id,
+		ref: ref,
+
+		update: time.Now(),
+		skip:   make(map[common.Hash]struct{}),
+	}
+}
+
+// NewWhisperMessage converts an internal message into an API version.
+func NewWhisperMessage(message *Message) WhisperMessage {
+	return WhisperMessage{
+		ref: message,
+
+		Payload: common.ToHex(message.Payload),
+		From:    common.ToHex(crypto.FromECDSAPub(message.Recover())),
+		To:      common.ToHex(crypto.FromECDSAPub(message.To)),
+		Sent:    message.Sent.Unix(),
+		TTL:     int64(message.TTL / time.Second),
+		Hash:    common.ToHex(message.Hash.Bytes()),
+	}
+}
diff --git a/whisper/whisperv2/doc.go b/whisper/whisperv2/doc.go
new file mode 100644
index 000000000..7252f44b1
--- /dev/null
+++ b/whisper/whisperv2/doc.go
@@ -0,0 +1,32 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+/*
+Package whisper implements the Whisper PoC-1.
+
+(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec)
+
+Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
+As such it may be likened and compared to both, not dissimilar to the
+matter/energy duality (apologies to physicists for the blatant abuse of a
+fundamental and beautiful natural principle).
+
+Whisper is a pure identity-based messaging system. Whisper provides a low-level
+(non-application-specific) but easily-accessible API without being based upon
+or prejudiced by the low-level hardware attributes and characteristics,
+particularly the notion of singular endpoints.
+*/
+package whisperv2
diff --git a/whisper/whisperv2/envelope.go b/whisper/whisperv2/envelope.go
new file mode 100644
index 000000000..7110ab457
--- /dev/null
+++ b/whisper/whisperv2/envelope.go
@@ -0,0 +1,147 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Contains the Whisper protocol Envelope element. For formal details please see
+// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#envelopes.
+
+package whisperv2
+
+import (
+	"crypto/ecdsa"
+	"encoding/binary"
+	"fmt"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/crypto/ecies"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// Envelope represents a clear-text data packet to transmit through the Whisper
+// network. Its contents may or may not be encrypted and signed.
+type Envelope struct {
+	Expiry uint32 // Whisper protocol specifies int32, really should be int64
+	TTL    uint32 // ^^^^^^
+	Topics []Topic
+	Data   []byte
+	Nonce  uint32
+
+	hash common.Hash // Cached hash of the envelope to avoid rehashing every time
+}
+
+// NewEnvelope wraps a Whisper message with expiration and destination data
+// included into an envelope for network forwarding.
+func NewEnvelope(ttl time.Duration, topics []Topic, msg *Message) *Envelope {
+	return &Envelope{
+		Expiry: uint32(time.Now().Add(ttl).Unix()),
+		TTL:    uint32(ttl.Seconds()),
+		Topics: topics,
+		Data:   msg.bytes(),
+		Nonce:  0,
+	}
+}
+
+// Seal closes the envelope by spending the requested amount of time as a proof
+// of work on hashing the data.
+func (self *Envelope) Seal(pow time.Duration) {
+	d := make([]byte, 64)
+	copy(d[:32], self.rlpWithoutNonce())
+
+	finish, bestBit := time.Now().Add(pow).UnixNano(), 0
+	for nonce := uint32(0); time.Now().UnixNano() < finish; {
+		for i := 0; i < 1024; i++ {
+			binary.BigEndian.PutUint32(d[60:], nonce)
+
+			firstBit := common.FirstBitSet(common.BigD(crypto.Keccak256(d)))
+			if firstBit > bestBit {
+				self.Nonce, bestBit = nonce, firstBit
+			}
+			nonce++
+		}
+	}
+}
+
+// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
+func (self *Envelope) rlpWithoutNonce() []byte {
+	enc, _ := rlp.EncodeToBytes([]interface{}{self.Expiry, self.TTL, self.Topics, self.Data})
+	return enc
+}
+
+// Open extracts the message contained within a potentially encrypted envelope.
+func (self *Envelope) Open(key *ecdsa.PrivateKey) (msg *Message, err error) {
+	// Split open the payload into a message construct
+	data := self.Data
+
+	message := &Message{
+		Flags: data[0],
+		Sent:  time.Unix(int64(self.Expiry-self.TTL), 0),
+		TTL:   time.Duration(self.TTL) * time.Second,
+		Hash:  self.Hash(),
+	}
+	data = data[1:]
+
+	if message.Flags&signatureFlag == signatureFlag {
+		if len(data) < signatureLength {
+			return nil, fmt.Errorf("unable to open envelope. First bit set but len(data) < len(signature)")
+		}
+		message.Signature, data = data[:signatureLength], data[signatureLength:]
+	}
+	message.Payload = data
+
+	// Decrypt the message, if requested
+	if key == nil {
+		return message, nil
+	}
+	err = message.decrypt(key)
+	switch err {
+	case nil:
+		return message, nil
+
+	case ecies.ErrInvalidPublicKey: // Payload isn't encrypted
+		return message, err
+
+	default:
+		return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
+	}
+}
+
+// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
+func (self *Envelope) Hash() common.Hash {
+	if (self.hash == common.Hash{}) {
+		enc, _ := rlp.EncodeToBytes(self)
+		self.hash = crypto.Keccak256Hash(enc)
+	}
+	return self.hash
+}
+
+// DecodeRLP decodes an Envelope from an RLP data stream.
+func (self *Envelope) DecodeRLP(s *rlp.Stream) error {
+	raw, err := s.Raw()
+	if err != nil {
+		return err
+	}
+	// The decoding of Envelope uses the struct fields but also needs
+	// to compute the hash of the whole RLP-encoded envelope. This
+	// type has the same structure as Envelope but is not an
+	// rlp.Decoder so we can reuse the Envelope struct definition.
+	type rlpenv Envelope
+	if err := rlp.DecodeBytes(raw, (*rlpenv)(self)); err != nil {
+		return err
+	}
+	self.hash = crypto.Keccak256Hash(raw)
+	return nil
+}
diff --git a/whisper/whisperv2/envelope_test.go b/whisper/whisperv2/envelope_test.go
new file mode 100644
index 000000000..75e2fbe8a
--- /dev/null
+++ b/whisper/whisperv2/envelope_test.go
@@ -0,0 +1,158 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"bytes"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/crypto/ecies"
+)
+
+func TestEnvelopeOpen(t *testing.T) {
+	payload := []byte("hello world")
+	message := NewMessage(payload)
+
+	envelope, err := message.Wrap(DefaultPoW, Options{})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	opened, err := envelope.Open(nil)
+	if err != nil {
+		t.Fatalf("failed to open envelope: %v", err)
+	}
+	if opened.Flags != message.Flags {
+		t.Fatalf("flags mismatch: have %d, want %d", opened.Flags, message.Flags)
+	}
+	if bytes.Compare(opened.Signature, message.Signature) != 0 {
+		t.Fatalf("signature mismatch: have 0x%x, want 0x%x", opened.Signature, message.Signature)
+	}
+	if bytes.Compare(opened.Payload, message.Payload) != 0 {
+		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, message.Payload)
+	}
+	if opened.Sent.Unix() != message.Sent.Unix() {
+		t.Fatalf("send time mismatch: have %d, want %d", opened.Sent, message.Sent)
+	}
+	if opened.TTL/time.Second != DefaultTTL/time.Second {
+		t.Fatalf("message TTL mismatch: have %v, want %v", opened.TTL, DefaultTTL)
+	}
+
+	if opened.Hash != envelope.Hash() {
+		t.Fatalf("message hash mismatch: have 0x%x, want 0x%x", opened.Hash, envelope.Hash())
+	}
+}
+
+func TestEnvelopeAnonymousOpenUntargeted(t *testing.T) {
+	payload := []byte("hello envelope")
+	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	opened, err := envelope.Open(nil)
+	if err != nil {
+		t.Fatalf("failed to open envelope: %v", err)
+	}
+	if opened.To != nil {
+		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
+	}
+	if bytes.Compare(opened.Payload, payload) != 0 {
+		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload)
+	}
+}
+
+func TestEnvelopeAnonymousOpenTargeted(t *testing.T) {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to generate test identity: %v", err)
+	}
+
+	payload := []byte("hello envelope")
+	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{
+		To: &key.PublicKey,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	opened, err := envelope.Open(nil)
+	if err != nil {
+		t.Fatalf("failed to open envelope: %v", err)
+	}
+	if opened.To != nil {
+		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
+	}
+	if bytes.Compare(opened.Payload, payload) == 0 {
+		t.Fatalf("payload match, should have been encrypted: 0x%x", opened.Payload)
+	}
+}
+
+func TestEnvelopeIdentifiedOpenUntargeted(t *testing.T) {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to generate test identity: %v", err)
+	}
+
+	payload := []byte("hello envelope")
+	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	opened, err := envelope.Open(key)
+	switch err {
+	case nil:
+		t.Fatalf("envelope opened with bad key: %v", opened)
+
+	case ecies.ErrInvalidPublicKey:
+		// Ok, key mismatch but opened
+
+	default:
+		t.Fatalf("failed to open envelope: %v", err)
+	}
+
+	if opened.To != nil {
+		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
+	}
+	if bytes.Compare(opened.Payload, payload) != 0 {
+		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload)
+	}
+}
+
+func TestEnvelopeIdentifiedOpenTargeted(t *testing.T) {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to generate test identity: %v", err)
+	}
+
+	payload := []byte("hello envelope")
+	envelope, err := NewMessage(payload).Wrap(DefaultPoW, Options{
+		To: &key.PublicKey,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	opened, err := envelope.Open(key)
+	if err != nil {
+		t.Fatalf("failed to open envelope: %v", err)
+	}
+	if opened.To != nil {
+		t.Fatalf("recipient mismatch: have 0x%x, want nil", opened.To)
+	}
+	if bytes.Compare(opened.Payload, payload) != 0 {
+		t.Fatalf("payload mismatch: have 0x%x, want 0x%x", opened.Payload, payload)
+	}
+}
diff --git a/whisper/whisperv2/filter.go b/whisper/whisperv2/filter.go
new file mode 100644
index 000000000..8ce4a54fb
--- /dev/null
+++ b/whisper/whisperv2/filter.go
@@ -0,0 +1,132 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Contains the message filter for fine grained subscriptions.
+
+package whisperv2
+
+import (
+	"crypto/ecdsa"
+
+	"github.com/ethereum/go-ethereum/event/filter"
+)
+
+// Filter is used to subscribe to specific types of whisper messages.
+type Filter struct {
+	To     *ecdsa.PublicKey   // Recipient of the message
+	From   *ecdsa.PublicKey   // Sender of the message
+	Topics [][]Topic          // Topics to filter messages with
+	Fn     func(msg *Message) // Handler in case of a match
+}
+
+// NewFilterTopics creates a 2D topic array used by whisper.Filter from binary
+// data elements.
+func NewFilterTopics(data ...[][]byte) [][]Topic {
+	filter := make([][]Topic, len(data))
+	for i, condition := range data {
+		// Handle the special case of condition == [[]byte{}]
+		if len(condition) == 1 && len(condition[0]) == 0 {
+			filter[i] = []Topic{}
+			continue
+		}
+		// Otherwise flatten normally
+		filter[i] = NewTopics(condition...)
+	}
+	return filter
+}
+
+// NewFilterTopicsFlat creates a 2D topic array used by whisper.Filter from flat
+// binary data elements.
+func NewFilterTopicsFlat(data ...[]byte) [][]Topic {
+	filter := make([][]Topic, len(data))
+	for i, element := range data {
+		// Only add non-wildcard topics
+		filter[i] = make([]Topic, 0, 1)
+		if len(element) > 0 {
+			filter[i] = append(filter[i], NewTopic(element))
+		}
+	}
+	return filter
+}
+
+// NewFilterTopicsFromStrings creates a 2D topic array used by whisper.Filter
+// from textual data elements.
+func NewFilterTopicsFromStrings(data ...[]string) [][]Topic {
+	filter := make([][]Topic, len(data))
+	for i, condition := range data {
+		// Handle the special case of condition == [""]
+		if len(condition) == 1 && condition[0] == "" {
+			filter[i] = []Topic{}
+			continue
+		}
+		// Otherwise flatten normally
+		filter[i] = NewTopicsFromStrings(condition...)
+	}
+	return filter
+}
+
+// NewFilterTopicsFromStringsFlat creates a 2D topic array used by whisper.Filter from flat
+// binary data elements.
+func NewFilterTopicsFromStringsFlat(data ...string) [][]Topic {
+	filter := make([][]Topic, len(data))
+	for i, element := range data {
+		// Only add non-wildcard topics
+		filter[i] = make([]Topic, 0, 1)
+		if element != "" {
+			filter[i] = append(filter[i], NewTopicFromString(element))
+		}
+	}
+	return filter
+}
+
+// filterer is the internal, fully initialized filter ready to match inbound
+// messages to a variety of criteria.
+type filterer struct {
+	to      string                 // Recipient of the message
+	from    string                 // Sender of the message
+	matcher *topicMatcher          // Topics to filter messages with
+	fn      func(data interface{}) // Handler in case of a match
+}
+
+// Compare checks if the specified filter matches the current one.
+func (self filterer) Compare(f filter.Filter) bool {
+	filter := f.(filterer)
+
+	// Check the message sender and recipient
+	if len(self.to) > 0 && self.to != filter.to {
+		return false
+	}
+	if len(self.from) > 0 && self.from != filter.from {
+		return false
+	}
+	// Check the topic filtering
+	topics := make([]Topic, len(filter.matcher.conditions))
+	for i, group := range filter.matcher.conditions {
+		// Message should contain a single topic entry, extract
+		for topics[i], _ = range group {
+			break
+		}
+	}
+	if !self.matcher.Matches(topics) {
+		return false
+	}
+	return true
+}
+
+// Trigger is called when a filter successfully matches an inbound message.
+func (self filterer) Trigger(data interface{}) {
+	self.fn(data)
+}
diff --git a/whisper/whisperv2/filter_test.go b/whisper/whisperv2/filter_test.go
new file mode 100644
index 000000000..5a14a84bb
--- /dev/null
+++ b/whisper/whisperv2/filter_test.go
@@ -0,0 +1,215 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"bytes"
+
+	"testing"
+)
+
+var filterTopicsCreationTests = []struct {
+	topics [][]string
+	filter [][][4]byte
+}{
+	{ // Simple topic filter
+		topics: [][]string{
+			{"abc", "def", "ghi"},
+			{"def"},
+			{"ghi", "abc"},
+		},
+		filter: [][][4]byte{
+			{{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9}},
+			{{0x34, 0x60, 0x7c, 0x9b}},
+			{{0x21, 0x41, 0x7d, 0xf9}, {0x4e, 0x03, 0x65, 0x7a}},
+		},
+	},
+	{ // Wild-carded topic filter
+		topics: [][]string{
+			{"abc", "def", "ghi"},
+			{},
+			{""},
+			{"def"},
+		},
+		filter: [][][4]byte{
+			{{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9}},
+			{},
+			{},
+			{{0x34, 0x60, 0x7c, 0x9b}},
+		},
+	},
+}
+
+var filterTopicsCreationFlatTests = []struct {
+	topics []string
+	filter [][][4]byte
+}{
+	{ // Simple topic list
+		topics: []string{"abc", "def", "ghi"},
+		filter: [][][4]byte{
+			{{0x4e, 0x03, 0x65, 0x7a}},
+			{{0x34, 0x60, 0x7c, 0x9b}},
+			{{0x21, 0x41, 0x7d, 0xf9}},
+		},
+	},
+	{ // Wild-carded topic list
+		topics: []string{"abc", "", "ghi"},
+		filter: [][][4]byte{
+			{{0x4e, 0x03, 0x65, 0x7a}},
+			{},
+			{{0x21, 0x41, 0x7d, 0xf9}},
+		},
+	},
+}
+
+func TestFilterTopicsCreation(t *testing.T) {
+	// Check full filter creation
+	for i, tt := range filterTopicsCreationTests {
+		// Check the textual creation
+		filter := NewFilterTopicsFromStrings(tt.topics...)
+		if len(filter) != len(tt.topics) {
+			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
+			continue
+		}
+		for j, condition := range filter {
+			if len(condition) != len(tt.filter[j]) {
+				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
+				continue
+			}
+			for k := 0; k < len(condition); k++ {
+				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
+					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
+				}
+			}
+		}
+		// Check the binary creation
+		binary := make([][][]byte, len(tt.topics))
+		for j, condition := range tt.topics {
+			binary[j] = make([][]byte, len(condition))
+			for k, segment := range condition {
+				binary[j][k] = []byte(segment)
+			}
+		}
+		filter = NewFilterTopics(binary...)
+		if len(filter) != len(tt.topics) {
+			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
+			continue
+		}
+		for j, condition := range filter {
+			if len(condition) != len(tt.filter[j]) {
+				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
+				continue
+			}
+			for k := 0; k < len(condition); k++ {
+				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
+					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
+				}
+			}
+		}
+	}
+	// Check flat filter creation
+	for i, tt := range filterTopicsCreationFlatTests {
+		// Check the textual creation
+		filter := NewFilterTopicsFromStringsFlat(tt.topics...)
+		if len(filter) != len(tt.topics) {
+			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
+			continue
+		}
+		for j, condition := range filter {
+			if len(condition) != len(tt.filter[j]) {
+				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
+				continue
+			}
+			for k := 0; k < len(condition); k++ {
+				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
+					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
+				}
+			}
+		}
+		// Check the binary creation
+		binary := make([][]byte, len(tt.topics))
+		for j, topic := range tt.topics {
+			binary[j] = []byte(topic)
+		}
+		filter = NewFilterTopicsFlat(binary...)
+		if len(filter) != len(tt.topics) {
+			t.Errorf("test %d: condition count mismatch: have %v, want %v", i, len(filter), len(tt.topics))
+			continue
+		}
+		for j, condition := range filter {
+			if len(condition) != len(tt.filter[j]) {
+				t.Errorf("test %d, condition %d: size mismatch: have %v, want %v", i, j, len(condition), len(tt.filter[j]))
+				continue
+			}
+			for k := 0; k < len(condition); k++ {
+				if bytes.Compare(condition[k][:], tt.filter[j][k][:]) != 0 {
+					t.Errorf("test %d, condition %d, segment %d: filter mismatch: have 0x%x, want 0x%x", i, j, k, condition[k], tt.filter[j][k])
+				}
+			}
+		}
+	}
+}
+
+var filterCompareTests = []struct {
+	matcher filterer
+	message filterer
+	match   bool
+}{
+	{ // Wild-card filter matching anything
+		matcher: filterer{to: "", from: "", matcher: newTopicMatcher()},
+		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		match:   true,
+	},
+	{ // Filter matching the to field
+		matcher: filterer{to: "to", from: "", matcher: newTopicMatcher()},
+		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		match:   true,
+	},
+	{ // Filter rejecting the to field
+		matcher: filterer{to: "to", from: "", matcher: newTopicMatcher()},
+		message: filterer{to: "", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		match:   false,
+	},
+	{ // Filter matching the from field
+		matcher: filterer{to: "", from: "from", matcher: newTopicMatcher()},
+		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		match:   true,
+	},
+	{ // Filter rejecting the from field
+		matcher: filterer{to: "", from: "from", matcher: newTopicMatcher()},
+		message: filterer{to: "to", from: "", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		match:   false,
+	},
+	{ // Filter matching the topic field
+		matcher: filterer{to: "", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		message: filterer{to: "to", from: "from", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		match:   true,
+	},
+	{ // Filter rejecting the topic field
+		matcher: filterer{to: "", from: "", matcher: newTopicMatcher(NewFilterTopicsFromStringsFlat("topic")...)},
+		message: filterer{to: "to", from: "from", matcher: newTopicMatcher()},
+		match:   false,
+	},
+}
+
+func TestFilterCompare(t *testing.T) {
+	for i, tt := range filterCompareTests {
+		if match := tt.matcher.Compare(tt.message); match != tt.match {
+			t.Errorf("test %d: match mismatch: have %v, want %v", i, match, tt.match)
+		}
+	}
+}
diff --git a/whisper/whisperv2/main.go b/whisper/whisperv2/main.go
new file mode 100644
index 000000000..be4160489
--- /dev/null
+++ b/whisper/whisperv2/main.go
@@ -0,0 +1,106 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// +build none
+
+// Contains a simple whisper peer setup and self messaging to allow playing
+// around with the protocol and API without a fancy client implementation.
+
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/nat"
+	"github.com/ethereum/go-ethereum/whisper"
+)
+
+func main() {
+	logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.InfoLevel))
+
+	// Generate the peer identity
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		fmt.Printf("Failed to generate peer key: %v.\n", err)
+		os.Exit(-1)
+	}
+	name := common.MakeName("whisper-go", "1.0")
+	shh := whisper.New()
+
+	// Create an Ethereum peer to communicate through
+	server := p2p.Server{
+		PrivateKey: key,
+		MaxPeers:   10,
+		Name:       name,
+		Protocols:  []p2p.Protocol{shh.Protocol()},
+		ListenAddr: ":30300",
+		NAT:        nat.Any(),
+	}
+	fmt.Println("Starting Ethereum peer...")
+	if err := server.Start(); err != nil {
+		fmt.Printf("Failed to start Ethereum peer: %v.\n", err)
+		os.Exit(1)
+	}
+
+	// Send a message to self to check that something works
+	payload := fmt.Sprintf("Hello world, this is %v. In case you're wondering, the time is %v", name, time.Now())
+	if err := selfSend(shh, []byte(payload)); err != nil {
+		fmt.Printf("Failed to self message: %v.\n", err)
+		os.Exit(-1)
+	}
+}
+
+// SendSelf wraps a payload into a Whisper envelope and forwards it to itself.
+func selfSend(shh *whisper.Whisper, payload []byte) error {
+	ok := make(chan struct{})
+
+	// Start watching for self messages, output any arrivals
+	id := shh.NewIdentity()
+	shh.Watch(whisper.Filter{
+		To: &id.PublicKey,
+		Fn: func(msg *whisper.Message) {
+			fmt.Printf("Message received: %s, signed with 0x%x.\n", string(msg.Payload), msg.Signature)
+			close(ok)
+		},
+	})
+	// Wrap the payload and encrypt it
+	msg := whisper.NewMessage(payload)
+	envelope, err := msg.Wrap(whisper.DefaultPoW, whisper.Options{
+		From: id,
+		To:   &id.PublicKey,
+		TTL:  whisper.DefaultTTL,
+	})
+	if err != nil {
+		return fmt.Errorf("failed to seal message: %v", err)
+	}
+	// Dump the message into the system and wait for it to pop back out
+	if err := shh.Send(envelope); err != nil {
+		return fmt.Errorf("failed to send self-message: %v", err)
+	}
+	select {
+	case <-ok:
+	case <-time.After(time.Second):
+		return fmt.Errorf("failed to receive message in time")
+	}
+	return nil
+}
diff --git a/whisper/whisperv2/message.go b/whisper/whisperv2/message.go
new file mode 100644
index 000000000..7ef9d0912
--- /dev/null
+++ b/whisper/whisperv2/message.go
@@ -0,0 +1,156 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Contains the Whisper protocol Message element. For formal details please see
+// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages.
+
+package whisperv2
+
+import (
+	"crypto/ecdsa"
+	"math/rand"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+)
+
+// Message represents an end-user data packet to transmit through the Whisper
+// protocol. These are wrapped into Envelopes that need not be understood by
+// intermediate nodes, just forwarded.
+type Message struct {
+	Flags     byte // First bit is signature presence, rest reserved and should be random
+	Signature []byte
+	Payload   []byte
+
+	Sent time.Time     // Time when the message was posted into the network
+	TTL  time.Duration // Maximum time to live allowed for the message
+
+	To   *ecdsa.PublicKey // Message recipient (identity used to decode the message)
+	Hash common.Hash      // Message envelope hash to act as a unique id
+}
+
+// Options specifies the exact way a message should be wrapped into an Envelope.
+type Options struct {
+	From   *ecdsa.PrivateKey
+	To     *ecdsa.PublicKey
+	TTL    time.Duration
+	Topics []Topic
+}
+
+// NewMessage creates and initializes a non-signed, non-encrypted Whisper message.
+func NewMessage(payload []byte) *Message {
+	// Construct an initial flag set: no signature, rest random
+	flags := byte(rand.Intn(256))
+	flags &= ^signatureFlag
+
+	// Assemble and return the message
+	return &Message{
+		Flags:   flags,
+		Payload: payload,
+		Sent:    time.Now(),
+	}
+}
+
+// Wrap bundles the message into an Envelope to transmit over the network.
+//
+// pow (Proof Of Work) controls how much time to spend on hashing the message,
+// inherently controlling its priority through the network (smaller hash, bigger
+// priority).
+//
+// The user can control the amount of identity, privacy and encryption through
+// the options parameter as follows:
+//   - options.From == nil && options.To == nil: anonymous broadcast
+//   - options.From != nil && options.To == nil: signed broadcast (known sender)
+//   - options.From == nil && options.To != nil: encrypted anonymous message
+//   - options.From != nil && options.To != nil: encrypted signed message
+func (self *Message) Wrap(pow time.Duration, options Options) (*Envelope, error) {
+	// Use the default TTL if non was specified
+	if options.TTL == 0 {
+		options.TTL = DefaultTTL
+	}
+	self.TTL = options.TTL
+
+	// Sign and encrypt the message if requested
+	if options.From != nil {
+		if err := self.sign(options.From); err != nil {
+			return nil, err
+		}
+	}
+	if options.To != nil {
+		if err := self.encrypt(options.To); err != nil {
+			return nil, err
+		}
+	}
+	// Wrap the processed message, seal it and return
+	envelope := NewEnvelope(options.TTL, options.Topics, self)
+	envelope.Seal(pow)
+
+	return envelope, nil
+}
+
+// sign calculates and sets the cryptographic signature for the message , also
+// setting the sign flag.
+func (self *Message) sign(key *ecdsa.PrivateKey) (err error) {
+	self.Flags |= signatureFlag
+	self.Signature, err = crypto.Sign(self.hash(), key)
+	return
+}
+
+// Recover retrieves the public key of the message signer.
+func (self *Message) Recover() *ecdsa.PublicKey {
+	defer func() { recover() }() // in case of invalid signature
+
+	// Short circuit if no signature is present
+	if self.Signature == nil {
+		return nil
+	}
+	// Otherwise try and recover the signature
+	pub, err := crypto.SigToPub(self.hash(), self.Signature)
+	if err != nil {
+		glog.V(logger.Error).Infof("Could not get public key from signature: %v", err)
+		return nil
+	}
+	return pub
+}
+
+// encrypt encrypts a message payload with a public key.
+func (self *Message) encrypt(key *ecdsa.PublicKey) (err error) {
+	self.Payload, err = crypto.Encrypt(key, self.Payload)
+	return
+}
+
+// decrypt decrypts an encrypted payload with a private key.
+func (self *Message) decrypt(key *ecdsa.PrivateKey) error {
+	cleartext, err := crypto.Decrypt(key, self.Payload)
+	if err == nil {
+		self.Payload = cleartext
+	}
+	return err
+}
+
+// hash calculates the SHA3 checksum of the message flags and payload.
+func (self *Message) hash() []byte {
+	return crypto.Keccak256(append([]byte{self.Flags}, self.Payload...))
+}
+
+// bytes flattens the message contents (flags, signature and payload) into a
+// single binary blob.
+func (self *Message) bytes() []byte {
+	return append([]byte{self.Flags}, append(self.Signature, self.Payload...)...)
+}
diff --git a/whisper/whisperv2/message_test.go b/whisper/whisperv2/message_test.go
new file mode 100644
index 000000000..efa64e431
--- /dev/null
+++ b/whisper/whisperv2/message_test.go
@@ -0,0 +1,159 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"bytes"
+	"crypto/elliptic"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/crypto/secp256k1"
+)
+
+// Tests whether a message can be wrapped without any identity or encryption.
+func TestMessageSimpleWrap(t *testing.T) {
+	payload := []byte("hello world")
+
+	msg := NewMessage(payload)
+	if _, err := msg.Wrap(DefaultPoW, Options{}); err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if msg.Flags&signatureFlag != 0 {
+		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, 0)
+	}
+	if len(msg.Signature) != 0 {
+		t.Fatalf("signature found for simple wrapping: 0x%x", msg.Signature)
+	}
+	if bytes.Compare(msg.Payload, payload) != 0 {
+		t.Fatalf("payload mismatch after wrapping: have 0x%x, want 0x%x", msg.Payload, payload)
+	}
+	if msg.TTL/time.Second != DefaultTTL/time.Second {
+		t.Fatalf("message TTL mismatch: have %v, want %v", msg.TTL, DefaultTTL)
+	}
+}
+
+// Tests whether a message can be signed, and wrapped in plain-text.
+func TestMessageCleartextSignRecover(t *testing.T) {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to create crypto key: %v", err)
+	}
+	payload := []byte("hello world")
+
+	msg := NewMessage(payload)
+	if _, err := msg.Wrap(DefaultPoW, Options{
+		From: key,
+	}); err != nil {
+		t.Fatalf("failed to sign message: %v", err)
+	}
+	if msg.Flags&signatureFlag != signatureFlag {
+		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, signatureFlag)
+	}
+	if bytes.Compare(msg.Payload, payload) != 0 {
+		t.Fatalf("payload mismatch after signing: have 0x%x, want 0x%x", msg.Payload, payload)
+	}
+
+	pubKey := msg.Recover()
+	if pubKey == nil {
+		t.Fatalf("failed to recover public key")
+	}
+	p1 := elliptic.Marshal(secp256k1.S256(), key.PublicKey.X, key.PublicKey.Y)
+	p2 := elliptic.Marshal(secp256k1.S256(), pubKey.X, pubKey.Y)
+	if !bytes.Equal(p1, p2) {
+		t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1)
+	}
+}
+
+// Tests whether a message can be encrypted and decrypted using an anonymous
+// sender (i.e. no signature).
+func TestMessageAnonymousEncryptDecrypt(t *testing.T) {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to create recipient crypto key: %v", err)
+	}
+	payload := []byte("hello world")
+
+	msg := NewMessage(payload)
+	envelope, err := msg.Wrap(DefaultPoW, Options{
+		To: &key.PublicKey,
+	})
+	if err != nil {
+		t.Fatalf("failed to encrypt message: %v", err)
+	}
+	if msg.Flags&signatureFlag != 0 {
+		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, 0)
+	}
+	if len(msg.Signature) != 0 {
+		t.Fatalf("signature found for anonymous message: 0x%x", msg.Signature)
+	}
+
+	out, err := envelope.Open(key)
+	if err != nil {
+		t.Fatalf("failed to open encrypted message: %v", err)
+	}
+	if !bytes.Equal(out.Payload, payload) {
+		t.Errorf("payload mismatch: have 0x%x, want 0x%x", out.Payload, payload)
+	}
+}
+
+// Tests whether a message can be properly signed and encrypted.
+func TestMessageFullCrypto(t *testing.T) {
+	fromKey, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to create sender crypto key: %v", err)
+	}
+	toKey, err := crypto.GenerateKey()
+	if err != nil {
+		t.Fatalf("failed to create recipient crypto key: %v", err)
+	}
+
+	payload := []byte("hello world")
+	msg := NewMessage(payload)
+	envelope, err := msg.Wrap(DefaultPoW, Options{
+		From: fromKey,
+		To:   &toKey.PublicKey,
+	})
+	if err != nil {
+		t.Fatalf("failed to encrypt message: %v", err)
+	}
+	if msg.Flags&signatureFlag != signatureFlag {
+		t.Fatalf("signature flag mismatch: have %d, want %d", msg.Flags&signatureFlag, signatureFlag)
+	}
+	if len(msg.Signature) == 0 {
+		t.Fatalf("no signature found for signed message")
+	}
+
+	out, err := envelope.Open(toKey)
+	if err != nil {
+		t.Fatalf("failed to open encrypted message: %v", err)
+	}
+	if !bytes.Equal(out.Payload, payload) {
+		t.Errorf("payload mismatch: have 0x%x, want 0x%x", out.Payload, payload)
+	}
+
+	pubKey := out.Recover()
+	if pubKey == nil {
+		t.Fatalf("failed to recover public key")
+	}
+	p1 := elliptic.Marshal(secp256k1.S256(), fromKey.PublicKey.X, fromKey.PublicKey.Y)
+	p2 := elliptic.Marshal(secp256k1.S256(), pubKey.X, pubKey.Y)
+	if !bytes.Equal(p1, p2) {
+		t.Fatalf("public key mismatch: have 0x%x, want 0x%x", p2, p1)
+	}
+}
diff --git a/whisper/whisperv2/peer.go b/whisper/whisperv2/peer.go
new file mode 100644
index 000000000..404ebd513
--- /dev/null
+++ b/whisper/whisperv2/peer.go
@@ -0,0 +1,175 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/rlp"
+	"gopkg.in/fatih/set.v0"
+)
+
+// peer represents a whisper protocol peer connection.
+type peer struct {
+	host *Whisper
+	peer *p2p.Peer
+	ws   p2p.MsgReadWriter
+
+	known *set.Set // Messages already known by the peer to avoid wasting bandwidth
+
+	quit chan struct{}
+}
+
+// newPeer creates a new whisper peer object, but does not run the handshake itself.
+func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *peer {
+	return &peer{
+		host:  host,
+		peer:  remote,
+		ws:    rw,
+		known: set.New(),
+		quit:  make(chan struct{}),
+	}
+}
+
+// start initiates the peer updater, periodically broadcasting the whisper packets
+// into the network.
+func (self *peer) start() {
+	go self.update()
+	glog.V(logger.Debug).Infof("%v: whisper started", self.peer)
+}
+
+// stop terminates the peer updater, stopping message forwarding to it.
+func (self *peer) stop() {
+	close(self.quit)
+	glog.V(logger.Debug).Infof("%v: whisper stopped", self.peer)
+}
+
+// handshake sends the protocol initiation status message to the remote peer and
+// verifies the remote status too.
+func (self *peer) handshake() error {
+	// Send the handshake status message asynchronously
+	errc := make(chan error, 1)
+	go func() {
+		errc <- p2p.SendItems(self.ws, statusCode, protocolVersion)
+	}()
+	// Fetch the remote status packet and verify protocol match
+	packet, err := self.ws.ReadMsg()
+	if err != nil {
+		return err
+	}
+	if packet.Code != statusCode {
+		return fmt.Errorf("peer sent %x before status packet", packet.Code)
+	}
+	s := rlp.NewStream(packet.Payload, uint64(packet.Size))
+	if _, err := s.List(); err != nil {
+		return fmt.Errorf("bad status message: %v", err)
+	}
+	peerVersion, err := s.Uint()
+	if err != nil {
+		return fmt.Errorf("bad status message: %v", err)
+	}
+	if peerVersion != protocolVersion {
+		return fmt.Errorf("protocol version mismatch %d != %d", peerVersion, protocolVersion)
+	}
+	// Wait until out own status is consumed too
+	if err := <-errc; err != nil {
+		return fmt.Errorf("failed to send status packet: %v", err)
+	}
+	return nil
+}
+
+// update executes periodic operations on the peer, including message transmission
+// and expiration.
+func (self *peer) update() {
+	// Start the tickers for the updates
+	expire := time.NewTicker(expirationCycle)
+	transmit := time.NewTicker(transmissionCycle)
+
+	// Loop and transmit until termination is requested
+	for {
+		select {
+		case <-expire.C:
+			self.expire()
+
+		case <-transmit.C:
+			if err := self.broadcast(); err != nil {
+				glog.V(logger.Info).Infof("%v: broadcast failed: %v", self.peer, err)
+				return
+			}
+
+		case <-self.quit:
+			return
+		}
+	}
+}
+
+// mark marks an envelope known to the peer so that it won't be sent back.
+func (self *peer) mark(envelope *Envelope) {
+	self.known.Add(envelope.Hash())
+}
+
+// marked checks if an envelope is already known to the remote peer.
+func (self *peer) marked(envelope *Envelope) bool {
+	return self.known.Has(envelope.Hash())
+}
+
+// expire iterates over all the known envelopes in the host and removes all
+// expired (unknown) ones from the known list.
+func (self *peer) expire() {
+	// Assemble the list of available envelopes
+	available := set.NewNonTS()
+	for _, envelope := range self.host.envelopes() {
+		available.Add(envelope.Hash())
+	}
+	// Cross reference availability with known status
+	unmark := make(map[common.Hash]struct{})
+	self.known.Each(func(v interface{}) bool {
+		if !available.Has(v.(common.Hash)) {
+			unmark[v.(common.Hash)] = struct{}{}
+		}
+		return true
+	})
+	// Dump all known but unavailable
+	for hash, _ := range unmark {
+		self.known.Remove(hash)
+	}
+}
+
+// broadcast iterates over the collection of envelopes and transmits yet unknown
+// ones over the network.
+func (self *peer) broadcast() error {
+	// Fetch the envelopes and collect the unknown ones
+	envelopes := self.host.envelopes()
+	transmit := make([]*Envelope, 0, len(envelopes))
+	for _, envelope := range envelopes {
+		if !self.marked(envelope) {
+			transmit = append(transmit, envelope)
+			self.mark(envelope)
+		}
+	}
+	// Transmit the unknown batch (potentially empty)
+	if err := p2p.Send(self.ws, messagesCode, transmit); err != nil {
+		return err
+	}
+	glog.V(logger.Detail).Infoln(self.peer, "broadcasted", len(transmit), "message(s)")
+	return nil
+}
diff --git a/whisper/whisperv2/peer_test.go b/whisper/whisperv2/peer_test.go
new file mode 100644
index 000000000..9755e134c
--- /dev/null
+++ b/whisper/whisperv2/peer_test.go
@@ -0,0 +1,261 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/discover"
+)
+
+type testPeer struct {
+	client *Whisper
+	stream *p2p.MsgPipeRW
+	termed chan struct{}
+}
+
+func startTestPeer() *testPeer {
+	// Create a simulated P2P remote peer and data streams to it
+	remote := p2p.NewPeer(discover.NodeID{}, "", nil)
+	tester, tested := p2p.MsgPipe()
+
+	// Create a whisper client and connect with it to the tester peer
+	client := New()
+	client.Start(nil)
+
+	termed := make(chan struct{})
+	go func() {
+		defer client.Stop()
+		defer close(termed)
+		defer tested.Close()
+
+		client.handlePeer(remote, tested)
+	}()
+
+	return &testPeer{
+		client: client,
+		stream: tester,
+		termed: termed,
+	}
+}
+
+func startTestPeerInited() (*testPeer, error) {
+	peer := startTestPeer()
+
+	if err := p2p.ExpectMsg(peer.stream, statusCode, []uint64{protocolVersion}); err != nil {
+		peer.stream.Close()
+		return nil, err
+	}
+	if err := p2p.SendItems(peer.stream, statusCode, protocolVersion); err != nil {
+		peer.stream.Close()
+		return nil, err
+	}
+	return peer, nil
+}
+
+func TestPeerStatusMessage(t *testing.T) {
+	tester := startTestPeer()
+
+	// Wait for the handshake status message and check it
+	if err := p2p.ExpectMsg(tester.stream, statusCode, []uint64{protocolVersion}); err != nil {
+		t.Fatalf("status message mismatch: %v", err)
+	}
+	// Terminate the node
+	tester.stream.Close()
+
+	select {
+	case <-tester.termed:
+	case <-time.After(time.Second):
+		t.Fatalf("local close timed out")
+	}
+}
+
+func TestPeerHandshakeFail(t *testing.T) {
+	tester := startTestPeer()
+
+	// Wait for and check the handshake
+	if err := p2p.ExpectMsg(tester.stream, statusCode, []uint64{protocolVersion}); err != nil {
+		t.Fatalf("status message mismatch: %v", err)
+	}
+	// Send an invalid handshake status and verify disconnect
+	if err := p2p.SendItems(tester.stream, messagesCode); err != nil {
+		t.Fatalf("failed to send malformed status: %v", err)
+	}
+	select {
+	case <-tester.termed:
+	case <-time.After(time.Second):
+		t.Fatalf("remote close timed out")
+	}
+}
+
+func TestPeerHandshakeSuccess(t *testing.T) {
+	tester := startTestPeer()
+
+	// Wait for and check the handshake
+	if err := p2p.ExpectMsg(tester.stream, statusCode, []uint64{protocolVersion}); err != nil {
+		t.Fatalf("status message mismatch: %v", err)
+	}
+	// Send a valid handshake status and make sure connection stays live
+	if err := p2p.SendItems(tester.stream, statusCode, protocolVersion); err != nil {
+		t.Fatalf("failed to send status: %v", err)
+	}
+	select {
+	case <-tester.termed:
+		t.Fatalf("valid handshake disconnected")
+
+	case <-time.After(100 * time.Millisecond):
+	}
+	// Clean up the test
+	tester.stream.Close()
+
+	select {
+	case <-tester.termed:
+	case <-time.After(time.Second):
+		t.Fatalf("local close timed out")
+	}
+}
+
+func TestPeerSend(t *testing.T) {
+	// Start a tester and execute the handshake
+	tester, err := startTestPeerInited()
+	if err != nil {
+		t.Fatalf("failed to start initialized peer: %v", err)
+	}
+	defer tester.stream.Close()
+
+	// Construct a message and inject into the tester
+	message := NewMessage([]byte("peer broadcast test message"))
+	envelope, err := message.Wrap(DefaultPoW, Options{
+		TTL: DefaultTTL,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if err := tester.client.Send(envelope); err != nil {
+		t.Fatalf("failed to send message: %v", err)
+	}
+	// Check that the message is eventually forwarded
+	payload := []interface{}{envelope}
+	if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil {
+		t.Fatalf("message mismatch: %v", err)
+	}
+	// Make sure that even with a re-insert, an empty batch is received
+	if err := tester.client.Send(envelope); err != nil {
+		t.Fatalf("failed to send message: %v", err)
+	}
+	if err := p2p.ExpectMsg(tester.stream, messagesCode, []interface{}{}); err != nil {
+		t.Fatalf("message mismatch: %v", err)
+	}
+}
+
+func TestPeerDeliver(t *testing.T) {
+	// Start a tester and execute the handshake
+	tester, err := startTestPeerInited()
+	if err != nil {
+		t.Fatalf("failed to start initialized peer: %v", err)
+	}
+	defer tester.stream.Close()
+
+	// Watch for all inbound messages
+	arrived := make(chan struct{}, 1)
+	tester.client.Watch(Filter{
+		Fn: func(message *Message) {
+			arrived <- struct{}{}
+		},
+	})
+	// Construct a message and deliver it to the tester peer
+	message := NewMessage([]byte("peer broadcast test message"))
+	envelope, err := message.Wrap(DefaultPoW, Options{
+		TTL: DefaultTTL,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if err := p2p.Send(tester.stream, messagesCode, []*Envelope{envelope}); err != nil {
+		t.Fatalf("failed to transfer message: %v", err)
+	}
+	// Check that the message is delivered upstream
+	select {
+	case <-arrived:
+	case <-time.After(time.Second):
+		t.Fatalf("message delivery timeout")
+	}
+	// Check that a resend is not delivered
+	if err := p2p.Send(tester.stream, messagesCode, []*Envelope{envelope}); err != nil {
+		t.Fatalf("failed to transfer message: %v", err)
+	}
+	select {
+	case <-time.After(2 * transmissionCycle):
+	case <-arrived:
+		t.Fatalf("repeating message arrived")
+	}
+}
+
+func TestPeerMessageExpiration(t *testing.T) {
+	// Start a tester and execute the handshake
+	tester, err := startTestPeerInited()
+	if err != nil {
+		t.Fatalf("failed to start initialized peer: %v", err)
+	}
+	defer tester.stream.Close()
+
+	// Fetch the peer instance for later inspection
+	tester.client.peerMu.RLock()
+	if peers := len(tester.client.peers); peers != 1 {
+		t.Fatalf("peer pool size mismatch: have %v, want %v", peers, 1)
+	}
+	var peer *peer
+	for peer, _ = range tester.client.peers {
+		break
+	}
+	tester.client.peerMu.RUnlock()
+
+	// Construct a message and pass it through the tester
+	message := NewMessage([]byte("peer test message"))
+	envelope, err := message.Wrap(DefaultPoW, Options{
+		TTL: time.Second,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if err := tester.client.Send(envelope); err != nil {
+		t.Fatalf("failed to send message: %v", err)
+	}
+	payload := []interface{}{envelope}
+	if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil {
+		// A premature empty message may have been broadcast, check the next too
+		if err := p2p.ExpectMsg(tester.stream, messagesCode, payload); err != nil {
+			t.Fatalf("message mismatch: %v", err)
+		}
+	}
+	// Check that the message is inside the cache
+	if !peer.known.Has(envelope.Hash()) {
+		t.Fatalf("message not found in cache")
+	}
+	// Discard messages until expiration and check cache again
+	exp := time.Now().Add(time.Second + 2*expirationCycle + 100*time.Millisecond)
+	for time.Now().Before(exp) {
+		if err := p2p.ExpectMsg(tester.stream, messagesCode, []interface{}{}); err != nil {
+			t.Fatalf("message mismatch: %v", err)
+		}
+	}
+	if peer.known.Has(envelope.Hash()) {
+		t.Fatalf("message not expired from cache")
+	}
+}
diff --git a/whisper/whisperv2/topic.go b/whisper/whisperv2/topic.go
new file mode 100644
index 000000000..3e2b47bd3
--- /dev/null
+++ b/whisper/whisperv2/topic.go
@@ -0,0 +1,140 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// 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 whisperv2
+
+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.
+//
+// Note, empty topics are considered the wildcard, and cannot be used in messages.
+func NewTopic(data []byte) Topic {
+	prefix := [4]byte{}
+	copy(prefix[:], crypto.Keccak256(data)[:4])
+	return Topic(prefix)
+}
+
+// NewTopics creates a list of topics from a list of binary data elements, by
+// iteratively calling NewTopic on each of them.
+func NewTopics(data ...[]byte) []Topic {
+	topics := make([]Topic, len(data))
+	for i, element := range data {
+		topics[i] = NewTopic(element)
+	}
+	return topics
+}
+
+// NewTopicFromString creates a topic using the binary data contents of the
+// specified string.
+func NewTopicFromString(data string) Topic {
+	return NewTopic([]byte(data))
+}
+
+// NewTopicsFromStrings creates a list of topics from a list of textual data
+// elements, by iteratively calling NewTopicFromString on each of them.
+func NewTopicsFromStrings(data ...string) []Topic {
+	topics := make([]Topic, len(data))
+	for i, element := range data {
+		topics[i] = NewTopicFromString(element)
+	}
+	return topics
+}
+
+// String converts a topic byte array to a string representation.
+func (self *Topic) String() string {
+	return string(self[:])
+}
+
+// topicMatcher is a filter expression to verify if a list of topics contained
+// in an arriving message matches some topic conditions. The topic matcher is
+// built up of a list of conditions, each of which must be satisfied by the
+// corresponding topic in the message. Each condition may require: a) an exact
+// topic match; b) a match from a set of topics; or c) a wild-card matching all.
+//
+// If a message contains more topics than required by the matcher, those beyond
+// the condition count are ignored and assumed to match.
+//
+// Consider the following sample topic matcher:
+//   sample := {
+//     {TopicA1, TopicA2, TopicA3},
+//     {TopicB},
+//     nil,
+//     {TopicD1, TopicD2}
+//   }
+// In order for a message to pass this filter, it should enumerate at least 4
+// topics, the first any of [TopicA1, TopicA2, TopicA3], the second mandatory
+// "TopicB", the third is ignored by the filter and the fourth either "TopicD1"
+// or "TopicD2". If the message contains further topics, the filter will match
+// them too.
+type topicMatcher struct {
+	conditions []map[Topic]struct{}
+}
+
+// newTopicMatcher create a topic matcher from a list of topic conditions.
+func newTopicMatcher(topics ...[]Topic) *topicMatcher {
+	matcher := make([]map[Topic]struct{}, len(topics))
+	for i, condition := range topics {
+		matcher[i] = make(map[Topic]struct{})
+		for _, topic := range condition {
+			matcher[i][topic] = struct{}{}
+		}
+	}
+	return &topicMatcher{conditions: matcher}
+}
+
+// newTopicMatcherFromBinary create a topic matcher from a list of binary conditions.
+func newTopicMatcherFromBinary(data ...[][]byte) *topicMatcher {
+	topics := make([][]Topic, len(data))
+	for i, condition := range data {
+		topics[i] = NewTopics(condition...)
+	}
+	return newTopicMatcher(topics...)
+}
+
+// newTopicMatcherFromStrings creates a topic matcher from a list of textual
+// conditions.
+func newTopicMatcherFromStrings(data ...[]string) *topicMatcher {
+	topics := make([][]Topic, len(data))
+	for i, condition := range data {
+		topics[i] = NewTopicsFromStrings(condition...)
+	}
+	return newTopicMatcher(topics...)
+}
+
+// Matches checks if a list of topics matches this particular condition set.
+func (self *topicMatcher) Matches(topics []Topic) bool {
+	// Mismatch if there aren't enough topics
+	if len(self.conditions) > len(topics) {
+		return false
+	}
+	// Check each topic condition for existence (skip wild-cards)
+	for i := 0; i < len(topics) && i < len(self.conditions); i++ {
+		if len(self.conditions[i]) > 0 {
+			if _, ok := self.conditions[i][topics[i]]; !ok {
+				return false
+			}
+		}
+	}
+	return true
+}
diff --git a/whisper/whisperv2/topic_test.go b/whisper/whisperv2/topic_test.go
new file mode 100644
index 000000000..efd4a2c61
--- /dev/null
+++ b/whisper/whisperv2/topic_test.go
@@ -0,0 +1,215 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"bytes"
+	"testing"
+)
+
+var topicCreationTests = []struct {
+	data []byte
+	hash [4]byte
+}{
+	{hash: [4]byte{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte("test name")},
+	{hash: [4]byte{0xf2, 0x6e, 0x77, 0x79}, data: []byte("some other test")},
+}
+
+func TestTopicCreation(t *testing.T) {
+	// Create the topics individually
+	for i, tt := range topicCreationTests {
+		topic := NewTopic(tt.data)
+		if bytes.Compare(topic[:], tt.hash[:]) != 0 {
+			t.Errorf("binary test %d: hash mismatch: have %v, want %v.", i, topic, tt.hash)
+		}
+	}
+	for i, tt := range topicCreationTests {
+		topic := NewTopicFromString(string(tt.data))
+		if bytes.Compare(topic[:], tt.hash[:]) != 0 {
+			t.Errorf("textual test %d: hash mismatch: have %v, want %v.", i, topic, tt.hash)
+		}
+	}
+	// Create the topics in batches
+	binaryData := make([][]byte, len(topicCreationTests))
+	for i, tt := range topicCreationTests {
+		binaryData[i] = tt.data
+	}
+	textualData := make([]string, len(topicCreationTests))
+	for i, tt := range topicCreationTests {
+		textualData[i] = string(tt.data)
+	}
+
+	topics := NewTopics(binaryData...)
+	for i, tt := range topicCreationTests {
+		if bytes.Compare(topics[i][:], tt.hash[:]) != 0 {
+			t.Errorf("binary batch test %d: hash mismatch: have %v, want %v.", i, topics[i], tt.hash)
+		}
+	}
+	topics = NewTopicsFromStrings(textualData...)
+	for i, tt := range topicCreationTests {
+		if bytes.Compare(topics[i][:], tt.hash[:]) != 0 {
+			t.Errorf("textual batch test %d: hash mismatch: have %v, want %v.", i, topics[i], tt.hash)
+		}
+	}
+}
+
+var topicMatcherCreationTest = struct {
+	binary  [][][]byte
+	textual [][]string
+	matcher []map[[4]byte]struct{}
+}{
+	binary: [][][]byte{
+		[][]byte{},
+		[][]byte{
+			[]byte("Topic A"),
+		},
+		[][]byte{
+			[]byte("Topic B1"),
+			[]byte("Topic B2"),
+			[]byte("Topic B3"),
+		},
+	},
+	textual: [][]string{
+		[]string{},
+		[]string{"Topic A"},
+		[]string{"Topic B1", "Topic B2", "Topic B3"},
+	},
+	matcher: []map[[4]byte]struct{}{
+		map[[4]byte]struct{}{},
+		map[[4]byte]struct{}{
+			[4]byte{0x25, 0xfc, 0x95, 0x66}: struct{}{},
+		},
+		map[[4]byte]struct{}{
+			[4]byte{0x93, 0x6d, 0xec, 0x09}: struct{}{},
+			[4]byte{0x25, 0x23, 0x34, 0xd3}: struct{}{},
+			[4]byte{0x6b, 0xc2, 0x73, 0xd1}: struct{}{},
+		},
+	},
+}
+
+func TestTopicMatcherCreation(t *testing.T) {
+	test := topicMatcherCreationTest
+
+	matcher := newTopicMatcherFromBinary(test.binary...)
+	for i, cond := range matcher.conditions {
+		for topic, _ := range cond {
+			if _, ok := test.matcher[i][topic]; !ok {
+				t.Errorf("condition %d; extra topic found: 0x%x", i, topic[:])
+			}
+		}
+	}
+	for i, cond := range test.matcher {
+		for topic, _ := range cond {
+			if _, ok := matcher.conditions[i][topic]; !ok {
+				t.Errorf("condition %d; topic not found: 0x%x", i, topic[:])
+			}
+		}
+	}
+
+	matcher = newTopicMatcherFromStrings(test.textual...)
+	for i, cond := range matcher.conditions {
+		for topic, _ := range cond {
+			if _, ok := test.matcher[i][topic]; !ok {
+				t.Errorf("condition %d; extra topic found: 0x%x", i, topic[:])
+			}
+		}
+	}
+	for i, cond := range test.matcher {
+		for topic, _ := range cond {
+			if _, ok := matcher.conditions[i][topic]; !ok {
+				t.Errorf("condition %d; topic not found: 0x%x", i, topic[:])
+			}
+		}
+	}
+}
+
+var topicMatcherTests = []struct {
+	filter [][]string
+	topics []string
+	match  bool
+}{
+	// Empty topic matcher should match everything
+	{
+		filter: [][]string{},
+		topics: []string{},
+		match:  true,
+	},
+	{
+		filter: [][]string{},
+		topics: []string{"a", "b", "c"},
+		match:  true,
+	},
+	// Fixed topic matcher should match strictly, but only prefix
+	{
+		filter: [][]string{[]string{"a"}, []string{"b"}},
+		topics: []string{"a"},
+		match:  false,
+	},
+	{
+		filter: [][]string{[]string{"a"}, []string{"b"}},
+		topics: []string{"a", "b"},
+		match:  true,
+	},
+	{
+		filter: [][]string{[]string{"a"}, []string{"b"}},
+		topics: []string{"a", "b", "c"},
+		match:  true,
+	},
+	// Multi-matcher should match any from a sub-group
+	{
+		filter: [][]string{[]string{"a1", "a2"}},
+		topics: []string{"a"},
+		match:  false,
+	},
+	{
+		filter: [][]string{[]string{"a1", "a2"}},
+		topics: []string{"a1"},
+		match:  true,
+	},
+	{
+		filter: [][]string{[]string{"a1", "a2"}},
+		topics: []string{"a2"},
+		match:  true,
+	},
+	// Wild-card condition should match anything
+	{
+		filter: [][]string{[]string{}, []string{"b"}},
+		topics: []string{"a"},
+		match:  false,
+	},
+	{
+		filter: [][]string{[]string{}, []string{"b"}},
+		topics: []string{"a", "b"},
+		match:  true,
+	},
+	{
+		filter: [][]string{[]string{}, []string{"b"}},
+		topics: []string{"b", "b"},
+		match:  true,
+	},
+}
+
+func TestTopicMatcher(t *testing.T) {
+	for i, tt := range topicMatcherTests {
+		topics := NewTopicsFromStrings(tt.topics...)
+
+		matcher := newTopicMatcherFromStrings(tt.filter...)
+		if match := matcher.Matches(topics); match != tt.match {
+			t.Errorf("test %d: match mismatch: have %v, want %v", i, match, tt.match)
+		}
+	}
+}
diff --git a/whisper/whisperv2/whisper.go b/whisper/whisperv2/whisper.go
new file mode 100644
index 000000000..d9054959e
--- /dev/null
+++ b/whisper/whisperv2/whisper.go
@@ -0,0 +1,378 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"crypto/ecdsa"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/crypto/ecies"
+	"github.com/ethereum/go-ethereum/event/filter"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/rpc"
+
+	"gopkg.in/fatih/set.v0"
+)
+
+const (
+	statusCode   = 0x00
+	messagesCode = 0x01
+
+	protocolVersion uint64 = 0x02
+	protocolName           = "shh"
+
+	signatureFlag   = byte(1 << 7)
+	signatureLength = 65
+
+	expirationCycle   = 800 * time.Millisecond
+	transmissionCycle = 300 * time.Millisecond
+)
+
+const (
+	DefaultTTL = 50 * time.Second
+	DefaultPoW = 50 * time.Millisecond
+)
+
+type MessageEvent struct {
+	To      *ecdsa.PrivateKey
+	From    *ecdsa.PublicKey
+	Message *Message
+}
+
+// Whisper represents a dark communication interface through the Ethereum
+// network, using its very own P2P communication layer.
+type Whisper struct {
+	protocol p2p.Protocol
+	filters  *filter.Filters
+
+	keys map[string]*ecdsa.PrivateKey
+
+	messages    map[common.Hash]*Envelope // Pool of messages currently tracked by this node
+	expirations map[uint32]*set.SetNonTS  // Message expiration pool (TODO: something lighter)
+	poolMu      sync.RWMutex              // Mutex to sync the message and expiration pools
+
+	peers  map[*peer]struct{} // Set of currently active peers
+	peerMu sync.RWMutex       // Mutex to sync the active peer set
+
+	quit chan struct{}
+}
+
+// New creates a Whisper client ready to communicate through the Ethereum P2P
+// network.
+func New() *Whisper {
+	whisper := &Whisper{
+		filters:     filter.New(),
+		keys:        make(map[string]*ecdsa.PrivateKey),
+		messages:    make(map[common.Hash]*Envelope),
+		expirations: make(map[uint32]*set.SetNonTS),
+		peers:       make(map[*peer]struct{}),
+		quit:        make(chan struct{}),
+	}
+	whisper.filters.Start()
+
+	// p2p whisper sub protocol handler
+	whisper.protocol = p2p.Protocol{
+		Name:    protocolName,
+		Version: uint(protocolVersion),
+		Length:  2,
+		Run:     whisper.handlePeer,
+	}
+
+	return whisper
+}
+
+// APIs returns the RPC descriptors the Whisper implementation offers
+func (s *Whisper) APIs() []rpc.API {
+	return []rpc.API{
+		{
+			Namespace: "shh",
+			Version:   "1.0",
+			Service:   NewPublicWhisperAPI(s),
+			Public:    true,
+		},
+	}
+}
+
+// Protocols returns the whisper sub-protocols ran by this particular client.
+func (self *Whisper) Protocols() []p2p.Protocol {
+	return []p2p.Protocol{self.protocol}
+}
+
+// Version returns the whisper sub-protocols version number.
+func (self *Whisper) Version() uint {
+	return self.protocol.Version
+}
+
+// NewIdentity generates a new cryptographic identity for the client, and injects
+// it into the known identities for message decryption.
+func (self *Whisper) NewIdentity() *ecdsa.PrivateKey {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		panic(err)
+	}
+	self.keys[string(crypto.FromECDSAPub(&key.PublicKey))] = key
+
+	return key
+}
+
+// HasIdentity checks if the the whisper node is configured with the private key
+// of the specified public pair.
+func (self *Whisper) HasIdentity(key *ecdsa.PublicKey) bool {
+	return self.keys[string(crypto.FromECDSAPub(key))] != nil
+}
+
+// GetIdentity retrieves the private key of the specified public identity.
+func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
+	return self.keys[string(crypto.FromECDSAPub(key))]
+}
+
+// Watch installs a new message handler to run in case a matching packet arrives
+// from the whisper network.
+func (self *Whisper) Watch(options Filter) int {
+	filter := filterer{
+		to:      string(crypto.FromECDSAPub(options.To)),
+		from:    string(crypto.FromECDSAPub(options.From)),
+		matcher: newTopicMatcher(options.Topics...),
+		fn: func(data interface{}) {
+			options.Fn(data.(*Message))
+		},
+	}
+	return self.filters.Install(filter)
+}
+
+// Unwatch removes an installed message handler.
+func (self *Whisper) Unwatch(id int) {
+	self.filters.Uninstall(id)
+}
+
+// Send injects a message into the whisper send queue, to be distributed in the
+// network in the coming cycles.
+func (self *Whisper) Send(envelope *Envelope) error {
+	return self.add(envelope)
+}
+
+// Start implements node.Service, starting the background data propagation thread
+// of the Whisper protocol.
+func (self *Whisper) Start(*p2p.Server) error {
+	glog.V(logger.Info).Infoln("Whisper started")
+	go self.update()
+	return nil
+}
+
+// Stop implements node.Service, stopping the background data propagation thread
+// of the Whisper protocol.
+func (self *Whisper) Stop() error {
+	close(self.quit)
+	glog.V(logger.Info).Infoln("Whisper stopped")
+	return nil
+}
+
+// Messages retrieves all the currently pooled messages matching a filter id.
+func (self *Whisper) Messages(id int) []*Message {
+	messages := make([]*Message, 0)
+	if filter := self.filters.Get(id); filter != nil {
+		for _, envelope := range self.messages {
+			if message := self.open(envelope); message != nil {
+				if self.filters.Match(filter, createFilter(message, envelope.Topics)) {
+					messages = append(messages, message)
+				}
+			}
+		}
+	}
+	return messages
+}
+
+// handlePeer is called by the underlying P2P layer when the whisper sub-protocol
+// connection is negotiated.
+func (self *Whisper) handlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
+	// Create the new peer and start tracking it
+	whisperPeer := newPeer(self, peer, rw)
+
+	self.peerMu.Lock()
+	self.peers[whisperPeer] = struct{}{}
+	self.peerMu.Unlock()
+
+	defer func() {
+		self.peerMu.Lock()
+		delete(self.peers, whisperPeer)
+		self.peerMu.Unlock()
+	}()
+
+	// Run the peer handshake and state updates
+	if err := whisperPeer.handshake(); err != nil {
+		return err
+	}
+	whisperPeer.start()
+	defer whisperPeer.stop()
+
+	// Read and process inbound messages directly to merge into client-global state
+	for {
+		// Fetch the next packet and decode the contained envelopes
+		packet, err := rw.ReadMsg()
+		if err != nil {
+			return err
+		}
+		var envelopes []*Envelope
+		if err := packet.Decode(&envelopes); err != nil {
+			glog.V(logger.Info).Infof("%v: failed to decode envelope: %v", peer, err)
+			continue
+		}
+		// Inject all envelopes into the internal pool
+		for _, envelope := range envelopes {
+			if err := self.add(envelope); err != nil {
+				// TODO Punish peer here. Invalid envelope.
+				glog.V(logger.Debug).Infof("%v: failed to pool envelope: %v", peer, err)
+			}
+			whisperPeer.mark(envelope)
+		}
+	}
+}
+
+// add inserts a new envelope into the message pool to be distributed within the
+// whisper network. It also inserts the envelope into the expiration pool at the
+// appropriate time-stamp.
+func (self *Whisper) add(envelope *Envelope) error {
+	self.poolMu.Lock()
+	defer self.poolMu.Unlock()
+
+	// short circuit when a received envelope has already expired
+	if envelope.Expiry < uint32(time.Now().Unix()) {
+		return nil
+	}
+
+	// Insert the message into the tracked pool
+	hash := envelope.Hash()
+	if _, ok := self.messages[hash]; ok {
+		glog.V(logger.Detail).Infof("whisper envelope already cached: %x\n", envelope)
+		return nil
+	}
+	self.messages[hash] = envelope
+
+	// Insert the message into the expiration pool for later removal
+	if self.expirations[envelope.Expiry] == nil {
+		self.expirations[envelope.Expiry] = set.NewNonTS()
+	}
+	if !self.expirations[envelope.Expiry].Has(hash) {
+		self.expirations[envelope.Expiry].Add(hash)
+
+		// Notify the local node of a message arrival
+		go self.postEvent(envelope)
+	}
+	glog.V(logger.Detail).Infof("cached whisper envelope %x\n", envelope)
+	return nil
+}
+
+// postEvent opens an envelope with the configured identities and delivers the
+// message upstream from application processing.
+func (self *Whisper) postEvent(envelope *Envelope) {
+	if message := self.open(envelope); message != nil {
+		self.filters.Notify(createFilter(message, envelope.Topics), message)
+	}
+}
+
+// open tries to decrypt a whisper envelope with all the configured identities,
+// returning the decrypted message and the key used to achieve it. If not keys
+// are configured, open will return the payload as if non encrypted.
+func (self *Whisper) open(envelope *Envelope) *Message {
+	// Short circuit if no identity is set, and assume clear-text
+	if len(self.keys) == 0 {
+		if message, err := envelope.Open(nil); err == nil {
+			return message
+		}
+	}
+	// Iterate over the keys and try to decrypt the message
+	for _, key := range self.keys {
+		message, err := envelope.Open(key)
+		if err == nil {
+			message.To = &key.PublicKey
+			return message
+		} else if err == ecies.ErrInvalidPublicKey {
+			return message
+		}
+	}
+	// Failed to decrypt, don't return anything
+	return nil
+}
+
+// createFilter creates a message filter to check against installed handlers.
+func createFilter(message *Message, topics []Topic) filter.Filter {
+	matcher := make([][]Topic, len(topics))
+	for i, topic := range topics {
+		matcher[i] = []Topic{topic}
+	}
+	return filterer{
+		to:      string(crypto.FromECDSAPub(message.To)),
+		from:    string(crypto.FromECDSAPub(message.Recover())),
+		matcher: newTopicMatcher(matcher...),
+	}
+}
+
+// update loops until the lifetime of the whisper node, updating its internal
+// state by expiring stale messages from the pool.
+func (self *Whisper) update() {
+	// Start a ticker to check for expirations
+	expire := time.NewTicker(expirationCycle)
+
+	// Repeat updates until termination is requested
+	for {
+		select {
+		case <-expire.C:
+			self.expire()
+
+		case <-self.quit:
+			return
+		}
+	}
+}
+
+// expire iterates over all the expiration timestamps, removing all stale
+// messages from the pools.
+func (self *Whisper) expire() {
+	self.poolMu.Lock()
+	defer self.poolMu.Unlock()
+
+	now := uint32(time.Now().Unix())
+	for then, hashSet := range self.expirations {
+		// Short circuit if a future time
+		if then > now {
+			continue
+		}
+		// Dump all expired messages and remove timestamp
+		hashSet.Each(func(v interface{}) bool {
+			delete(self.messages, v.(common.Hash))
+			return true
+		})
+		self.expirations[then].Clear()
+	}
+}
+
+// envelopes retrieves all the messages currently pooled by the node.
+func (self *Whisper) envelopes() []*Envelope {
+	self.poolMu.RLock()
+	defer self.poolMu.RUnlock()
+
+	envelopes := make([]*Envelope, 0, len(self.messages))
+	for _, envelope := range self.messages {
+		envelopes = append(envelopes, envelope)
+	}
+	return envelopes
+}
diff --git a/whisper/whisperv2/whisper_test.go b/whisper/whisperv2/whisper_test.go
new file mode 100644
index 000000000..1e0d3f85d
--- /dev/null
+++ b/whisper/whisperv2/whisper_test.go
@@ -0,0 +1,216 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv2
+
+import (
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/discover"
+)
+
+func startTestCluster(n int) []*Whisper {
+	// Create the batch of simulated peers
+	nodes := make([]*p2p.Peer, n)
+	for i := 0; i < n; i++ {
+		nodes[i] = p2p.NewPeer(discover.NodeID{}, "", nil)
+	}
+	whispers := make([]*Whisper, n)
+	for i := 0; i < n; i++ {
+		whispers[i] = New()
+		whispers[i].Start(nil)
+	}
+	// Wire all the peers to the root one
+	for i := 1; i < n; i++ {
+		src, dst := p2p.MsgPipe()
+
+		go whispers[0].handlePeer(nodes[i], src)
+		go whispers[i].handlePeer(nodes[0], dst)
+	}
+	return whispers
+}
+
+func TestSelfMessage(t *testing.T) {
+	// Start the single node cluster
+	client := startTestCluster(1)[0]
+
+	// Start watching for self messages, signal any arrivals
+	self := client.NewIdentity()
+	done := make(chan struct{})
+
+	client.Watch(Filter{
+		To: &self.PublicKey,
+		Fn: func(msg *Message) {
+			close(done)
+		},
+	})
+	// Send a dummy message to oneself
+	msg := NewMessage([]byte("self whisper"))
+	envelope, err := msg.Wrap(DefaultPoW, Options{
+		From: self,
+		To:   &self.PublicKey,
+		TTL:  DefaultTTL,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	// Dump the message into the system and wait for it to pop back out
+	if err := client.Send(envelope); err != nil {
+		t.Fatalf("failed to send self-message: %v", err)
+	}
+	select {
+	case <-done:
+	case <-time.After(time.Second):
+		t.Fatalf("self-message receive timeout")
+	}
+}
+
+func TestDirectMessage(t *testing.T) {
+	// Start the sender-recipient cluster
+	cluster := startTestCluster(2)
+
+	sender := cluster[0]
+	senderId := sender.NewIdentity()
+
+	recipient := cluster[1]
+	recipientId := recipient.NewIdentity()
+
+	// Watch for arriving messages on the recipient
+	done := make(chan struct{})
+	recipient.Watch(Filter{
+		To: &recipientId.PublicKey,
+		Fn: func(msg *Message) {
+			close(done)
+		},
+	})
+	// Send a dummy message from the sender
+	msg := NewMessage([]byte("direct whisper"))
+	envelope, err := msg.Wrap(DefaultPoW, Options{
+		From: senderId,
+		To:   &recipientId.PublicKey,
+		TTL:  DefaultTTL,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if err := sender.Send(envelope); err != nil {
+		t.Fatalf("failed to send direct message: %v", err)
+	}
+	// Wait for an arrival or a timeout
+	select {
+	case <-done:
+	case <-time.After(time.Second):
+		t.Fatalf("direct message receive timeout")
+	}
+}
+
+func TestAnonymousBroadcast(t *testing.T) {
+	testBroadcast(true, t)
+}
+
+func TestIdentifiedBroadcast(t *testing.T) {
+	testBroadcast(false, t)
+}
+
+func testBroadcast(anonymous bool, t *testing.T) {
+	// Start the single sender multi recipient cluster
+	cluster := startTestCluster(3)
+
+	sender := cluster[1]
+	targets := cluster[1:]
+	for _, target := range targets {
+		if !anonymous {
+			target.NewIdentity()
+		}
+	}
+	// Watch for arriving messages on the recipients
+	dones := make([]chan struct{}, len(targets))
+	for i := 0; i < len(targets); i++ {
+		done := make(chan struct{}) // need for the closure
+		dones[i] = done
+
+		targets[i].Watch(Filter{
+			Topics: NewFilterTopicsFromStringsFlat("broadcast topic"),
+			Fn: func(msg *Message) {
+				close(done)
+			},
+		})
+	}
+	// Send a dummy message from the sender
+	msg := NewMessage([]byte("broadcast whisper"))
+	envelope, err := msg.Wrap(DefaultPoW, Options{
+		Topics: NewTopicsFromStrings("broadcast topic"),
+		TTL:    DefaultTTL,
+	})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if err := sender.Send(envelope); err != nil {
+		t.Fatalf("failed to send broadcast message: %v", err)
+	}
+	// Wait for an arrival on each recipient, or timeouts
+	timeout := time.After(time.Second)
+	for _, done := range dones {
+		select {
+		case <-done:
+		case <-timeout:
+			t.Fatalf("broadcast message receive timeout")
+		}
+	}
+}
+
+func TestMessageExpiration(t *testing.T) {
+	// Start the single node cluster and inject a dummy message
+	node := startTestCluster(1)[0]
+
+	message := NewMessage([]byte("expiring message"))
+	envelope, err := message.Wrap(DefaultPoW, Options{TTL: time.Second})
+	if err != nil {
+		t.Fatalf("failed to wrap message: %v", err)
+	}
+	if err := node.Send(envelope); err != nil {
+		t.Fatalf("failed to inject message: %v", err)
+	}
+	// Check that the message is inside the cache
+	node.poolMu.RLock()
+	_, found := node.messages[envelope.Hash()]
+	node.poolMu.RUnlock()
+
+	if !found {
+		t.Fatalf("message not found in cache")
+	}
+	// Wait for expiration and check cache again
+	time.Sleep(time.Second)         // wait for expiration
+	time.Sleep(2 * expirationCycle) // wait for cleanup cycle
+
+	node.poolMu.RLock()
+	_, found = node.messages[envelope.Hash()]
+	node.poolMu.RUnlock()
+	if found {
+		t.Fatalf("message not expired from cache")
+	}
+
+	// Check that adding an expired envelope doesn't do anything.
+	node.add(envelope)
+	node.poolMu.RLock()
+	_, found = node.messages[envelope.Hash()]
+	node.poolMu.RUnlock()
+	if found {
+		t.Fatalf("message was added to cache")
+	}
+}
diff --git a/whisper/whisperv5/benchmarks_test.go b/whisper/whisperv5/benchmarks_test.go
new file mode 100644
index 000000000..8bb6c0574
--- /dev/null
+++ b/whisper/whisperv5/benchmarks_test.go
@@ -0,0 +1,202 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"testing"
+
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+func BenchmarkDeriveKeyMaterial(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		deriveKeyMaterial([]byte("test"), 0)
+	}
+}
+
+func BenchmarkDeriveOneTimeKey(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		DeriveOneTimeKey([]byte("test value 1"), []byte("test value 2"), 0)
+	}
+}
+
+//func TestEncryptionSym(b *testing.T) {
+func BenchmarkEncryptionSym(b *testing.B) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		b.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	for i := 0; i < b.N; i++ {
+		msg := NewSentMessage(params)
+		_, err := msg.Wrap(params)
+		if err != nil {
+			b.Errorf("failed Wrap with seed %d: %s.", seed, err)
+			b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload))
+			return
+		}
+	}
+}
+
+func BenchmarkEncryptionAsym(b *testing.B) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		b.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		b.Errorf("failed GenerateKey with seed %d: %s.", seed, err)
+		return
+	}
+	params.KeySym = nil
+	params.Dst = &key.PublicKey
+
+	for i := 0; i < b.N; i++ {
+		msg := NewSentMessage(params)
+		_, err := msg.Wrap(params)
+		if err != nil {
+			b.Errorf("failed Wrap with seed %d: %s.", seed, err)
+			return
+		}
+	}
+}
+
+func BenchmarkDecryptionSymValid(b *testing.B) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		b.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		b.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+	f := Filter{KeySym: params.KeySym}
+
+	for i := 0; i < b.N; i++ {
+		msg := env.Open(&f)
+		if msg == nil {
+			b.Errorf("failed to open with seed %d.", seed)
+			return
+		}
+	}
+}
+
+func BenchmarkDecryptionSymInvalid(b *testing.B) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		b.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		b.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+	f := Filter{KeySym: []byte("arbitrary stuff here")}
+
+	for i := 0; i < b.N; i++ {
+		msg := env.Open(&f)
+		if msg != nil {
+			b.Errorf("opened envelope with invalid key, seed: %d.", seed)
+			return
+		}
+	}
+}
+
+func BenchmarkDecryptionAsymValid(b *testing.B) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		b.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		b.Errorf("failed GenerateKey with seed %d: %s.", seed, err)
+		return
+	}
+	f := Filter{KeyAsym: key}
+	params.KeySym = nil
+	params.Dst = &key.PublicKey
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		b.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	for i := 0; i < b.N; i++ {
+		msg := env.Open(&f)
+		if msg == nil {
+			b.Errorf("fail to open, seed: %d.", seed)
+			return
+		}
+	}
+}
+
+func BenchmarkDecryptionAsymInvalid(b *testing.B) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		b.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		b.Errorf("failed GenerateKey with seed %d: %s.", seed, err)
+		return
+	}
+	params.KeySym = nil
+	params.Dst = &key.PublicKey
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		b.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	key, err = crypto.GenerateKey()
+	if err != nil {
+		b.Errorf("failed GenerateKey with seed %d: %s.", seed, err)
+		return
+	}
+	f := Filter{KeyAsym: key}
+
+	for i := 0; i < b.N; i++ {
+		msg := env.Open(&f)
+		if msg != nil {
+			b.Errorf("opened envelope with invalid key, seed: %d.", seed)
+			return
+		}
+	}
+}
diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go
new file mode 100644
index 000000000..ef3b93d12
--- /dev/null
+++ b/whisper/whisperv5/doc.go
@@ -0,0 +1,87 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+/*
+Package whisper implements the Whisper PoC-1.
+
+(https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec)
+
+Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
+As such it may be likened and compared to both, not dissimilar to the
+matter/energy duality (apologies to physicists for the blatant abuse of a
+fundamental and beautiful natural principle).
+
+Whisper is a pure identity-based messaging system. Whisper provides a low-level
+(non-application-specific) but easily-accessible API without being based upon
+or prejudiced by the low-level hardware attributes and characteristics,
+particularly the notion of singular endpoints.
+*/
+package whisperv5
+
+import (
+	"fmt"
+	"time"
+)
+
+const (
+	EnvelopeVersion    = uint64(0)
+	ProtocolVersion    = uint64(5)
+	ProtocolVersionStr = "5.0"
+	ProtocolName       = "shh"
+
+	statusCode           = 0
+	messagesCode         = 1
+	p2pCode              = 2
+	mailRequestCode      = 3
+	NumberOfMessageCodes = 4
+
+	paddingMask   = byte(3)
+	signatureFlag = byte(4)
+
+	TopicLength     = 4
+	signatureLength = 65
+	aesKeyLength    = 32
+	saltLength      = 12
+
+	MaxMessageLength = 0xFFFF // todo: remove this restriction after testing in morden and analizing stats. this should be regulated by MinimumPoW.
+	MinimumPoW       = 10.0   // todo: review
+
+	padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature
+	padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility
+
+	expirationCycle   = time.Second
+	transmissionCycle = 300 * time.Millisecond
+
+	DefaultTTL     = 50 // seconds
+	SynchAllowance = 10 // seconds
+)
+
+type unknownVersionError uint64
+
+func (e unknownVersionError) Error() string {
+	return fmt.Sprintf("invalid envelope version %d", uint64(e))
+}
+
+// MailServer represents a mail server, capable of
+// archiving the old messages for subsequent delivery
+// to the peers. Any implementation must ensure that both
+// functions are thread-safe. Also, they must return ASAP.
+// DeliverMail should use directMessagesCode for delivery,
+// in order to bypass the expiry checks.
+type MailServer interface {
+	Archive(env *Envelope)
+	DeliverMail(whisperPeer *Peer, data []byte)
+}
diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go
new file mode 100644
index 000000000..57d454a08
--- /dev/null
+++ b/whisper/whisperv5/envelope.go
@@ -0,0 +1,233 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Contains the Whisper protocol Envelope element. For formal details please see
+// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#envelopes.
+
+package whisperv5
+
+import (
+	"crypto/ecdsa"
+	"encoding/binary"
+	"fmt"
+	"math"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/crypto/ecies"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// Envelope represents a clear-text data packet to transmit through the Whisper
+// network. Its contents may or may not be encrypted and signed.
+type Envelope struct {
+	Version  []byte
+	Expiry   uint32
+	TTL      uint32
+	Topic    TopicType
+	Salt     []byte
+	AESNonce []byte
+	Data     []byte
+	EnvNonce uint64
+
+	pow  float64     // Message-specific PoW as described in the Whisper specification.
+	hash common.Hash // Cached hash of the envelope to avoid rehashing every time.
+	// Don't access hash directly, use Hash() function instead.
+}
+
+// NewEnvelope wraps a Whisper message with expiration and destination data
+// included into an envelope for network forwarding.
+func NewEnvelope(ttl uint32, topic TopicType, salt []byte, aesNonce []byte, msg *SentMessage) *Envelope {
+	env := Envelope{
+		Version:  make([]byte, 1),
+		Expiry:   uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()),
+		TTL:      ttl,
+		Topic:    topic,
+		Salt:     salt,
+		AESNonce: aesNonce,
+		Data:     msg.Raw,
+		EnvNonce: 0,
+	}
+
+	if EnvelopeVersion < 256 {
+		env.Version[0] = byte(EnvelopeVersion)
+	} else {
+		panic("please increase the size of Envelope.Version before releasing this version")
+	}
+
+	return &env
+}
+
+func (e *Envelope) IsSymmetric() bool {
+	return e.AESNonce != nil
+}
+
+func (e *Envelope) isAsymmetric() bool {
+	return !e.IsSymmetric()
+}
+
+func (e *Envelope) Ver() uint64 {
+	return bytesToIntLittleEndian(e.Version)
+}
+
+// Seal closes the envelope by spending the requested amount of time as a proof
+// of work on hashing the data.
+func (e *Envelope) Seal(options *MessageParams) {
+	var target int
+	if options.PoW == 0 {
+		// adjust for the duration of Seal() execution only if execution time is predefined unconditionally
+		e.Expiry += options.WorkTime
+	} else {
+		target = e.powToFirstBit(options.PoW)
+	}
+
+	buf := make([]byte, 64)
+	h := crypto.Keccak256(e.rlpWithoutNonce())
+	copy(buf[:32], h)
+
+	finish, bestBit := time.Now().Add(time.Duration(options.WorkTime)*time.Second).UnixNano(), 0
+	for nonce := uint64(0); time.Now().UnixNano() < finish; {
+		for i := 0; i < 1024; i++ {
+			binary.BigEndian.PutUint64(buf[56:], nonce)
+			h = crypto.Keccak256(buf)
+			firstBit := common.FirstBitSet(common.BigD(h))
+			if firstBit > bestBit {
+				e.EnvNonce, bestBit = nonce, firstBit
+				if target > 0 && bestBit >= target {
+					return
+				}
+			}
+			nonce++
+		}
+	}
+}
+
+func (e *Envelope) PoW() float64 {
+	if e.pow == 0 {
+		e.calculatePoW(0)
+	}
+	return e.pow
+}
+
+func (e *Envelope) calculatePoW(diff uint32) {
+	buf := make([]byte, 64)
+	h := crypto.Keccak256(e.rlpWithoutNonce())
+	copy(buf[:32], h)
+	binary.BigEndian.PutUint64(buf[56:], e.EnvNonce)
+	h = crypto.Keccak256(buf)
+	firstBit := common.FirstBitSet(common.BigD(h))
+	x := math.Pow(2, float64(firstBit))
+	x /= float64(len(e.Data))
+	x /= float64(e.TTL + diff)
+	e.pow = x
+}
+
+func (e *Envelope) powToFirstBit(pow float64) int {
+	x := pow
+	x *= float64(len(e.Data))
+	x *= float64(e.TTL)
+	bits := math.Log2(x)
+	bits = math.Ceil(bits)
+	return int(bits)
+}
+
+// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
+func (e *Envelope) rlpWithoutNonce() []byte {
+	res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Salt, e.AESNonce, e.Data})
+	return res
+}
+
+// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
+func (e *Envelope) Hash() common.Hash {
+	if (e.hash == common.Hash{}) {
+		encoded, _ := rlp.EncodeToBytes(e)
+		e.hash = crypto.Keccak256Hash(encoded)
+	}
+	return e.hash
+}
+
+// DecodeRLP decodes an Envelope from an RLP data stream.
+func (e *Envelope) DecodeRLP(s *rlp.Stream) error {
+	raw, err := s.Raw()
+	if err != nil {
+		return err
+	}
+	// The decoding of Envelope uses the struct fields but also needs
+	// to compute the hash of the whole RLP-encoded envelope. This
+	// type has the same structure as Envelope but is not an
+	// rlp.Decoder (does not implement DecodeRLP function).
+	// Only public members will be encoded.
+	type rlpenv Envelope
+	if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil {
+		return err
+	}
+	e.hash = crypto.Keccak256Hash(raw)
+	return nil
+}
+
+// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
+func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) {
+	message := &ReceivedMessage{Raw: e.Data}
+	err := message.decryptAsymmetric(key)
+	switch err {
+	case nil:
+		return message, nil
+	case ecies.ErrInvalidPublicKey: // addressed to somebody else
+		return nil, err
+	default:
+		return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
+	}
+}
+
+// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
+func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
+	msg = &ReceivedMessage{Raw: e.Data}
+	err = msg.decryptSymmetric(key, e.Salt, e.AESNonce)
+	if err != nil {
+		msg = nil
+	}
+	return msg, err
+}
+
+// Open tries to decrypt an envelope, and populates the message fields in case of success.
+func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
+	if e.isAsymmetric() {
+		msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
+		if msg != nil {
+			msg.Dst = &watcher.KeyAsym.PublicKey
+		}
+	} else if e.IsSymmetric() {
+		msg, _ = e.OpenSymmetric(watcher.KeySym)
+		if msg != nil {
+			msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
+		}
+	}
+
+	if msg != nil {
+		ok := msg.Validate()
+		if !ok {
+			return nil
+		}
+		msg.Topic = e.Topic
+		msg.PoW = e.PoW()
+		msg.TTL = e.TTL
+		msg.Sent = e.Expiry - e.TTL
+		msg.EnvelopeHash = e.Hash()
+		msg.EnvelopeVersion = e.Ver()
+	}
+	return msg
+}
diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go
new file mode 100644
index 000000000..5cc7be587
--- /dev/null
+++ b/whisper/whisperv5/filter.go
@@ -0,0 +1,197 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"crypto/ecdsa"
+	"sync"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+type Filter struct {
+	Src        *ecdsa.PublicKey  // Sender of the message
+	KeyAsym    *ecdsa.PrivateKey // Private Key of recipient
+	KeySym     []byte            // Key associated with the Topic
+	Topics     []TopicType       // Topics to filter messages with
+	PoW        float64           // Proof of work as described in the Whisper spec
+	AcceptP2P  bool              // Indicates whether this filter is interested in direct peer-to-peer messages
+	SymKeyHash common.Hash       // The Keccak256Hash of the symmetric key, needed for optimization
+
+	Messages map[common.Hash]*ReceivedMessage
+	mutex    sync.RWMutex
+}
+
+type Filters struct {
+	id       int
+	watchers map[int]*Filter
+	whisper  *Whisper
+	mutex    sync.RWMutex
+}
+
+func NewFilters(w *Whisper) *Filters {
+	return &Filters{
+		watchers: make(map[int]*Filter),
+		whisper:  w,
+	}
+}
+
+func (fs *Filters) Install(watcher *Filter) int {
+	if watcher.Messages == nil {
+		watcher.Messages = make(map[common.Hash]*ReceivedMessage)
+	}
+
+	fs.mutex.Lock()
+	defer fs.mutex.Unlock()
+
+	fs.watchers[fs.id] = watcher
+	ret := fs.id
+	fs.id++
+	return ret
+}
+
+func (fs *Filters) Uninstall(id int) {
+	fs.mutex.Lock()
+	defer fs.mutex.Unlock()
+	delete(fs.watchers, id)
+}
+
+func (fs *Filters) Get(i int) *Filter {
+	fs.mutex.RLock()
+	defer fs.mutex.RUnlock()
+	return fs.watchers[i]
+}
+
+func (fs *Filters) NotifyWatchers(env *Envelope, messageCode uint64) {
+	fs.mutex.RLock()
+	var msg *ReceivedMessage
+	for _, watcher := range fs.watchers {
+		if messageCode == p2pCode && !watcher.AcceptP2P {
+			continue
+		}
+
+		match := false
+		if msg != nil {
+			match = watcher.MatchMessage(msg)
+		} else {
+			match = watcher.MatchEnvelope(env)
+			if match {
+				msg = env.Open(watcher)
+			}
+		}
+
+		if match && msg != nil {
+			watcher.Trigger(msg)
+		}
+	}
+	fs.mutex.RUnlock() // we need to unlock before calling addDecryptedMessage
+
+	if msg != nil {
+		fs.whisper.addDecryptedMessage(msg)
+	}
+}
+
+func (f *Filter) expectsAsymmetricEncryption() bool {
+	return f.KeyAsym != nil
+}
+
+func (f *Filter) expectsSymmetricEncryption() bool {
+	return f.KeySym != nil
+}
+
+func (f *Filter) Trigger(msg *ReceivedMessage) {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	if _, exist := f.Messages[msg.EnvelopeHash]; !exist {
+		f.Messages[msg.EnvelopeHash] = msg
+	}
+}
+
+func (f *Filter) Retrieve() (all []*ReceivedMessage) {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	all = make([]*ReceivedMessage, 0, len(f.Messages))
+	for _, msg := range f.Messages {
+		all = append(all, msg)
+	}
+	f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages
+	return all
+}
+
+func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
+	if f.PoW > 0 && msg.PoW < f.PoW {
+		return false
+	}
+	if f.Src != nil && !isPubKeyEqual(msg.Src, f.Src) {
+		return false
+	}
+
+	if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
+		// if Dst match, ignore the topic
+		return isPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
+	} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
+		// check if that both the key and the topic match
+		if f.SymKeyHash == msg.SymKeyHash {
+			for _, t := range f.Topics {
+				if t == msg.Topic {
+					return true
+				}
+			}
+			return false
+		}
+	}
+	return false
+}
+
+func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
+	if f.PoW > 0 && envelope.pow < f.PoW {
+		return false
+	}
+
+	encryptionMethodMatch := false
+	if f.expectsAsymmetricEncryption() && envelope.isAsymmetric() {
+		encryptionMethodMatch = true
+		if f.Topics == nil {
+			// wildcard
+			return true
+		}
+	} else if f.expectsSymmetricEncryption() && envelope.IsSymmetric() {
+		encryptionMethodMatch = true
+	}
+
+	if encryptionMethodMatch {
+		for _, t := range f.Topics {
+			if t == envelope.Topic {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func isPubKeyEqual(a, b *ecdsa.PublicKey) bool {
+	if !ValidatePublicKey(a) {
+		return false
+	} else if !ValidatePublicKey(b) {
+		return false
+	}
+	// the Curve is always the same, just compare the points
+	return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
+}
diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go
new file mode 100644
index 000000000..8c25b0519
--- /dev/null
+++ b/whisper/whisperv5/filter_test.go
@@ -0,0 +1,707 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"math/big"
+	"math/rand"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+var seed int64
+
+// InitSingleTest should be called in the beginning of every
+// test, which uses RNG, in order to make the tests
+// reproduciblity independent of their sequence.
+func InitSingleTest() {
+	seed = time.Now().Unix()
+	rand.Seed(seed)
+}
+
+func InitDebugTest(i int64) {
+	seed = i
+	rand.Seed(seed)
+}
+
+type FilterTestCase struct {
+	f      *Filter
+	id     int
+	alive  bool
+	msgCnt int
+}
+
+func generateFilter(x *testing.T, symmetric bool) (*Filter, error) {
+	var f Filter
+	f.Messages = make(map[common.Hash]*ReceivedMessage)
+
+	const topicNum = 8
+	f.Topics = make([]TopicType, topicNum)
+	for i := 0; i < topicNum; i++ {
+		randomize(f.Topics[i][:])
+		f.Topics[i][0] = 0x01
+	}
+
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("generateFilter failed 1 with seed %d.", seed)
+		return nil, err
+	}
+	f.Src = &key.PublicKey
+
+	if symmetric {
+		f.KeySym = make([]byte, 12)
+		randomize(f.KeySym)
+		f.SymKeyHash = crypto.Keccak256Hash(f.KeySym)
+	} else {
+		f.KeyAsym, err = crypto.GenerateKey()
+		if err != nil {
+			x.Errorf("generateFilter failed 2 with seed %d.", seed)
+			return nil, err
+		}
+	}
+
+	// AcceptP2P & PoW are not set
+	return &f, nil
+}
+
+func generateTestCases(x *testing.T, SizeTestFilters int) []FilterTestCase {
+	cases := make([]FilterTestCase, SizeTestFilters)
+	for i := 0; i < SizeTestFilters; i++ {
+		f, _ := generateFilter(x, true)
+		cases[i].f = f
+		cases[i].alive = (rand.Int()&int(1) == 0)
+	}
+	return cases
+}
+
+func TestInstallFilters(x *testing.T) {
+	InitSingleTest()
+
+	const SizeTestFilters = 256
+	w := NewWhisper(nil)
+	filters := NewFilters(w)
+	tst := generateTestCases(x, SizeTestFilters)
+
+	var j int
+	for i := 0; i < SizeTestFilters; i++ {
+		j = filters.Install(tst[i].f)
+		tst[i].id = j
+	}
+
+	if j < SizeTestFilters-1 {
+		x.Errorf("seed %d: wrong index %d", seed, j)
+		return
+	}
+
+	for _, t := range tst {
+		if !t.alive {
+			filters.Uninstall(t.id)
+		}
+	}
+
+	for i, t := range tst {
+		fil := filters.Get(t.id)
+		exist := (fil != nil)
+		if exist != t.alive {
+			x.Errorf("seed %d: failed alive: %d, %v, %v", seed, i, exist, t.alive)
+			return
+		}
+		if exist && fil.PoW != t.f.PoW {
+			x.Errorf("seed %d: failed Get: %d, %v, %v", seed, i, exist, t.alive)
+			return
+		}
+	}
+}
+
+func TestComparePubKey(x *testing.T) {
+	InitSingleTest()
+
+	key1, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey 1 with seed %d: %s.", seed, err)
+		return
+	}
+	key2, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey 2 with seed %d: %s.", seed, err)
+		return
+	}
+	if isPubKeyEqual(&key1.PublicKey, &key2.PublicKey) {
+		x.Errorf("failed !equal with seed %d.", seed)
+		return
+	}
+
+	// generate key3 == key1
+	rand.Seed(seed)
+	key3, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey 3 with seed %d: %s.", seed, err)
+		return
+	}
+	if isPubKeyEqual(&key1.PublicKey, &key3.PublicKey) {
+		x.Errorf("failed equal with seed %d.", seed)
+		return
+	}
+}
+
+func TestMatchEnvelope(x *testing.T) {
+	InitSingleTest()
+
+	fsym, err := generateFilter(x, true)
+	if err != nil {
+		x.Errorf("failed generateFilter 1 with seed %d: %s.", seed, err)
+		return
+	}
+
+	fasym, err := generateFilter(x, false)
+	if err != nil {
+		x.Errorf("failed generateFilter 2 with seed %d: %s.", seed, err)
+		return
+	}
+
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams 3 with seed %d: %s.", seed, err)
+		return
+	}
+
+	params.Topic[0] = 0xFF // ensure mismatch
+
+	// mismatch with pseudo-random data
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap 4 with seed %d: %s.", seed, err)
+		return
+	}
+	match := fsym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 5 with seed %d.", seed)
+		return
+	}
+	match = fasym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 6 with seed %d.", seed)
+		return
+	}
+
+	// encrypt symmetrically
+	i := rand.Int() % 4
+	fsym.Topics[i] = params.Topic
+	fasym.Topics[i] = params.Topic
+	msg = NewSentMessage(params)
+	env, err = msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed test case 7 with seed %d, test case 3: %s.", seed, err)
+		return
+	}
+
+	// symmetric + matching topic: match
+	match = fsym.MatchEnvelope(env)
+	if !match {
+		x.Errorf("failed test case 8 with seed %d.", seed)
+		return
+	}
+
+	// asymmetric + matching topic: mismatch
+	match = fasym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 9 with seed %d.", seed)
+		return
+	}
+
+	// symmetric + matching topic + insufficient PoW: mismatch
+	fsym.PoW = env.PoW() + 1.0
+	match = fsym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 10 with seed %d.", seed)
+		return
+	}
+
+	// symmetric + matching topic + sufficient PoW: match
+	fsym.PoW = env.PoW() / 2
+	match = fsym.MatchEnvelope(env)
+	if !match {
+		x.Errorf("failed test case 11 with seed %d.", seed)
+		return
+	}
+
+	// symmetric + topics are nil: mismatch
+	prevTopics := fsym.Topics
+	fsym.Topics = nil
+	match = fasym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 12 with seed %d.", seed)
+		return
+	}
+	fsym.Topics = prevTopics
+
+	// encrypt asymmetrically
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey 13 with seed %d: %s.", seed, err)
+		return
+	}
+	params.KeySym = nil
+	params.Dst = &key.PublicKey
+	msg = NewSentMessage(params)
+	env, err = msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed test case 14 with seed %d, test case 3: %s.", seed, err)
+		return
+	}
+
+	// encryption method mismatch
+	match = fsym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 15 with seed %d.", seed)
+		return
+	}
+
+	// asymmetric + mismatching topic: mismatch
+	match = fasym.MatchEnvelope(env)
+	if !match {
+		x.Errorf("failed test case 16 with seed %d.", seed)
+		return
+	}
+
+	// asymmetric + matching topic: match
+	fasym.Topics[i] = fasym.Topics[i+1]
+	match = fasym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 17 with seed %d.", seed)
+		return
+	}
+
+	// asymmetric + topic is nil (wildcard): match
+	fasym.Topics = nil
+	match = fasym.MatchEnvelope(env)
+	if !match {
+		x.Errorf("failed test case 18 with seed %d.", seed)
+		return
+	}
+
+	// asymmetric + insufficient PoW: mismatch
+	fasym.PoW = env.PoW() + 1.0
+	match = fasym.MatchEnvelope(env)
+	if match {
+		x.Errorf("failed test case 19 with seed %d.", seed)
+		return
+	}
+
+	// asymmetric + sufficient PoW: match
+	fasym.PoW = env.PoW() / 2
+	match = fasym.MatchEnvelope(env)
+	if !match {
+		x.Errorf("failed test case 20 with seed %d.", seed)
+		return
+	}
+}
+
+func TestMatchMessageSym(x *testing.T) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	f, err := generateFilter(x, true)
+	if err != nil {
+		x.Errorf("failed generateFilter 1 with seed %d: %s.", seed, err)
+		return
+	}
+
+	const index = 1
+	params.KeySym = f.KeySym
+	params.Topic = f.Topics[index]
+
+	sentMessage := NewSentMessage(params)
+	env, err := sentMessage.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap 2 with seed %d: %s.", seed, err)
+		return
+	}
+
+	msg := env.Open(f)
+	if msg == nil {
+		x.Errorf("failed to open 3 with seed %d.", seed)
+		return
+	}
+
+	// Src mismatch
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 4 with seed %d.", seed)
+		return
+	}
+
+	// Src: match
+	*f.Src.X = *params.Src.PublicKey.X
+	*f.Src.Y = *params.Src.PublicKey.Y
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 5 with seed %d.", seed)
+		return
+	}
+
+	// insufficient PoW: mismatch
+	f.PoW = msg.PoW + 1.0
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 6 with seed %d.", seed)
+		return
+	}
+
+	// sufficient PoW: match
+	f.PoW = msg.PoW / 2
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 7 with seed %d.", seed)
+		return
+	}
+
+	// topic mismatch
+	f.Topics[index][0]++
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 8 with seed %d.", seed)
+		return
+	}
+	f.Topics[index][0]--
+
+	// key mismatch
+	f.SymKeyHash[0]++
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 9 with seed %d.", seed)
+		return
+	}
+	f.SymKeyHash[0]--
+
+	// Src absent: match
+	f.Src = nil
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 10 with seed %d.", seed)
+		return
+	}
+
+	// key hash mismatch mismatch
+	h := f.SymKeyHash
+	f.SymKeyHash = common.Hash{}
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 11 with seed %d.", seed)
+		return
+	}
+	f.SymKeyHash = h
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 12 with seed %d.", seed)
+		return
+	}
+
+	// encryption method mismatch
+	f.KeySym = nil
+	f.KeyAsym, err = crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey 13 with seed %d: %s.", seed, err)
+		return
+	}
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 14 with seed %d.", seed)
+		return
+	}
+}
+
+func TestMatchMessageAsym(x *testing.T) {
+	InitSingleTest()
+
+	f, err := generateFilter(x, false)
+	if err != nil {
+		x.Errorf("failed generateFilter with seed %d: %s.", seed, err)
+		return
+	}
+
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	const index = 1
+	params.Topic = f.Topics[index]
+	params.Dst = &f.KeyAsym.PublicKey
+	keySymOrig := params.KeySym
+	params.KeySym = nil
+
+	sentMessage := NewSentMessage(params)
+	env, err := sentMessage.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	msg := env.Open(f)
+	if msg == nil {
+		x.Errorf("failed to open with seed %d.", seed)
+		return
+	}
+
+	// Src mismatch
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 4 with seed %d.", seed)
+		return
+	}
+
+	// Src: match
+	*f.Src.X = *params.Src.PublicKey.X
+	*f.Src.Y = *params.Src.PublicKey.Y
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 5 with seed %d.", seed)
+		return
+	}
+
+	// insufficient PoW: mismatch
+	f.PoW = msg.PoW + 1.0
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 6 with seed %d.", seed)
+		return
+	}
+
+	// sufficient PoW: match
+	f.PoW = msg.PoW / 2
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 7 with seed %d.", seed)
+		return
+	}
+
+	// topic mismatch, but still match, because for asymmetric encryption
+	// only private key matters (in case the message is already decrypted)
+	f.Topics[index][0]++
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 8 with seed %d.", seed)
+		return
+	}
+	f.Topics[index][0]--
+
+	// key mismatch
+	prev := *f.KeyAsym.PublicKey.X
+	zero := *big.NewInt(0)
+	*f.KeyAsym.PublicKey.X = zero
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 9 with seed %d.", seed)
+		return
+	}
+	*f.KeyAsym.PublicKey.X = prev
+
+	// Src absent: match
+	f.Src = nil
+	if !f.MatchMessage(msg) {
+		x.Errorf("failed test case 10 with seed %d.", seed)
+		return
+	}
+
+	// encryption method mismatch
+	f.KeySym = keySymOrig
+	f.KeyAsym = nil
+	if f.MatchMessage(msg) {
+		x.Errorf("failed test case 11 with seed %d.", seed)
+		return
+	}
+}
+
+func cloneFilter(orig *Filter) *Filter {
+	var clone Filter
+	clone.Messages = make(map[common.Hash]*ReceivedMessage)
+	clone.Src = orig.Src
+	clone.KeyAsym = orig.KeyAsym
+	clone.KeySym = orig.KeySym
+	clone.Topics = orig.Topics
+	clone.PoW = orig.PoW
+	clone.AcceptP2P = orig.AcceptP2P
+	clone.SymKeyHash = orig.SymKeyHash
+	return &clone
+}
+
+func generateCompatibeEnvelope(x *testing.T, f *Filter) *Envelope {
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams 77 with seed %d: %s.", seed, err)
+		return nil
+	}
+
+	params.KeySym = f.KeySym
+	params.Topic = f.Topics[2]
+	sentMessage := NewSentMessage(params)
+	env, err := sentMessage.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap 78 with seed %d: %s.", seed, err)
+		return nil
+	}
+	return env
+}
+
+func TestWatchers(x *testing.T) {
+	InitSingleTest()
+
+	const NumFilters = 16
+	const NumMessages = 256
+	var i, j int
+	var e *Envelope
+
+	w := NewWhisper(nil)
+	filters := NewFilters(w)
+	tst := generateTestCases(x, NumFilters)
+	for i = 0; i < NumFilters; i++ {
+		tst[i].f.Src = nil
+		j = filters.Install(tst[i].f)
+		tst[i].id = j
+	}
+
+	last := j
+
+	var envelopes [NumMessages]*Envelope
+	for i = 0; i < NumMessages; i++ {
+		j = rand.Int() % NumFilters
+		e = generateCompatibeEnvelope(x, tst[j].f)
+		envelopes[i] = e
+		tst[j].msgCnt++
+	}
+
+	for i = 0; i < NumMessages; i++ {
+		filters.NotifyWatchers(envelopes[i], messagesCode)
+	}
+
+	var total int
+	var mail []*ReceivedMessage
+	var count [NumFilters]int
+
+	for i = 0; i < NumFilters; i++ {
+		mail = tst[i].f.Retrieve()
+		count[i] = len(mail)
+		total += len(mail)
+	}
+
+	if total != NumMessages {
+		x.Errorf("failed test case 1 with seed %d: total = %d, want: %d.", seed, total, NumMessages)
+		return
+	}
+
+	for i = 0; i < NumFilters; i++ {
+		mail = tst[i].f.Retrieve()
+		if len(mail) != 0 {
+			x.Errorf("failed test case 2 with seed %d: i = %d.", seed, i)
+			return
+		}
+
+		if tst[i].msgCnt != count[i] {
+			x.Errorf("failed test case 3 with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
+			return
+		}
+	}
+
+	// another round with a cloned filter
+
+	clone := cloneFilter(tst[0].f)
+	filters.Uninstall(last)
+	total = 0
+	last = NumFilters - 1
+	tst[last].f = clone
+	filters.Install(clone)
+	for i = 0; i < NumFilters; i++ {
+		tst[i].msgCnt = 0
+		count[i] = 0
+	}
+
+	// make sure that the first watcher receives at least one message
+	e = generateCompatibeEnvelope(x, tst[0].f)
+	envelopes[0] = e
+	tst[0].msgCnt++
+	for i = 1; i < NumMessages; i++ {
+		j = rand.Int() % NumFilters
+		e = generateCompatibeEnvelope(x, tst[j].f)
+		envelopes[i] = e
+		tst[j].msgCnt++
+	}
+
+	for i = 0; i < NumMessages; i++ {
+		filters.NotifyWatchers(envelopes[i], messagesCode)
+	}
+
+	for i = 0; i < NumFilters; i++ {
+		mail = tst[i].f.Retrieve()
+		count[i] = len(mail)
+		total += len(mail)
+	}
+
+	combined := tst[0].msgCnt + tst[last].msgCnt
+	if total != NumMessages+count[0] {
+		x.Errorf("failed test case 4 with seed %d: total = %d, count[0] = %d.", seed, total, count[0])
+		return
+	}
+
+	if combined != count[0] {
+		x.Errorf("failed test case 5 with seed %d: combined = %d, count[0] = %d.", seed, combined, count[0])
+		return
+	}
+
+	if combined != count[last] {
+		x.Errorf("failed test case 6 with seed %d: combined = %d, count[last] = %d.", seed, combined, count[last])
+		return
+	}
+
+	for i = 1; i < NumFilters-1; i++ {
+		mail = tst[i].f.Retrieve()
+		if len(mail) != 0 {
+			x.Errorf("failed test case 7 with seed %d: i = %d.", seed, i)
+			return
+		}
+
+		if tst[i].msgCnt != count[i] {
+			x.Errorf("failed test case 8 with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
+			return
+		}
+	}
+
+	// test AcceptP2P
+
+	total = 0
+	filters.NotifyWatchers(envelopes[0], p2pCode)
+
+	for i = 0; i < NumFilters; i++ {
+		mail = tst[i].f.Retrieve()
+		total += len(mail)
+	}
+
+	if total != 0 {
+		x.Errorf("failed test case 9 with seed %d.", seed)
+		return
+	}
+
+	f := filters.Get(0)
+	f.AcceptP2P = true
+	total = 0
+	filters.NotifyWatchers(envelopes[0], p2pCode)
+
+	for i = 0; i < NumFilters; i++ {
+		mail = tst[i].f.Retrieve()
+		total += len(mail)
+	}
+
+	if total != 1 {
+		x.Errorf("failed test case 10 with seed %d: total = %d.", seed, total)
+		return
+	}
+}
diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go
new file mode 100644
index 000000000..680d1f8a2
--- /dev/null
+++ b/whisper/whisperv5/message.go
@@ -0,0 +1,378 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Contains the Whisper protocol Message element. For formal details please see
+// the specs at https://github.com/ethereum/wiki/wiki/Whisper-PoC-1-Protocol-Spec#messages.
+// todo: fix the spec link, and move it to doc.go
+
+package whisperv5
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"crypto/sha256"
+	"errors"
+	"fmt"
+	mrand "math/rand"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"golang.org/x/crypto/pbkdf2"
+)
+
+// Options specifies the exact way a message should be wrapped into an Envelope.
+type MessageParams struct {
+	TTL      uint32
+	Src      *ecdsa.PrivateKey
+	Dst      *ecdsa.PublicKey
+	KeySym   []byte
+	Topic    TopicType
+	WorkTime uint32
+	PoW      float64
+	Payload  []byte
+	Padding  []byte
+}
+
+// SentMessage represents an end-user data packet to transmit through the
+// Whisper protocol. These are wrapped into Envelopes that need not be
+// understood by intermediate nodes, just forwarded.
+type SentMessage struct {
+	Raw []byte
+}
+
+// ReceivedMessage represents a data packet to be received through the
+// Whisper protocol.
+type ReceivedMessage struct {
+	Raw []byte
+
+	Payload   []byte
+	Padding   []byte
+	Signature []byte
+
+	PoW   float64          // Proof of work as described in the Whisper spec
+	Sent  uint32           // Time when the message was posted into the network
+	TTL   uint32           // Maximum time to live allowed for the message
+	Src   *ecdsa.PublicKey // Message recipient (identity used to decode the message)
+	Dst   *ecdsa.PublicKey // Message recipient (identity used to decode the message)
+	Topic TopicType
+
+	SymKeyHash      common.Hash // The Keccak256Hash of the key, associated with the Topic
+	EnvelopeHash    common.Hash // Message envelope hash to act as a unique id
+	EnvelopeVersion uint64
+}
+
+func isMessageSigned(flags byte) bool {
+	return (flags & signatureFlag) != 0
+}
+
+func (msg *ReceivedMessage) isSymmetricEncryption() bool {
+	return msg.SymKeyHash != common.Hash{}
+}
+
+func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
+	return msg.Dst != nil
+}
+
+func DeriveOneTimeKey(key []byte, salt []byte, version uint64) ([]byte, error) {
+	if version == 0 {
+		derivedKey := pbkdf2.Key(key, salt, 8, aesKeyLength, sha256.New)
+		return derivedKey, nil
+	} else {
+		return nil, unknownVersionError(version)
+	}
+}
+
+// NewMessage creates and initializes a non-signed, non-encrypted Whisper message.
+func NewSentMessage(params *MessageParams) *SentMessage {
+	msg := SentMessage{}
+	msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper)
+	msg.Raw[0] = 0 // set all the flags to zero
+	msg.appendPadding(params)
+	msg.Raw = append(msg.Raw, params.Payload...)
+	return &msg
+}
+
+// appendPadding appends the pseudorandom padding bytes and sets the padding flag.
+// The last byte contains the size of padding (thus, its size must not exceed 256).
+func (msg *SentMessage) appendPadding(params *MessageParams) {
+	total := len(params.Payload) + 1
+	if params.Src != nil {
+		total += signatureLength
+	}
+	padChunk := padSizeLimitUpper
+	if total <= padSizeLimitLower {
+		padChunk = padSizeLimitLower
+	}
+	odd := total % padChunk
+	if odd > 0 {
+		padSize := padChunk - odd
+		if padSize > 255 {
+			// this algorithm is only valid if padSizeLimitUpper <= 256.
+			// if padSizeLimitUpper will ever change, please fix the algorithm
+			// (for more information see ReceivedMessage.extractPadding() function).
+			panic("please fix the padding algorithm before releasing new version")
+		}
+		buf := make([]byte, padSize)
+		randomize(buf[1:]) // change to: err = mrand.Read(buf[1:])
+		buf[0] = byte(padSize)
+		if params.Padding != nil {
+			copy(buf[1:], params.Padding)
+		}
+		msg.Raw = append(msg.Raw, buf...)
+		msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
+	}
+}
+
+// sign calculates and sets the cryptographic signature for the message,
+// also setting the sign flag.
+func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
+	if isMessageSigned(msg.Raw[0]) {
+		// this should not happen, but no reason to panic
+		glog.V(logger.Error).Infof("Trying to sign a message which was already signed")
+		return nil
+	}
+
+	msg.Raw[0] |= signatureFlag
+	hash := crypto.Keccak256(msg.Raw)
+	signature, err := crypto.Sign(hash, key)
+	if err != nil {
+		msg.Raw[0] &= ^signatureFlag // clear the flag
+		return err
+	}
+	msg.Raw = append(msg.Raw, signature...)
+	return nil
+}
+
+// encryptAsymmetric encrypts a message with a public key.
+func (msg *SentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
+	if !ValidatePublicKey(key) {
+		return fmt.Errorf("Invalid public key provided for asymmetric encryption")
+	}
+	encrypted, err := crypto.Encrypt(key, msg.Raw)
+	if err == nil {
+		msg.Raw = encrypted
+	}
+	return err
+}
+
+// encryptSymmetric encrypts a message with a topic key, using AES-GCM-256.
+// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
+func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte, err error) {
+	if !validateSymmetricKey(key) {
+		return nil, nil, errors.New("invalid key provided for symmetric encryption")
+	}
+
+	salt = make([]byte, saltLength)
+	_, err = crand.Read(salt)
+	if err != nil {
+		return nil, nil, err
+	} else if !validateSymmetricKey(salt) {
+		return nil, nil, errors.New("crypto/rand failed to generate salt")
+	}
+
+	derivedKey, err := DeriveOneTimeKey(key, salt, EnvelopeVersion)
+	if err != nil {
+		return nil, nil, err
+	}
+	if !validateSymmetricKey(derivedKey) {
+		return nil, nil, errors.New("failed to derive one-time key")
+	}
+	block, err := aes.NewCipher(derivedKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	aesgcm, err := cipher.NewGCM(block)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// never use more than 2^32 random nonces with a given key
+	nonce = make([]byte, aesgcm.NonceSize())
+	_, err = crand.Read(nonce)
+	if err != nil {
+		return nil, nil, err
+	}
+	msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil)
+	return salt, nonce, nil
+}
+
+// Wrap bundles the message into an Envelope to transmit over the network.
+//
+// pow (Proof Of Work) controls how much time to spend on hashing the message,
+// inherently controlling its priority through the network (smaller hash, bigger
+// priority).
+//
+// The user can control the amount of identity, privacy and encryption through
+// the options parameter as follows:
+//   - options.From == nil && options.To == nil: anonymous broadcast
+//   - options.From != nil && options.To == nil: signed broadcast (known sender)
+//   - options.From == nil && options.To != nil: encrypted anonymous message
+//   - options.From != nil && options.To != nil: encrypted signed message
+func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) {
+	if options.TTL == 0 {
+		options.TTL = DefaultTTL
+	}
+	if options.Src != nil {
+		err = msg.sign(options.Src)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if len(msg.Raw) > MaxMessageLength {
+		glog.V(logger.Error).Infof("Message size must not exceed %d bytes", MaxMessageLength)
+		return nil, errors.New("Oversized message")
+	}
+	var salt, nonce []byte
+	if options.Dst != nil {
+		err = msg.encryptAsymmetric(options.Dst)
+	} else if options.KeySym != nil {
+		salt, nonce, err = msg.encryptSymmetric(options.KeySym)
+	} else {
+		err = errors.New("Unable to encrypt the message: neither Dst nor Key")
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg)
+	envelope.Seal(options)
+	return envelope, nil
+}
+
+// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256.
+// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
+func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []byte) error {
+	derivedKey, err := DeriveOneTimeKey(key, salt, msg.EnvelopeVersion)
+	if err != nil {
+		return err
+	}
+
+	block, err := aes.NewCipher(derivedKey)
+	if err != nil {
+		return err
+	}
+	aesgcm, err := cipher.NewGCM(block)
+	if err != nil {
+		return err
+	}
+	if len(nonce) != aesgcm.NonceSize() {
+		info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize())
+		glog.V(logger.Error).Infof(info)
+		return errors.New(info)
+	}
+	decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)
+	if err != nil {
+		return err
+	}
+	msg.Raw = decrypted
+	return nil
+}
+
+// decryptAsymmetric decrypts an encrypted payload with a private key.
+func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
+	decrypted, err := crypto.Decrypt(key, msg.Raw)
+	if err == nil {
+		msg.Raw = decrypted
+	}
+	return err
+}
+
+// Validate checks the validity and extracts the fields in case of success
+func (msg *ReceivedMessage) Validate() bool {
+	end := len(msg.Raw)
+	if end < 1 {
+		return false
+	}
+
+	if isMessageSigned(msg.Raw[0]) {
+		end -= signatureLength
+		if end <= 1 {
+			return false
+		}
+		msg.Signature = msg.Raw[end:]
+		msg.Src = msg.SigToPubKey()
+		if msg.Src == nil {
+			return false
+		}
+	}
+
+	padSize, ok := msg.extractPadding(end)
+	if !ok {
+		return false
+	}
+
+	msg.Payload = msg.Raw[1+padSize : end]
+	return true
+}
+
+// extractPadding extracts the padding from raw message.
+// although we don't support sending messages with padding size
+// exceeding 255 bytes, such messages are perfectly valid, and
+// can be successfully decrypted.
+func (msg *ReceivedMessage) extractPadding(end int) (int, bool) {
+	paddingSize := 0
+	sz := int(msg.Raw[0] & paddingMask) // number of bytes containing the entire size of padding, could be zero
+	if sz != 0 {
+		paddingSize = int(bytesToIntLittleEndian(msg.Raw[1 : 1+sz]))
+		if paddingSize < sz || paddingSize+1 > end {
+			return 0, false
+		}
+		msg.Padding = msg.Raw[1+sz : 1+paddingSize]
+	}
+	return paddingSize, true
+}
+
+// Recover retrieves the public key of the message signer.
+func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
+	defer func() { recover() }() // in case of invalid signature
+
+	pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
+	if err != nil {
+		glog.V(logger.Error).Infof("Could not get public key from signature: %v", err)
+		return nil
+	}
+	return pub
+}
+
+// hash calculates the SHA3 checksum of the message flags, payload and padding.
+func (msg *ReceivedMessage) hash() []byte {
+	if isMessageSigned(msg.Raw[0]) {
+		sz := len(msg.Raw) - signatureLength
+		return crypto.Keccak256(msg.Raw[:sz])
+	}
+	return crypto.Keccak256(msg.Raw)
+}
+
+// rand.Rand provides a Read method in Go 1.7 and later,
+// but we can't use it yet.
+func randomize(b []byte) {
+	cnt := 0
+	val := mrand.Int63()
+	for n := 0; n < len(b); n++ {
+		b[n] = byte(val)
+		val >>= 8
+		cnt++
+		if cnt >= 7 {
+			cnt = 0
+			val = mrand.Int63()
+		}
+	}
+}
diff --git a/whisper/whisperv5/message_test.go b/whisper/whisperv5/message_test.go
new file mode 100644
index 000000000..78041fc1a
--- /dev/null
+++ b/whisper/whisperv5/message_test.go
@@ -0,0 +1,306 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"bytes"
+	"math/rand"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+func copyFromBuf(dst []byte, src []byte, beg int) int {
+	copy(dst, src[beg:])
+	return beg + len(dst)
+}
+
+func generateMessageParams() (*MessageParams, error) {
+	buf := make([]byte, 1024)
+	randomize(buf)
+	sz := rand.Intn(400)
+
+	var p MessageParams
+	p.TTL = uint32(rand.Intn(1024))
+	p.Payload = make([]byte, sz)
+	p.Padding = make([]byte, padSizeLimitUpper)
+	p.KeySym = make([]byte, aesKeyLength)
+
+	var b int
+	b = copyFromBuf(p.Payload, buf, b)
+	b = copyFromBuf(p.Padding, buf, b)
+	b = copyFromBuf(p.KeySym, buf, b)
+	p.Topic = BytesToTopic(buf[b:])
+
+	var err error
+	p.Src, err = crypto.GenerateKey()
+	if err != nil {
+		return nil, err
+	}
+
+	// p.Dst, p.PoW, p.WorkTime are not set
+	p.PoW = 0.01
+	return &p, nil
+}
+
+func singleMessageTest(x *testing.T, symmetric bool) {
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey with seed %d: %s.", seed, err)
+		return
+	}
+
+	if !symmetric {
+		params.KeySym = nil
+		params.Dst = &key.PublicKey
+	}
+
+	text := make([]byte, 0, 512)
+	steg := make([]byte, 0, 512)
+	raw := make([]byte, 0, 1024)
+	text = append(text, params.Payload...)
+	steg = append(steg, params.Padding...)
+	raw = append(raw, params.Padding...)
+
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	var decrypted *ReceivedMessage
+	if symmetric {
+		decrypted, err = env.OpenSymmetric(params.KeySym)
+	} else {
+		decrypted, err = env.OpenAsymmetric(key)
+	}
+
+	if err != nil {
+		x.Errorf("failed to encrypt with seed %d: %s.", seed, err)
+		return
+	}
+
+	if !decrypted.Validate() {
+		x.Errorf("failed to validate with seed %d.", seed)
+		return
+	}
+
+	padsz := len(decrypted.Padding)
+	if bytes.Compare(steg[:padsz], decrypted.Padding) != 0 {
+		x.Errorf("failed with seed %d: compare padding.", seed)
+		return
+	}
+	if bytes.Compare(text, decrypted.Payload) != 0 {
+		x.Errorf("failed with seed %d: compare payload.", seed)
+		return
+	}
+	if !isMessageSigned(decrypted.Raw[0]) {
+		x.Errorf("failed with seed %d: unsigned.", seed)
+		return
+	}
+	if len(decrypted.Signature) != signatureLength {
+		x.Errorf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
+		return
+	}
+	if !isPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
+		x.Errorf("failed with seed %d: signature mismatch.", seed)
+		return
+	}
+}
+
+func TestMessageEncryption(x *testing.T) {
+	InitSingleTest()
+
+	var symmetric bool
+	for i := 0; i < 256; i++ {
+		singleMessageTest(x, symmetric)
+		symmetric = !symmetric
+	}
+}
+
+func TestMessageWrap(x *testing.T) {
+	seed = int64(1777444222)
+	rand.Seed(seed)
+	target := 128.0
+
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	msg := NewSentMessage(params)
+	params.TTL = 1
+	params.WorkTime = 12
+	params.PoW = target
+	env, err := msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	pow := env.PoW()
+	if pow < target {
+		x.Errorf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
+		return
+	}
+}
+
+func TestMessageSeal(x *testing.T) {
+	// this test depends on deterministic choice of seed (1976726903)
+	seed = int64(1976726903)
+	rand.Seed(seed)
+
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	msg := NewSentMessage(params)
+	params.TTL = 1
+	aesnonce := make([]byte, 12)
+	salt := make([]byte, 12)
+	randomize(aesnonce)
+	randomize(salt)
+
+	env := NewEnvelope(params.TTL, params.Topic, salt, aesnonce, msg)
+	if err != nil {
+		x.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	env.Expiry = uint32(seed) // make it deterministic
+	target := 32.0
+	params.WorkTime = 4
+	params.PoW = target
+	env.Seal(params)
+
+	env.calculatePoW(0)
+	pow := env.PoW()
+	if pow < target {
+		x.Errorf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
+		return
+	}
+
+	params.WorkTime = 1
+	params.PoW = 1000000000.0
+	env.Seal(params)
+	env.calculatePoW(0)
+	pow = env.PoW()
+	if pow < 2*target {
+		x.Errorf("failed Wrap with seed %d: pow too small %f.", seed, pow)
+		return
+	}
+}
+
+func TestEnvelopeOpen(x *testing.T) {
+	InitSingleTest()
+
+	var symmetric bool
+	for i := 0; i < 256; i++ {
+		singleEnvelopeOpenTest(x, symmetric)
+		symmetric = !symmetric
+	}
+}
+
+func singleEnvelopeOpenTest(x *testing.T, symmetric bool) {
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed generateMessageParams with seed %d: %s.", seed, err)
+		return
+	}
+
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		x.Errorf("failed GenerateKey with seed %d: %s.", seed, err)
+		return
+	}
+
+	if !symmetric {
+		params.KeySym = nil
+		params.Dst = &key.PublicKey
+	}
+
+	text := make([]byte, 0, 512)
+	steg := make([]byte, 0, 512)
+	raw := make([]byte, 0, 1024)
+	text = append(text, params.Payload...)
+	steg = append(steg, params.Padding...)
+	raw = append(raw, params.Padding...)
+
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed Wrap with seed %d: %s.", seed, err)
+		return
+	}
+
+	f := Filter{KeyAsym: key, KeySym: params.KeySym}
+	decrypted := env.Open(&f)
+	if decrypted == nil {
+		x.Errorf("failed to open with seed %d.", seed)
+		return
+	}
+
+	padsz := len(decrypted.Padding)
+	if bytes.Compare(steg[:padsz], decrypted.Padding) != 0 {
+		x.Errorf("failed with seed %d: compare padding.", seed)
+		return
+	}
+	if bytes.Compare(text, decrypted.Payload) != 0 {
+		x.Errorf("failed with seed %d: compare payload.", seed)
+		return
+	}
+	if !isMessageSigned(decrypted.Raw[0]) {
+		x.Errorf("failed with seed %d: unsigned.", seed)
+		return
+	}
+	if len(decrypted.Signature) != signatureLength {
+		x.Errorf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
+		return
+	}
+	if !isPubKeyEqual(decrypted.Src, &params.Src.PublicKey) {
+		x.Errorf("failed with seed %d: signature mismatch.", seed)
+		return
+	}
+	if decrypted.isAsymmetricEncryption() == symmetric {
+		x.Errorf("failed with seed %d: asymmetric %v vs. %v.", seed, decrypted.isAsymmetricEncryption(), symmetric)
+		return
+	}
+	if decrypted.isSymmetricEncryption() != symmetric {
+		x.Errorf("failed with seed %d: symmetric %v vs. %v.", seed, decrypted.isSymmetricEncryption(), symmetric)
+		return
+	}
+	if !symmetric {
+		if decrypted.Dst == nil {
+			x.Errorf("failed with seed %d: dst is nil.", seed)
+			return
+		}
+		if !isPubKeyEqual(decrypted.Dst, &key.PublicKey) {
+			x.Errorf("failed with seed %d: Dst.", seed)
+			return
+		}
+	}
+}
diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go
new file mode 100644
index 000000000..fc1afb6d6
--- /dev/null
+++ b/whisper/whisperv5/peer.go
@@ -0,0 +1,174 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/rlp"
+	set "gopkg.in/fatih/set.v0"
+)
+
+// peer represents a whisper protocol peer connection.
+type Peer struct {
+	host    *Whisper
+	peer    *p2p.Peer
+	ws      p2p.MsgReadWriter
+	trusted bool
+
+	known *set.Set // Messages already known by the peer to avoid wasting bandwidth
+
+	quit chan struct{}
+}
+
+// newPeer creates a new whisper peer object, but does not run the handshake itself.
+func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
+	return &Peer{
+		host:    host,
+		peer:    remote,
+		ws:      rw,
+		trusted: false,
+		known:   set.New(),
+		quit:    make(chan struct{}),
+	}
+}
+
+// start initiates the peer updater, periodically broadcasting the whisper packets
+// into the network.
+func (p *Peer) start() {
+	go p.update()
+	glog.V(logger.Debug).Infof("%v: whisper started", p.peer)
+}
+
+// stop terminates the peer updater, stopping message forwarding to it.
+func (p *Peer) stop() {
+	close(p.quit)
+	glog.V(logger.Debug).Infof("%v: whisper stopped", p.peer)
+}
+
+// handshake sends the protocol initiation status message to the remote peer and
+// verifies the remote status too.
+func (p *Peer) handshake() error {
+	// Send the handshake status message asynchronously
+	errc := make(chan error, 1)
+	go func() {
+		errc <- p2p.Send(p.ws, statusCode, ProtocolVersion)
+	}()
+	// Fetch the remote status packet and verify protocol match
+	packet, err := p.ws.ReadMsg()
+	if err != nil {
+		return err
+	}
+	if packet.Code != statusCode {
+		return fmt.Errorf("peer sent %x before status packet", packet.Code)
+	}
+	s := rlp.NewStream(packet.Payload, uint64(packet.Size))
+	peerVersion, err := s.Uint()
+	if err != nil {
+		return fmt.Errorf("bad status message: %v", err)
+	}
+	if peerVersion != ProtocolVersion {
+		return fmt.Errorf("protocol version mismatch %d != %d", peerVersion, ProtocolVersion)
+	}
+	// Wait until out own status is consumed too
+	if err := <-errc; err != nil {
+		return fmt.Errorf("failed to send status packet: %v", err)
+	}
+	return nil
+}
+
+// update executes periodic operations on the peer, including message transmission
+// and expiration.
+func (p *Peer) update() {
+	// Start the tickers for the updates
+	expire := time.NewTicker(expirationCycle)
+	transmit := time.NewTicker(transmissionCycle)
+
+	// Loop and transmit until termination is requested
+	for {
+		select {
+		case <-expire.C:
+			p.expire()
+
+		case <-transmit.C:
+			if err := p.broadcast(); err != nil {
+				glog.V(logger.Info).Infof("%v: broadcast failed: %v", p.peer, err)
+				return
+			}
+
+		case <-p.quit:
+			return
+		}
+	}
+}
+
+// mark marks an envelope known to the peer so that it won't be sent back.
+func (peer *Peer) mark(envelope *Envelope) {
+	peer.known.Add(envelope.Hash())
+}
+
+// marked checks if an envelope is already known to the remote peer.
+func (peer *Peer) marked(envelope *Envelope) bool {
+	return peer.known.Has(envelope.Hash())
+}
+
+// expire iterates over all the known envelopes in the host and removes all
+// expired (unknown) ones from the known list.
+func (peer *Peer) expire() {
+	// Assemble the list of available envelopes
+	available := set.NewNonTS()
+	for _, envelope := range peer.host.Envelopes() {
+		available.Add(envelope.Hash())
+	}
+	// Cross reference availability with known status
+	unmark := make(map[common.Hash]struct{})
+	peer.known.Each(func(v interface{}) bool {
+		if !available.Has(v.(common.Hash)) {
+			unmark[v.(common.Hash)] = struct{}{}
+		}
+		return true
+	})
+	// Dump all known but unavailable
+	for hash, _ := range unmark {
+		peer.known.Remove(hash)
+	}
+}
+
+// broadcast iterates over the collection of envelopes and transmits yet unknown
+// ones over the network.
+func (p *Peer) broadcast() error {
+	// Fetch the envelopes and collect the unknown ones
+	envelopes := p.host.Envelopes()
+	transmit := make([]*Envelope, 0, len(envelopes))
+	for _, envelope := range envelopes {
+		if !p.marked(envelope) {
+			transmit = append(transmit, envelope)
+			p.mark(envelope)
+		}
+	}
+	// Transmit the unknown batch (potentially empty)
+	if err := p2p.Send(p.ws, messagesCode, transmit); err != nil {
+		return err
+	}
+	glog.V(logger.Detail).Infoln(p.peer, "broadcasted", len(transmit), "message(s)")
+	return nil
+}
diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go
new file mode 100644
index 000000000..03c4725d8
--- /dev/null
+++ b/whisper/whisperv5/peer_test.go
@@ -0,0 +1,307 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"fmt"
+	"net"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/discover"
+	"github.com/ethereum/go-ethereum/p2p/nat"
+)
+
+var keys []string = []string{
+	"d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9",
+	"73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98",
+	"119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc",
+	"deeda8709dea935bb772248a3144dea449ffcc13e8e5a1fd4ef20ce4e9c87837",
+	"5bd208a079633befa349441bdfdc4d85ba9bd56081525008380a63ac38a407cf",
+	"1d27fb4912002d58a2a42a50c97edb05c1b3dffc665dbaa42df1fe8d3d95c9b5",
+	"15def52800c9d6b8ca6f3066b7767a76afc7b611786c1276165fbc61636afb68",
+	"51be6ab4b2dc89f251ff2ace10f3c1cc65d6855f3e083f91f6ff8efdfd28b48c",
+	"ef1ef7441bf3c6419b162f05da6037474664f198b58db7315a6f4de52414b4a0",
+	"09bdf6985aabc696dc1fbeb5381aebd7a6421727343872eb2fadfc6d82486fd9",
+	"15d811bf2e01f99a224cdc91d0cf76cea08e8c67905c16fee9725c9be71185c4",
+	"2f83e45cf1baaea779789f755b7da72d8857aeebff19362dd9af31d3c9d14620",
+	"73f04e34ac6532b19c2aae8f8e52f38df1ac8f5cd10369f92325b9b0494b0590",
+	"1e2e07b69e5025537fb73770f483dc8d64f84ae3403775ef61cd36e3faf162c1",
+	"8963d9bbb3911aac6d30388c786756b1c423c4fbbc95d1f96ddbddf39809e43a",
+	"0422da85abc48249270b45d8de38a4cc3c02032ede1fcf0864a51092d58a2f1f",
+	"8ae5c15b0e8c7cade201fdc149831aa9b11ff626a7ffd27188886cc108ad0fa8",
+	"acd8f5a71d4aecfcb9ad00d32aa4bcf2a602939b6a9dd071bab443154184f805",
+	"a285a922125a7481600782ad69debfbcdb0316c1e97c267aff29ef50001ec045",
+	"28fd4eee78c6cd4bf78f39f8ab30c32c67c24a6223baa40e6f9c9a0e1de7cef5",
+	"c5cca0c9e6f043b288c6f1aef448ab59132dab3e453671af5d0752961f013fc7",
+	"46df99b051838cb6f8d1b73f232af516886bd8c4d0ee07af9a0a033c391380fd",
+	"c6a06a53cbaadbb432884f36155c8f3244e244881b5ee3e92e974cfa166d793f",
+	"783b90c75c63dc72e2f8d11b6f1b4de54d63825330ec76ee8db34f06b38ea211",
+	"9450038f10ca2c097a8013e5121b36b422b95b04892232f930a29292d9935611",
+	"e215e6246ed1cfdcf7310d4d8cdbe370f0d6a8371e4eb1089e2ae05c0e1bc10f",
+	"487110939ed9d64ebbc1f300adeab358bc58875faf4ca64990fbd7fe03b78f2b",
+	"824a70ea76ac81366da1d4f4ac39de851c8ac49dca456bb3f0a186ceefa269a5",
+	"ba8f34fa40945560d1006a328fe70c42e35cc3d1017e72d26864cd0d1b150f15",
+	"30a5dfcfd144997f428901ea88a43c8d176b19c79dde54cc58eea001aa3d246c",
+	"de59f7183aca39aa245ce66a05245fecfc7e2c75884184b52b27734a4a58efa2",
+	"92629e2ff5f0cb4f5f08fffe0f64492024d36f045b901efb271674b801095c5a",
+	"7184c1701569e3a4c4d2ddce691edd983b81e42e09196d332e1ae2f1e062cff4",
+}
+
+const NumNodes = 16 // must not exceed the number of keys (32)
+
+type TestData struct {
+	counter [NumNodes]int
+	mutex   sync.RWMutex
+}
+
+type TestNode struct {
+	shh     *Whisper
+	id      *ecdsa.PrivateKey
+	server  *p2p.Server
+	filerId int
+}
+
+var result TestData
+var nodes [NumNodes]*TestNode
+var sharedKey []byte = []byte("some arbitrary data here")
+var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0}
+var expectedMessage []byte = []byte("per rectum ad astra")
+
+// This test does the following:
+// 1. creates a chain of whisper nodes,
+// 2. installs the filters with shared (predefined) parameters,
+// 3. each node sends a number of random (undecryptable) messages,
+// 4. first node sends one expected (decryptable) message,
+// 5. checks if each node have received and decrypted exactly one message.
+func TestSimulation(x *testing.T) {
+	initialize(x)
+
+	for i := 0; i < NumNodes; i++ {
+		sendMsg(x, false, i)
+	}
+
+	sendMsg(x, true, 0)
+	checkPropagation(x)
+	stopServers()
+}
+
+func initialize(x *testing.T) {
+	//glog.SetV(6)
+	//glog.SetToStderr(true)
+
+	var err error
+	ip := net.IPv4(127, 0, 0, 1)
+	port0 := 30303
+
+	for i := 0; i < NumNodes; i++ {
+		var node TestNode
+		node.shh = NewWhisper(nil)
+		node.shh.test = true
+		tt := make([]TopicType, 0)
+		tt = append(tt, sharedTopic)
+		f := Filter{KeySym: sharedKey, Topics: tt}
+		node.filerId = node.shh.Watch(&f)
+		node.id, err = crypto.HexToECDSA(keys[i])
+		if err != nil {
+			x.Errorf("failed convert the key: %s.", keys[i])
+			return
+		}
+		port := port0 + i
+		addr := fmt.Sprintf(":%d", port) // e.g. ":30303"
+		name := common.MakeName("whisper-go", "2.0")
+		var peers []*discover.Node
+		if i > 0 {
+			peerNodeId := nodes[i-1].id
+			peerPort := uint16(port - 1)
+			peerNode := discover.PubkeyID(&peerNodeId.PublicKey)
+			peer := discover.NewNode(peerNode, ip, peerPort, peerPort)
+			peers = append(peers, peer)
+		}
+
+		node.server = &p2p.Server{
+			Config: p2p.Config{
+				PrivateKey:     node.id,
+				MaxPeers:       NumNodes/2 + 1,
+				Name:           name,
+				Protocols:      node.shh.Protocols(),
+				ListenAddr:     addr,
+				NAT:            nat.Any(),
+				BootstrapNodes: peers,
+				StaticNodes:    peers,
+				TrustedNodes:   peers,
+			},
+		}
+
+		err = node.server.Start()
+		if err != nil {
+			x.Errorf("failed to start server %d.", i)
+			return
+		}
+
+		nodes[i] = &node
+	}
+}
+
+func stopServers() {
+	for i := 0; i < NumNodes; i++ {
+		n := nodes[i]
+		if n != nil {
+			n.shh.Unwatch(n.filerId)
+			n.server.Stop()
+		}
+	}
+}
+
+func checkPropagation(x *testing.T) {
+	if x.Failed() {
+		return
+	}
+
+	const cycle = 100
+	const iterations = 100
+
+	for j := 0; j < iterations; j++ {
+		time.Sleep(cycle * time.Millisecond)
+
+		for i := 0; i < NumNodes; i++ {
+			f := nodes[i].shh.GetFilter(nodes[i].filerId)
+			if f == nil {
+				x.Errorf("failed to get filterId %d from node %d.", nodes[i].filerId, i)
+				return
+			}
+
+			mail := f.Retrieve()
+			if !validateMail(x, i, mail) {
+				return
+			}
+
+			if isTestComplete() {
+
+				return
+			}
+		}
+	}
+
+	x.Errorf("Test was not complete: timeout %d seconds.", iterations*cycle/1000)
+}
+
+func validateMail(x *testing.T, index int, mail []*ReceivedMessage) bool {
+	var cnt int
+	for _, m := range mail {
+		if bytes.Compare(m.Payload, expectedMessage) == 0 {
+			cnt++
+		}
+	}
+
+	if cnt == 0 {
+		// no messages received yet: nothing is wrong
+		return true
+	}
+	if cnt > 1 {
+		x.Errorf("node %d received %d.", index, cnt)
+		return false
+	}
+
+	if cnt > 0 {
+		result.mutex.Lock()
+		defer result.mutex.Unlock()
+		result.counter[index] += cnt
+		if result.counter[index] > 1 {
+			x.Errorf("node %d accumulated %d.", index, result.counter[index])
+			return false
+		}
+	}
+	return true
+}
+
+func isTestComplete() bool {
+	result.mutex.RLock()
+	defer result.mutex.RUnlock()
+
+	for i := 0; i < NumNodes; i++ {
+		if result.counter[i] < 1 {
+			return false
+		}
+	}
+
+	for i := 0; i < NumNodes; i++ {
+		envelopes := nodes[i].shh.Envelopes()
+		if len(envelopes) < 2 {
+			return false
+		}
+	}
+
+	return true
+}
+
+func sendMsg(x *testing.T, expected bool, id int) {
+	if x.Failed() {
+		return
+	}
+
+	opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001}
+	if !expected {
+		opt.KeySym[0]++
+		opt.Topic[0]++
+		opt.Payload = opt.Payload[1:]
+	}
+
+	msg := NewSentMessage(&opt)
+	envelope, err := msg.Wrap(&opt)
+	if err != nil {
+		x.Errorf("failed to seal message.")
+		return
+	}
+
+	err = nodes[id].shh.Send(envelope)
+	if err != nil {
+		x.Errorf("failed to send message.")
+		return
+	}
+}
+
+func TestPeerBasic(x *testing.T) {
+	InitSingleTest()
+
+	params, err := generateMessageParams()
+	if err != nil {
+		x.Errorf("failed 1 with seed %d.", seed)
+		return
+	}
+
+	params.PoW = 0.001
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		x.Errorf("failed 2 with seed %d.", seed)
+		return
+	}
+
+	p := newPeer(nil, nil, nil)
+	p.mark(env)
+	if !p.marked(env) {
+		x.Errorf("failed 3 with seed %d.", seed)
+		return
+	}
+}
diff --git a/whisper/whisperv5/topic.go b/whisper/whisperv5/topic.go
new file mode 100644
index 000000000..c29c344be
--- /dev/null
+++ b/whisper/whisperv5/topic.go
@@ -0,0 +1,70 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// 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 whisperv5
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// 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 TopicType [TopicLength]byte
+
+func BytesToTopic(b []byte) (t TopicType) {
+	sz := TopicLength
+	if x := len(b); x < TopicLength {
+		sz = x
+	}
+	for i := 0; i < sz; i++ {
+		t[i] = b[i]
+	}
+	return t
+}
+
+// String converts a topic byte array to a string representation.
+func (topic *TopicType) String() string {
+	return string(common.ToHex(topic[:]))
+}
+
+// UnmarshalJSON parses a hex representation to a topic.
+func (t *TopicType) UnmarshalJSON(input []byte) error {
+	length := len(input)
+	if length >= 2 && input[0] == '"' && input[length-1] == '"' {
+		input = input[1 : length-1]
+	}
+	// strip "0x" for length check
+	if len(input) > 1 && strings.ToLower(string(input[:2])) == "0x" {
+		input = input[2:]
+	}
+	// validate the length of the input
+	if len(input) != TopicLength*2 {
+		return fmt.Errorf("unmarshalJSON failed: topic must be exactly %d bytes", TopicLength)
+	}
+	b := common.FromHex(string(input))
+	if b == nil {
+		return fmt.Errorf("unmarshalJSON failed: wrong topic format")
+	}
+	*t = BytesToTopic(b)
+	return nil
+}
diff --git a/whisper/whisperv5/topic_test.go b/whisper/whisperv5/topic_test.go
new file mode 100644
index 000000000..c2a940b79
--- /dev/null
+++ b/whisper/whisperv5/topic_test.go
@@ -0,0 +1,136 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import "testing"
+
+var topicStringTests = []struct {
+	topic TopicType
+	str   string
+}{
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"},
+	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"},
+	{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"},
+	{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"},
+}
+
+func TestTopicString(x *testing.T) {
+	for i, tst := range topicStringTests {
+		s := tst.topic.String()
+		if s != tst.str {
+			x.Errorf("failed test %d: have %s, want %s.", i, s, tst.str)
+		}
+	}
+}
+
+var bytesToTopicTests = []struct {
+	data  []byte
+	topic TopicType
+}{
+	{topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}},
+	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}},
+	{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}},
+	{topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}},
+	{topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}},
+	{topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil},
+}
+
+func TestBytesToTopic(x *testing.T) {
+	for i, tst := range bytesToTopicTests {
+		t := BytesToTopic(tst.data)
+		if t != tst.topic {
+			x.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
+		}
+	}
+}
+
+var unmarshalTestsGood = []struct {
+	topic TopicType
+	data  []byte
+}{
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0x00000000")},
+	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte("0x007f80ff")},
+	{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte("0xff807f00")},
+	{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte("0xf26e7779")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("00000000")},
+	{topic: TopicType{0x00, 0x80, 0x01, 0x00}, data: []byte("00800100")},
+	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte("007f80ff")},
+	{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte("ff807f00")},
+	{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte("f26e7779")},
+}
+
+var unmarshalTestsBad = []struct {
+	topic TopicType
+	data  []byte
+}{
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0x000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0x0000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0x000000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0x0000000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("000000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("0000000000")},
+	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte("abcdefg0")},
+}
+
+var unmarshalTestsUgly = []struct {
+	topic TopicType
+	data  []byte
+}{
+	{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte("00000001")},
+}
+
+func TestUnmarshalTestsGood(x *testing.T) {
+	for i, tst := range unmarshalTestsGood {
+		var t TopicType
+		err := t.UnmarshalJSON(tst.data)
+		if err != nil {
+			x.Errorf("failed test %d. input: %v.", i, tst.data)
+		} else if t != tst.topic {
+			x.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
+		}
+	}
+}
+
+func TestUnmarshalTestsBad(x *testing.T) {
+	// in this test UnmarshalJSON() is supposed to fail
+	for i, tst := range unmarshalTestsBad {
+		var t TopicType
+		err := t.UnmarshalJSON(tst.data)
+		if err == nil {
+			x.Errorf("failed test %d. input: %v.", i, tst.data)
+		}
+	}
+}
+
+func TestUnmarshalTestsUgly(x *testing.T) {
+	// in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong
+	for i, tst := range unmarshalTestsUgly {
+		var t TopicType
+		err := t.UnmarshalJSON(tst.data)
+		if err != nil {
+			x.Errorf("failed test %d. input: %v.", i, tst.data)
+		} else if t == tst.topic {
+			x.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
+		}
+	}
+}
diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go
new file mode 100644
index 000000000..836810824
--- /dev/null
+++ b/whisper/whisperv5/whisper.go
@@ -0,0 +1,585 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"crypto/sha256"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/logger"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/rlp"
+	"golang.org/x/crypto/pbkdf2"
+	set "gopkg.in/fatih/set.v0"
+)
+
+// Whisper represents a dark communication interface through the Ethereum
+// network, using its very own P2P communication layer.
+type Whisper struct {
+	protocol p2p.Protocol
+	filters  *Filters
+
+	privateKeys map[string]*ecdsa.PrivateKey
+	symKeys     map[string][]byte
+	keyMu       sync.RWMutex
+
+	envelopes   map[common.Hash]*Envelope        // Pool of messages currently tracked by this node
+	messages    map[common.Hash]*ReceivedMessage // Pool of successfully decrypted messages, which are not expired yet
+	expirations map[uint32]*set.SetNonTS         // Message expiration pool
+	poolMu      sync.RWMutex                     // Mutex to sync the message and expiration pools
+
+	peers  map[*Peer]struct{} // Set of currently active peers
+	peerMu sync.RWMutex       // Mutex to sync the active peer set
+
+	mailServer MailServer
+
+	quit chan struct{}
+	test bool
+}
+
+// New creates a Whisper client ready to communicate through the Ethereum P2P network.
+// Param s should be passed if you want to implement mail server, otherwise nil.
+func NewWhisper(server MailServer) *Whisper {
+	whisper := &Whisper{
+		privateKeys: make(map[string]*ecdsa.PrivateKey),
+		symKeys:     make(map[string][]byte),
+		envelopes:   make(map[common.Hash]*Envelope),
+		messages:    make(map[common.Hash]*ReceivedMessage),
+		expirations: make(map[uint32]*set.SetNonTS),
+		peers:       make(map[*Peer]struct{}),
+		mailServer:  server,
+		quit:        make(chan struct{}),
+	}
+	whisper.filters = NewFilters(whisper)
+
+	// p2p whisper sub protocol handler
+	whisper.protocol = p2p.Protocol{
+		Name:    ProtocolName,
+		Version: uint(ProtocolVersion),
+		Length:  NumberOfMessageCodes,
+		Run:     whisper.HandlePeer,
+	}
+
+	return whisper
+}
+
+// Protocols returns the whisper sub-protocols ran by this particular client.
+func (w *Whisper) Protocols() []p2p.Protocol {
+	return []p2p.Protocol{w.protocol}
+}
+
+// Version returns the whisper sub-protocols version number.
+func (w *Whisper) Version() uint {
+	return w.protocol.Version
+}
+
+func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
+	w.peerMu.Lock()
+	defer w.peerMu.Unlock()
+	for p, _ := range w.peers {
+		id := p.peer.ID()
+		if bytes.Equal(peerID, id[:]) {
+			return p, nil
+		}
+	}
+	return nil, fmt.Errorf("Could not find peer with ID: %x", peerID)
+}
+
+// MarkPeerTrusted marks specific peer trusted, which will allow it
+// to send historic (expired) messages.
+func (w *Whisper) MarkPeerTrusted(peerID []byte) error {
+	p, err := w.getPeer(peerID)
+	if err != nil {
+		return err
+	}
+	p.trusted = true
+	return nil
+}
+
+func (w *Whisper) RequestHistoricMessages(peerID []byte, data []byte) error {
+	p, err := w.getPeer(peerID)
+	if err != nil {
+		return err
+	}
+	p.trusted = true
+	return p2p.Send(p.ws, mailRequestCode, data)
+}
+
+func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
+	p, err := w.getPeer(peerID)
+	if err != nil {
+		return err
+	}
+	return p2p.Send(p.ws, p2pCode, envelope)
+}
+
+// NewIdentity generates a new cryptographic identity for the client, and injects
+// it into the known identities for message decryption.
+func (w *Whisper) NewIdentity() *ecdsa.PrivateKey {
+	key, err := crypto.GenerateKey()
+	if err != nil || !validatePrivateKey(key) {
+		key, err = crypto.GenerateKey() // retry once
+	}
+	if err != nil {
+		panic(err)
+	}
+	if !validatePrivateKey(key) {
+		panic("Failed to generate valid key")
+	}
+	w.keyMu.Lock()
+	defer w.keyMu.Unlock()
+	w.privateKeys[common.ToHex(crypto.FromECDSAPub(&key.PublicKey))] = key
+	return key
+}
+
+// DeleteIdentity deletes the specified key if it exists.
+func (w *Whisper) DeleteIdentity(key string) {
+	w.keyMu.Lock()
+	defer w.keyMu.Unlock()
+	delete(w.privateKeys, key)
+}
+
+// HasIdentity checks if the the whisper node is configured with the private key
+// of the specified public pair.
+func (w *Whisper) HasIdentity(pubKey string) bool {
+	w.keyMu.RLock()
+	defer w.keyMu.RUnlock()
+	return w.privateKeys[pubKey] != nil
+}
+
+// GetIdentity retrieves the private key of the specified public identity.
+func (w *Whisper) GetIdentity(pubKey string) *ecdsa.PrivateKey {
+	w.keyMu.RLock()
+	defer w.keyMu.RUnlock()
+	return w.privateKeys[pubKey]
+}
+
+func (w *Whisper) GenerateSymKey(name string) error {
+	buf := make([]byte, aesKeyLength*2)
+	_, err := crand.Read(buf) // todo: check how safe is this function
+	if err != nil {
+		return err
+	} else if !validateSymmetricKey(buf) {
+		return fmt.Errorf("crypto/rand failed to generate random data")
+	}
+
+	key := buf[:aesKeyLength]
+	salt := buf[aesKeyLength:]
+	derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion)
+	if err != nil {
+		return err
+	} else if !validateSymmetricKey(derived) {
+		return fmt.Errorf("failed to derive valid key")
+	}
+
+	w.keyMu.Lock()
+	defer w.keyMu.Unlock()
+
+	if w.symKeys[name] != nil {
+		return fmt.Errorf("Key with name [%s] already exists", name)
+	}
+	w.symKeys[name] = derived
+	return nil
+}
+
+func (w *Whisper) AddSymKey(name string, key []byte) error {
+	if w.HasSymKey(name) {
+		return fmt.Errorf("Key with name [%s] already exists", name)
+	}
+
+	derived, err := deriveKeyMaterial(key, EnvelopeVersion)
+	if err != nil {
+		return err
+	}
+
+	w.keyMu.Lock()
+	defer w.keyMu.Unlock()
+
+	// double check is necessary, because deriveKeyMaterial() is slow
+	if w.symKeys[name] != nil {
+		return fmt.Errorf("Key with name [%s] already exists", name)
+	}
+	w.symKeys[name] = derived
+	return nil
+}
+
+func (w *Whisper) HasSymKey(name string) bool {
+	w.keyMu.RLock()
+	defer w.keyMu.RUnlock()
+	return w.symKeys[name] != nil
+}
+
+func (w *Whisper) DeleteSymKey(name string) {
+	w.keyMu.Lock()
+	defer w.keyMu.Unlock()
+	delete(w.symKeys, name)
+}
+
+func (w *Whisper) GetSymKey(name string) []byte {
+	w.keyMu.RLock()
+	defer w.keyMu.RUnlock()
+	return w.symKeys[name]
+}
+
+// Watch installs a new message handler to run in case a matching packet arrives
+// from the whisper network.
+func (w *Whisper) Watch(f *Filter) int {
+	return w.filters.Install(f)
+}
+
+func (w *Whisper) GetFilter(id int) *Filter {
+	return w.filters.Get(id)
+}
+
+// Unwatch removes an installed message handler.
+func (w *Whisper) Unwatch(id int) {
+	w.filters.Uninstall(id)
+}
+
+// Send injects a message into the whisper send queue, to be distributed in the
+// network in the coming cycles.
+func (w *Whisper) Send(envelope *Envelope) error {
+	return w.add(envelope)
+}
+
+// Start implements node.Service, starting the background data propagation thread
+// of the Whisper protocol.
+func (w *Whisper) Start(*p2p.Server) error {
+	glog.V(logger.Info).Infoln("Whisper started")
+	go w.update()
+	return nil
+}
+
+// Stop implements node.Service, stopping the background data propagation thread
+// of the Whisper protocol.
+func (w *Whisper) Stop() error {
+	close(w.quit)
+	glog.V(logger.Info).Infoln("Whisper stopped")
+	return nil
+}
+
+// handlePeer is called by the underlying P2P layer when the whisper sub-protocol
+// connection is negotiated.
+func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
+	// Create the new peer and start tracking it
+	whisperPeer := newPeer(wh, peer, rw)
+
+	wh.peerMu.Lock()
+	wh.peers[whisperPeer] = struct{}{}
+	wh.peerMu.Unlock()
+
+	defer func() {
+		wh.peerMu.Lock()
+		delete(wh.peers, whisperPeer)
+		wh.peerMu.Unlock()
+	}()
+
+	// Run the peer handshake and state updates
+	if err := whisperPeer.handshake(); err != nil {
+		return err
+	}
+	whisperPeer.start()
+	defer whisperPeer.stop()
+
+	return wh.runMessageLoop(whisperPeer, rw)
+}
+
+// runMessageLoop reads and processes inbound messages directly to merge into client-global state.
+func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
+	for {
+		// fetch the next packet
+		packet, err := rw.ReadMsg()
+		if err != nil {
+			return err
+		}
+
+		switch packet.Code {
+		case statusCode:
+			// this should not happen, but no need to panic; just ignore this message.
+			glog.V(logger.Warn).Infof("%v: unxepected status message received", p.peer)
+		case messagesCode:
+			// decode the contained envelopes
+			var envelopes []*Envelope
+			if err := packet.Decode(&envelopes); err != nil {
+				glog.V(logger.Warn).Infof("%v: failed to decode envelope: [%v], peer will be disconnected", p.peer, err)
+				return fmt.Errorf("garbage received")
+			}
+			// inject all envelopes into the internal pool
+			for _, envelope := range envelopes {
+				if err := wh.add(envelope); err != nil {
+					glog.V(logger.Warn).Infof("%v: bad envelope received: [%v], peer will be disconnected", p.peer, err)
+					return fmt.Errorf("invalid envelope")
+				}
+				p.mark(envelope)
+				if wh.mailServer != nil {
+					wh.mailServer.Archive(envelope)
+				}
+			}
+		case p2pCode:
+			// peer-to-peer message, sent directly to peer bypassing PoW checks, etc.
+			// this message is not supposed to be forwarded to other peers, and
+			// therefore might not satisfy the PoW, expiry and other requirements.
+			// these messages are only accepted from the trusted peer.
+			if p.trusted {
+				var envelopes []*Envelope
+				if err := packet.Decode(&envelopes); err != nil {
+					glog.V(logger.Warn).Infof("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err)
+					return fmt.Errorf("garbage received (directMessage)")
+				}
+				for _, envelope := range envelopes {
+					wh.postEvent(envelope, p2pCode)
+				}
+			}
+		case mailRequestCode:
+			// Must be processed if mail server is implemented. Otherwise ignore.
+			if wh.mailServer != nil {
+				s := rlp.NewStream(packet.Payload, uint64(packet.Size))
+				data, err := s.Bytes()
+				if err == nil {
+					wh.mailServer.DeliverMail(p, data)
+				} else {
+					glog.V(logger.Error).Infof("%v: bad requestHistoricMessages received: [%v]", p.peer, err)
+				}
+			}
+		default:
+			// New message types might be implemented in the future versions of Whisper.
+			// For forward compatibility, just ignore.
+		}
+
+		packet.Discard()
+	}
+}
+
+// add inserts a new envelope into the message pool to be distributed within the
+// whisper network. It also inserts the envelope into the expiration pool at the
+// appropriate time-stamp. In case of error, connection should be dropped.
+func (wh *Whisper) add(envelope *Envelope) error {
+	now := uint32(time.Now().Unix())
+	sent := envelope.Expiry - envelope.TTL
+
+	if sent > now {
+		if sent-SynchAllowance > now {
+			return fmt.Errorf("message created in the future")
+		} else {
+			// recalculate PoW, adjusted for the time difference, plus one second for latency
+			envelope.calculatePoW(sent - now + 1)
+		}
+	}
+
+	if envelope.Expiry < now {
+		if envelope.Expiry+SynchAllowance*2 < now {
+			return fmt.Errorf("very old message")
+		} else {
+			return nil // drop envelope without error
+		}
+	}
+
+	if len(envelope.Data) > MaxMessageLength {
+		return fmt.Errorf("huge messages are not allowed")
+	}
+
+	if len(envelope.Version) > 4 {
+		return fmt.Errorf("oversized Version")
+	}
+
+	if len(envelope.AESNonce) > 12 {
+		// the standard AES GSM nonce size is 12,
+		// but const gcmStandardNonceSize cannot be accessed directly
+		return fmt.Errorf("oversized AESNonce")
+	}
+
+	if len(envelope.Salt) > saltLength {
+		return fmt.Errorf("oversized Salt")
+	}
+
+	if envelope.PoW() < MinimumPoW && !wh.test {
+		glog.V(logger.Debug).Infof("envelope with low PoW dropped: %f", envelope.PoW())
+		return nil // drop envelope without error
+	}
+
+	hash := envelope.Hash()
+
+	wh.poolMu.Lock()
+	_, alreadyCached := wh.envelopes[hash]
+	if !alreadyCached {
+		wh.envelopes[hash] = envelope
+		if wh.expirations[envelope.Expiry] == nil {
+			wh.expirations[envelope.Expiry] = set.NewNonTS()
+		}
+		if !wh.expirations[envelope.Expiry].Has(hash) {
+			wh.expirations[envelope.Expiry].Add(hash)
+		}
+	}
+	wh.poolMu.Unlock()
+
+	if alreadyCached {
+		glog.V(logger.Detail).Infof("whisper envelope already cached: %x\n", envelope)
+	} else {
+		wh.postEvent(envelope, messagesCode) // notify the local node about the new message
+		glog.V(logger.Detail).Infof("cached whisper envelope %v\n", envelope)
+	}
+	return nil
+}
+
+// postEvent delivers the message to the watchers.
+func (w *Whisper) postEvent(envelope *Envelope, messageCode uint64) {
+	// if the version of incoming message is higher than
+	// currently supported version, we can not decrypt it,
+	// and therefore just ignore this message
+	if envelope.Ver() <= EnvelopeVersion {
+		// todo: review if you need an additional thread here
+		go w.filters.NotifyWatchers(envelope, messageCode)
+	}
+}
+
+// update loops until the lifetime of the whisper node, updating its internal
+// state by expiring stale messages from the pool.
+func (w *Whisper) update() {
+	// Start a ticker to check for expirations
+	expire := time.NewTicker(expirationCycle)
+
+	// Repeat updates until termination is requested
+	for {
+		select {
+		case <-expire.C:
+			w.expire()
+
+		case <-w.quit:
+			return
+		}
+	}
+}
+
+// expire iterates over all the expiration timestamps, removing all stale
+// messages from the pools.
+func (w *Whisper) expire() {
+	w.poolMu.Lock()
+	defer w.poolMu.Unlock()
+
+	now := uint32(time.Now().Unix())
+	for then, hashSet := range w.expirations {
+		// Short circuit if a future time
+		if then > now {
+			continue
+		}
+		// Dump all expired messages and remove timestamp
+		hashSet.Each(func(v interface{}) bool {
+			delete(w.envelopes, v.(common.Hash))
+			delete(w.messages, v.(common.Hash))
+			return true
+		})
+		w.expirations[then].Clear()
+	}
+}
+
+// envelopes retrieves all the messages currently pooled by the node.
+func (w *Whisper) Envelopes() []*Envelope {
+	w.poolMu.RLock()
+	defer w.poolMu.RUnlock()
+
+	all := make([]*Envelope, 0, len(w.envelopes))
+	for _, envelope := range w.envelopes {
+		all = append(all, envelope)
+	}
+	return all
+}
+
+// Messages retrieves all the decrypted messages matching a filter id.
+func (w *Whisper) Messages(id int) []*ReceivedMessage {
+	result := make([]*ReceivedMessage, 0)
+	w.poolMu.RLock()
+	defer w.poolMu.RUnlock()
+
+	if filter := w.filters.Get(id); filter != nil {
+		for _, msg := range w.messages {
+			if filter.MatchMessage(msg) {
+				result = append(result, msg)
+			}
+		}
+	}
+	return result
+}
+
+func (w *Whisper) addDecryptedMessage(msg *ReceivedMessage) {
+	w.poolMu.Lock()
+	defer w.poolMu.Unlock()
+
+	w.messages[msg.EnvelopeHash] = msg
+}
+
+func ValidatePublicKey(k *ecdsa.PublicKey) bool {
+	return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
+}
+
+func validatePrivateKey(k *ecdsa.PrivateKey) bool {
+	if k == nil || k.D == nil || k.D.Sign() == 0 {
+		return false
+	}
+	return ValidatePublicKey(&k.PublicKey)
+}
+
+// validateSymmetricKey returns false if the key contains all zeros
+func validateSymmetricKey(k []byte) bool {
+	return len(k) > 0 && !containsOnlyZeros(k)
+}
+
+func containsOnlyZeros(data []byte) bool {
+	for _, b := range data {
+		if b != 0 {
+			return false
+		}
+	}
+	return true
+}
+
+func bytesToIntLittleEndian(b []byte) (res uint64) {
+	mul := uint64(1)
+	for i := 0; i < len(b); i++ {
+		res += uint64(b[i]) * mul
+		mul *= 256
+	}
+	return res
+}
+
+func BytesToIntBigEndian(b []byte) (res uint64) {
+	for i := 0; i < len(b); i++ {
+		res *= 256
+		res += uint64(b[i])
+	}
+	return res
+}
+
+// DeriveSymmetricKey derives symmetric key material from the key or password.
+// pbkdf2 is used for security, in case people use password instead of randomly generated keys.
+func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error) {
+	if version == 0 {
+		// kdf should run no less than 0.1 seconds on average compute,
+		// because it's a once in a session experience
+		derivedKey := pbkdf2.Key(key, nil, 65356, aesKeyLength, sha256.New)
+		return derivedKey, nil
+	} else {
+		return nil, unknownVersionError(version)
+	}
+}
diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go
new file mode 100644
index 000000000..1db26265a
--- /dev/null
+++ b/whisper/whisperv5/whisper_test.go
@@ -0,0 +1,377 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package whisperv5
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+func TestWhisperBasic(x *testing.T) {
+	w := NewWhisper(nil)
+	p := w.Protocols()
+	shh := p[0]
+	if shh.Name != ProtocolName {
+		x.Errorf("failed Protocol Name: %v.", shh.Name)
+		return
+	}
+	if uint64(shh.Version) != ProtocolVersion {
+		x.Errorf("failed Protocol Version: %v.", shh.Version)
+		return
+	}
+	if shh.Length != NumberOfMessageCodes {
+		x.Errorf("failed Protocol Length: %v.", shh.Length)
+		return
+	}
+	if shh.Run == nil {
+		x.Errorf("failed shh.Run.")
+		return
+	}
+	if uint64(w.Version()) != ProtocolVersion {
+		x.Errorf("failed whisper Version: %v.", shh.Version)
+		return
+	}
+	if w.GetFilter(0) != nil {
+		x.Errorf("failed GetFilter.")
+		return
+	}
+
+	peerID := make([]byte, 64)
+	randomize(peerID)
+	peer, err := w.getPeer(peerID)
+	if peer != nil {
+		x.Errorf("failed GetPeer.")
+		return
+	}
+	err = w.MarkPeerTrusted(peerID)
+	if err == nil {
+		x.Errorf("failed MarkPeerTrusted.")
+		return
+	}
+	err = w.RequestHistoricMessages(peerID, peerID)
+	if err == nil {
+		x.Errorf("failed RequestHistoricMessages.")
+		return
+	}
+	err = w.SendP2PMessage(peerID, nil)
+	if err == nil {
+		x.Errorf("failed SendP2PMessage.")
+		return
+	}
+	exist := w.HasSymKey("non-existing")
+	if exist {
+		x.Errorf("failed HasSymKey.")
+		return
+	}
+	key := w.GetSymKey("non-existing")
+	if key != nil {
+		x.Errorf("failed GetSymKey.")
+		return
+	}
+	mail := w.Envelopes()
+	if len(mail) != 0 {
+		x.Errorf("failed w.Envelopes().")
+		return
+	}
+	m := w.Messages(0)
+	if len(m) != 0 {
+		x.Errorf("failed w.Messages.")
+		return
+	}
+
+	var derived []byte
+	ver := uint64(0xDEADBEEF)
+	derived, err = deriveKeyMaterial(peerID, ver)
+	if err != unknownVersionError(ver) {
+		x.Errorf("failed deriveKeyMaterial 1 with param = %v: %s.", peerID, err)
+		return
+	}
+	derived, err = deriveKeyMaterial(peerID, 0)
+	if err != nil {
+		x.Errorf("failed deriveKeyMaterial 2 with param = %v: %s.", peerID, err)
+		return
+	}
+	if !validateSymmetricKey(derived) {
+		x.Errorf("failed validateSymmetricKey with param = %v.", derived)
+		return
+	}
+	if containsOnlyZeros(derived) {
+		x.Errorf("failed containsOnlyZeros with param = %v.", derived)
+		return
+	}
+
+	buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0}
+	le := bytesToIntLittleEndian(buf)
+	be := BytesToIntBigEndian(buf)
+	if le != uint64(0x280e5ff) {
+		x.Errorf("failed bytesToIntLittleEndian: %d.", le)
+		return
+	}
+	if be != uint64(0xffe5800200) {
+		x.Errorf("failed BytesToIntBigEndian: %d.", be)
+		return
+	}
+
+	pk := w.NewIdentity()
+	if !validatePrivateKey(pk) {
+		x.Errorf("failed validatePrivateKey: %v.", pk)
+		return
+	}
+	if !ValidatePublicKey(&pk.PublicKey) {
+		x.Errorf("failed ValidatePublicKey: %v.", pk)
+		return
+	}
+}
+
+func TestWhisperIdentityManagement(x *testing.T) {
+	w := NewWhisper(nil)
+	id1 := w.NewIdentity()
+	id2 := w.NewIdentity()
+	pub1 := common.ToHex(crypto.FromECDSAPub(&id1.PublicKey))
+	pub2 := common.ToHex(crypto.FromECDSAPub(&id2.PublicKey))
+	pk1 := w.GetIdentity(pub1)
+	pk2 := w.GetIdentity(pub2)
+	if !w.HasIdentity(pub1) {
+		x.Errorf("failed HasIdentity 1.")
+		return
+	}
+	if !w.HasIdentity(pub2) {
+		x.Errorf("failed HasIdentity 2.")
+		return
+	}
+	if pk1 != id1 {
+		x.Errorf("failed GetIdentity 3.")
+		return
+	}
+	if pk2 != id2 {
+		x.Errorf("failed GetIdentity 4.")
+		return
+	}
+
+	// Delete one identity
+	w.DeleteIdentity(pub1)
+	pk1 = w.GetIdentity(pub1)
+	pk2 = w.GetIdentity(pub2)
+	if w.HasIdentity(pub1) {
+		x.Errorf("failed HasIdentity 11.")
+		return
+	}
+	if !w.HasIdentity(pub2) {
+		x.Errorf("failed HasIdentity 12.")
+		return
+	}
+	if pk1 != nil {
+		x.Errorf("failed GetIdentity 13.")
+		return
+	}
+	if pk2 != id2 {
+		x.Errorf("failed GetIdentity 14.")
+		return
+	}
+
+	// Delete again non-existing identity
+	w.DeleteIdentity(pub1)
+	pk1 = w.GetIdentity(pub1)
+	pk2 = w.GetIdentity(pub2)
+	if w.HasIdentity(pub1) {
+		x.Errorf("failed HasIdentity 21.")
+		return
+	}
+	if !w.HasIdentity(pub2) {
+		x.Errorf("failed HasIdentity 22.")
+		return
+	}
+	if pk1 != nil {
+		x.Errorf("failed GetIdentity 23.")
+		return
+	}
+	if pk2 != id2 {
+		x.Errorf("failed GetIdentity 24.")
+		return
+	}
+
+	// Delete second identity
+	w.DeleteIdentity(pub2)
+	pk1 = w.GetIdentity(pub1)
+	pk2 = w.GetIdentity(pub2)
+	if w.HasIdentity(pub1) {
+		x.Errorf("failed HasIdentity 31.")
+		return
+	}
+	if w.HasIdentity(pub2) {
+		x.Errorf("failed HasIdentity 32.")
+		return
+	}
+	if pk1 != nil {
+		x.Errorf("failed GetIdentity 33.")
+		return
+	}
+	if pk2 != nil {
+		x.Errorf("failed GetIdentity 34.")
+		return
+	}
+}
+
+func TestWhisperSymKeyManagement(x *testing.T) {
+	InitSingleTest()
+
+	var k1, k2 []byte
+	w := NewWhisper(nil)
+	id1 := string("arbitrary-string-1")
+	id2 := string("arbitrary-string-2")
+
+	err := w.GenerateSymKey(id1)
+	if err != nil {
+		x.Errorf("failed test case 1 with seed %d: %s.", seed, err)
+		return
+	}
+
+	k1 = w.GetSymKey(id1)
+	k2 = w.GetSymKey(id2)
+	if !w.HasSymKey(id1) {
+		x.Errorf("failed HasIdentity 2.")
+		return
+	}
+	if w.HasSymKey(id2) {
+		x.Errorf("failed HasIdentity 3.")
+		return
+	}
+	if k1 == nil {
+		x.Errorf("failed GetIdentity 4.")
+		return
+	}
+	if k2 != nil {
+		x.Errorf("failed GetIdentity 5.")
+		return
+	}
+
+	// add existing id, nothing should change
+	randomKey := make([]byte, 16)
+	randomize(randomKey)
+	err = w.AddSymKey(id1, randomKey)
+	if err == nil {
+		x.Errorf("failed test case 10 with seed %d.", seed)
+		return
+	}
+
+	k1 = w.GetSymKey(id1)
+	k2 = w.GetSymKey(id2)
+	if !w.HasSymKey(id1) {
+		x.Errorf("failed HasIdentity 12.")
+		return
+	}
+	if w.HasSymKey(id2) {
+		x.Errorf("failed HasIdentity 13.")
+		return
+	}
+	if k1 == nil {
+		x.Errorf("failed GetIdentity 14.")
+		return
+	}
+	if bytes.Compare(k1, randomKey) == 0 {
+		x.Errorf("failed GetIdentity 15: k1 == randomKey.")
+		return
+	}
+	if k2 != nil {
+		x.Errorf("failed GetIdentity 16.")
+		return
+	}
+
+	err = w.AddSymKey(id2, randomKey) // add non-existing (yet)
+	if err != nil {
+		x.Errorf("failed test case 21 with seed %d: %s.", seed, err)
+		return
+	}
+	k1 = w.GetSymKey(id1)
+	k2 = w.GetSymKey(id2)
+	if !w.HasSymKey(id1) {
+		x.Errorf("failed HasIdentity 22.")
+		return
+	}
+	if !w.HasSymKey(id2) {
+		x.Errorf("failed HasIdentity 23.")
+		return
+	}
+	if k1 == nil {
+		x.Errorf("failed GetIdentity 24.")
+		return
+	}
+	if k2 == nil {
+		x.Errorf("failed GetIdentity 25.")
+		return
+	}
+	if bytes.Compare(k1, k2) == 0 {
+		x.Errorf("failed GetIdentity 26.")
+		return
+	}
+	if bytes.Compare(k1, randomKey) == 0 {
+		x.Errorf("failed GetIdentity 27.")
+		return
+	}
+	if len(k1) != aesKeyLength {
+		x.Errorf("failed GetIdentity 28.")
+		return
+	}
+	if len(k2) != aesKeyLength {
+		x.Errorf("failed GetIdentity 29.")
+		return
+	}
+
+	w.DeleteSymKey(id1)
+	k1 = w.GetSymKey(id1)
+	k2 = w.GetSymKey(id2)
+	if w.HasSymKey(id1) {
+		x.Errorf("failed HasIdentity 31.")
+		return
+	}
+	if !w.HasSymKey(id2) {
+		x.Errorf("failed HasIdentity 32.")
+		return
+	}
+	if k1 != nil {
+		x.Errorf("failed GetIdentity 33.")
+		return
+	}
+	if k2 == nil {
+		x.Errorf("failed GetIdentity 34.")
+		return
+	}
+
+	w.DeleteSymKey(id1)
+	w.DeleteSymKey(id2)
+	k1 = w.GetSymKey(id1)
+	k2 = w.GetSymKey(id2)
+	if w.HasSymKey(id1) {
+		x.Errorf("failed HasIdentity 41.")
+		return
+	}
+	if w.HasSymKey(id2) {
+		x.Errorf("failed HasIdentity 42.")
+		return
+	}
+	if k1 != nil {
+		x.Errorf("failed GetIdentity 43.")
+		return
+	}
+	if k2 != nil {
+		x.Errorf("failed GetIdentity 44.")
+		return
+	}
+}
-- 
cgit