diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go
index 82d7eda3ceee5d3aa1d1d13a4c6193b1d57f636f..7431980b54a1fa105b6062252765a5ada00f3341 100644
--- a/cmd/wnode/main.go
+++ b/cmd/wnode/main.go
@@ -27,7 +27,9 @@ import (
 	"encoding/hex"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
@@ -46,7 +48,6 @@ import (
 )
 
 const quitCommand = "~Q"
-const symKeyName = "da919ea33001b04dfc630522e33078ec0df11"
 
 // singletons
 var (
@@ -64,7 +65,8 @@ var (
 	pub        *ecdsa.PublicKey
 	asymKey    *ecdsa.PrivateKey
 	nodeid     *ecdsa.PrivateKey
-	topic      whisper.TopicType
+	topic      []byte
+	asymKeyID  string
 	filterID   string
 	symPass    string
 	msPassword string
@@ -72,27 +74,30 @@ var (
 
 // cmd arguments
 var (
-	echoMode       = flag.Bool("e", false, "echo mode: prints some arguments for diagnostics")
-	bootstrapMode  = flag.Bool("b", false, "boostrap node: don't actively connect to peers, wait for incoming connections")
-	forwarderMode  = flag.Bool("f", false, "forwarder mode: only forward messages, neither send nor decrypt messages")
-	mailServerMode = flag.Bool("s", false, "mail server mode: delivers expired messages on demand")
-	requestMail    = flag.Bool("r", false, "request expired messages from the bootstrap server")
-	asymmetricMode = flag.Bool("a", false, "use asymmetric encryption")
-	testMode       = flag.Bool("t", false, "use of predefined parameters for diagnostics")
-	generateKey    = flag.Bool("k", false, "generate and show the private key")
+	bootstrapMode  = flag.Bool("standalone", false, "boostrap node: don't actively connect to peers, wait for incoming connections")
+	forwarderMode  = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither send nor decrypt messages")
+	mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand")
+	requestMail    = flag.Bool("mailclient", false, "request expired messages from the bootstrap server")
+	asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption")
+	generateKey    = flag.Bool("generatekey", false, "generate and show the private key")
+	fileExMode     = flag.Bool("fileexchange", false, "file exchange mode")
+	testMode       = flag.Bool("test", false, "use of predefined parameters for diagnostics")
+	echoMode       = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics")
 
 	argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level")
 	argTTL       = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
 	argWorkTime  = flag.Uint("work", 5, "work time in seconds")
-	argPoW       = flag.Float64("pow", whisper.MinimumPoW, "PoW for normal messages in float format (e.g. 2.7)")
-	argServerPoW = flag.Float64("mspow", whisper.MinimumPoW, "PoW requirement for Mail Server request")
-
-	argIP     = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)")
-	argPub    = flag.String("pub", "", "public key for asymmetric encryption")
-	argDBPath = flag.String("dbpath", "", "path to the server's DB directory")
-	argIDFile = flag.String("idfile", "", "file name with node id (private key)")
-	argEnode  = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)")
-	argTopic  = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)")
+	argMaxSize   = flag.Int("maxsize", whisper.DefaultMaxMessageLength, "max size of message")
+	argPoW       = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)")
+	argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request")
+
+	argIP      = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)")
+	argPub     = flag.String("pub", "", "public key for asymmetric encryption")
+	argDBPath  = flag.String("dbpath", "", "path to the server's DB directory")
+	argIDFile  = flag.String("idfile", "", "file name with node id (private key)")
+	argEnode   = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)")
+	argTopic   = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)")
+	argSaveDir = flag.String("savedir", "", "directory where incoming messages will be saved as files")
 )
 
 func main() {
@@ -124,7 +129,7 @@ func processArgs() {
 		if err != nil {
 			utils.Fatalf("Failed to parse the topic: %s", err)
 		}
-		topic = whisper.BytesToTopic(x)
+		topic = x
 	}
 
 	if *asymmetricMode && len(*argPub) > 0 {
@@ -134,6 +139,14 @@ func processArgs() {
 		}
 	}
 
+	if len(*argSaveDir) > 0 {
+		if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) {
+			utils.Fatalf("Download directory '%s' does not exist", *argSaveDir)
+		}
+	} else if *fileExMode {
+		utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode")
+	}
+
 	if *echoMode {
 		echo()
 	}
@@ -199,9 +212,40 @@ func initialize() {
 		shh = whisper.New()
 	}
 
-	asymKey = shh.NewIdentity()
+	if *argPoW != whisper.DefaultMinimumPoW {
+		err := shh.SetMinimumPoW(*argPoW)
+		if err != nil {
+			utils.Fatalf("Failed to set PoW: %s", err)
+		}
+	}
+
+	if *argMaxSize != whisper.DefaultMaxMessageLength {
+		err := shh.SetMaxMessageLength(*argMaxSize)
+		if err != nil {
+			utils.Fatalf("Failed to set max message size: %s", err)
+		}
+	}
+
+	asymKeyID, err = shh.NewKeyPair()
+	if err != nil {
+		utils.Fatalf("Failed to generate a new key pair: %s", err)
+	}
+
+	asymKey, err = shh.GetPrivateKey(asymKeyID)
+	if err != nil {
+		utils.Fatalf("Failed to retrieve a new key pair: %s", err)
+	}
+
 	if nodeid == nil {
-		nodeid = shh.NewIdentity()
+		tmpID, err := shh.NewKeyPair()
+		if err != nil {
+			utils.Fatalf("Failed to generate a new key pair: %s", err)
+		}
+
+		nodeid, err = shh.GetPrivateKey(tmpID)
+		if err != nil {
+			utils.Fatalf("Failed to retrieve a new key pair: %s", err)
+		}
 	}
 
 	maxPeers := 80
@@ -213,7 +257,8 @@ func initialize() {
 		Config: p2p.Config{
 			PrivateKey:     nodeid,
 			MaxPeers:       maxPeers,
-			Name:           common.MakeName("whisper-go", "5.0"),
+			Discovery:      true,
+			Name:           common.MakeName("wnode", "5.0"),
 			Protocols:      shh.Protocols(),
 			ListenAddr:     *argIP,
 			NAT:            nat.Any(),
@@ -288,8 +333,14 @@ func configureNode() {
 			}
 		}
 
-		shh.AddSymKey(symKeyName, []byte(symPass))
-		symKey = shh.GetSymKey(symKeyName)
+		symKeyID, err := shh.AddSymKeyFromPassword(symPass)
+		if err != nil {
+			utils.Fatalf("Failed to create symmetric key: %s", err)
+		}
+		symKey, err = shh.GetSymKey(symKeyID)
+		if err != nil {
+			utils.Fatalf("Failed to save symmetric key: %s", err)
+		}
 		if len(*argTopic) == 0 {
 			generateTopic([]byte(symPass))
 		}
@@ -302,12 +353,12 @@ func configureNode() {
 	}
 
 	filter := whisper.Filter{
-		KeySym:    symKey,
-		KeyAsym:   asymKey,
-		Topics:    []whisper.TopicType{topic},
-		AcceptP2P: p2pAccept,
+		KeySym:   symKey,
+		KeyAsym:  asymKey,
+		Topics:   [][]byte{topic},
+		AllowP2P: p2pAccept,
 	}
-	filterID, err = shh.Watch(&filter)
+	filterID, err = shh.Subscribe(&filter)
 	if err != nil {
 		utils.Fatalf("Failed to install filter: %s", err)
 	}
@@ -351,6 +402,8 @@ func run() {
 
 	if *requestMail {
 		requestExpiredMessagesLoop()
+	} else if *fileExMode {
+		sendFilesLoop()
 	} else {
 		sendLoop()
 	}
@@ -376,6 +429,31 @@ func sendLoop() {
 	}
 }
 
+func sendFilesLoop() {
+	for {
+		s := scanLine("")
+		if s == quitCommand {
+			fmt.Println("Quit command received")
+			close(done)
+			break
+		}
+		b, err := ioutil.ReadFile(s)
+		if err != nil {
+			fmt.Printf(">>> Error: %s \n", err)
+			continue
+		} else {
+			h := sendMsg(b)
+			if (h == common.Hash{}) {
+				fmt.Printf(">>> Error: message was not sent \n")
+			} else {
+				timestamp := time.Now().Unix()
+				from := crypto.PubkeyToAddress(asymKey.PublicKey)
+				fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h)
+			}
+		}
+	}
+}
+
 func scanLine(prompt string) string {
 	if len(prompt) > 0 {
 		fmt.Print(prompt)
@@ -402,29 +480,36 @@ func scanUint(prompt string) uint32 {
 	return uint32(i)
 }
 
-func sendMsg(payload []byte) {
+func sendMsg(payload []byte) common.Hash {
 	params := whisper.MessageParams{
 		Src:      asymKey,
 		Dst:      pub,
 		KeySym:   symKey,
 		Payload:  payload,
-		Topic:    topic,
+		Topic:    whisper.BytesToTopic(topic),
 		TTL:      uint32(*argTTL),
 		PoW:      *argPoW,
 		WorkTime: uint32(*argWorkTime),
 	}
 
 	msg := whisper.NewSentMessage(&params)
+	if msg == nil {
+		fmt.Printf("failed to create new message (OS level error)")
+		os.Exit(0)
+	}
 	envelope, err := msg.Wrap(&params)
 	if err != nil {
 		fmt.Printf("failed to seal message: %v \n", err)
-		return
+		return common.Hash{}
 	}
 
 	err = shh.Send(envelope)
 	if err != nil {
 		fmt.Printf("failed to send message: %v \n", err)
+		return common.Hash{}
 	}
+
+	return envelope.Hash()
 }
 
 func messageLoop() {
@@ -440,7 +525,11 @@ func messageLoop() {
 		case <-ticker.C:
 			messages := f.Retrieve()
 			for _, msg := range messages {
-				printMessageInfo(msg)
+				if *fileExMode || len(msg.Payload) > 2048 {
+					writeMessageToFile(*argSaveDir, msg)
+				} else {
+					printMessageInfo(msg)
+				}
 			}
 		case <-done:
 			return
@@ -464,19 +553,47 @@ func printMessageInfo(msg *whisper.ReceivedMessage) {
 	}
 }
 
+func writeMessageToFile(dir string, msg *whisper.ReceivedMessage) {
+	timestamp := fmt.Sprintf("%d", msg.Sent)
+	name := fmt.Sprintf("%x", msg.EnvelopeHash)
+
+	var address common.Address
+	if msg.Src != nil {
+		address = crypto.PubkeyToAddress(*msg.Src)
+	}
+
+	if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) {
+		// message from myself: don't save, only report
+		fmt.Printf("\n%s <%x>: message received: '%s'\n", timestamp, address, name)
+	} else if len(dir) > 0 {
+		fullpath := filepath.Join(dir, name)
+		err := ioutil.WriteFile(fullpath, msg.Payload, 0644)
+		if err != nil {
+			fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err)
+		} else {
+			fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(msg.Payload))
+		}
+	} else {
+		fmt.Printf("\n%s {%x}: big message received (%d bytes), but not saved: %s\n", timestamp, address, len(msg.Payload), name)
+	}
+}
+
 func requestExpiredMessagesLoop() {
 	var key, peerID []byte
 	var timeLow, timeUpp uint32
 	var t string
 	var xt, empty whisper.TopicType
 
-	err := shh.AddSymKey(mailserver.MailServerKeyName, []byte(msPassword))
+	keyID, err := shh.AddSymKeyFromPassword(msPassword)
 	if err != nil {
 		utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
 	}
-	key = shh.GetSymKey(mailserver.MailServerKeyName)
+	key, err = shh.GetSymKey(keyID)
+	if err != nil {
+		utils.Fatalf("Failed to save symmetric key for mail request: %s", err)
+	}
 	peerID = extractIdFromEnode(*argEnode)
-	shh.MarkPeerTrusted(peerID)
+	shh.AllowP2PMessagesFromPeer(peerID)
 
 	for {
 		timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
@@ -509,6 +626,9 @@ func requestExpiredMessagesLoop() {
 		params.WorkTime = 5
 
 		msg := whisper.NewSentMessage(&params)
+		if msg == nil {
+			utils.Fatalf("failed to create new message (OS level error)")
+		}
 		env, err := msg.Wrap(&params)
 		if err != nil {
 			utils.Fatalf("Wrap failed: %s", err)
@@ -527,7 +647,6 @@ func extractIdFromEnode(s string) []byte {
 	n, err := discover.ParseNode(s)
 	if err != nil {
 		utils.Fatalf("Failed to parse enode: %s", err)
-		return nil
 	}
 	return n.ID[:]
 }
diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go
index 6533c56c2f3efabe327cbd3d374870604de354fe..d705c622f4ceeda20d8dea248606631538a0dc0f 100644
--- a/whisper/mailserver/mailserver.go
+++ b/whisper/mailserver/mailserver.go
@@ -31,8 +31,6 @@ import (
 	"github.com/syndtr/goleveldb/leveldb/util"
 )
 
-const MailServerKeyName = "958e04ab302fb36ad2616a352cbac79d"
-
 type WMailServer struct {
 	db  *leveldb.DB
 	w   *whisper.Whisper
@@ -75,11 +73,14 @@ func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, p
 	s.w = shh
 	s.pow = pow
 
-	err = s.w.AddSymKey(MailServerKeyName, []byte(password))
+	MailServerKeyID, err := s.w.AddSymKeyFromPassword(password)
 	if err != nil {
 		utils.Fatalf("Failed to create symmetric key for MailServer: %s", err)
 	}
-	s.key = s.w.GetSymKey(MailServerKeyName)
+	s.key, err = s.w.GetSymKey(MailServerKeyID)
+	if err != nil {
+		utils.Fatalf("Failed to save symmetric key for MailServer")
+	}
 }
 
 func (s *WMailServer) Close() {
diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go
index 8b58a826fd2c93f99aa9559366ba64afb3e86e4d..ffdff3191560dd2d2ef79199909de0946d622030 100644
--- a/whisper/mailserver/server_test.go
+++ b/whisper/mailserver/server_test.go
@@ -30,8 +30,8 @@ import (
 )
 
 const powRequirement = 0.00001
-const keyName = "6d604bac5401ce9a6b995f1b45a4ab"
 
+var keyID string
 var shh *whisper.Whisper
 var seed = time.Now().Unix()
 
@@ -90,7 +90,7 @@ func TestMailServer(t *testing.T) {
 	server.Init(shh, dir, password, powRequirement)
 	defer server.Close()
 
-	err = shh.AddSymKey(keyName, []byte(password))
+	keyID, err = shh.AddSymKeyFromPassword(password)
 	if err != nil {
 		t.Fatalf("Failed to create symmetric key for mail request: %s", err)
 	}
@@ -102,7 +102,14 @@ func TestMailServer(t *testing.T) {
 }
 
 func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) {
-	testPeerID := shh.NewIdentity()
+	id, err := shh.NewKeyPair()
+	if err != nil {
+		t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
+	}
+	testPeerID, err := shh.GetPrivateKey(id)
+	if err != nil {
+		t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
+	}
 	birth := env.Expiry - env.TTL
 	p := &ServerTestParams{
 		topic: env.Topic,
@@ -167,8 +174,13 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
 	binary.BigEndian.PutUint32(data[4:], p.upp)
 	copy(data[8:], p.topic[:])
 
+	key, err := shh.GetSymKey(keyID)
+	if err != nil {
+		t.Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
+	}
+
 	params := &whisper.MessageParams{
-		KeySym:   shh.GetSymKey(keyName),
+		KeySym:   key,
 		Topic:    p.topic,
 		Payload:  data,
 		PoW:      powRequirement * 2,
diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go
index 9b43f7b70639de59fbff234d52d750a02b6c740c..579efba9e315f620bc5103c713cf2ef127e199a3 100644
--- a/whisper/whisperv5/api.go
+++ b/whisper/whisperv5/api.go
@@ -20,15 +20,14 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	mathrand "math/rand"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/discover"
 )
 
-var whisperOffLineErr = errors.New("whisper is offline")
+var whisperOfflineErr = errors.New("whisper is offline")
 
 // PublicWhisperAPI provides the whisper RPC service.
 type PublicWhisperAPI struct {
@@ -43,7 +42,7 @@ func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
 // Start starts the Whisper worker threads.
 func (api *PublicWhisperAPI) Start() error {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return whisperOfflineErr
 	}
 	return api.whisper.Start(nil)
 }
@@ -51,7 +50,7 @@ func (api *PublicWhisperAPI) Start() error {
 // Stop stops the Whisper worker threads.
 func (api *PublicWhisperAPI) Stop() error {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return whisperOfflineErr
 	}
 	return api.whisper.Stop()
 }
@@ -59,179 +58,219 @@ func (api *PublicWhisperAPI) Stop() error {
 // Version returns the Whisper version this node offers.
 func (api *PublicWhisperAPI) Version() (hexutil.Uint, error) {
 	if api.whisper == nil {
-		return 0, whisperOffLineErr
+		return 0, whisperOfflineErr
 	}
 	return hexutil.Uint(api.whisper.Version()), nil
 }
 
-// Stats returns the Whisper statistics for diagnostics.
-func (api *PublicWhisperAPI) Stats() (string, error) {
+// Info returns the Whisper statistics for diagnostics.
+func (api *PublicWhisperAPI) Info() (string, error) {
 	if api.whisper == nil {
-		return "", whisperOffLineErr
+		return "", whisperOfflineErr
 	}
 	return api.whisper.Stats(), nil
 }
 
-// MarkPeerTrusted marks specific peer trusted, which will allow it
+// SetMaxMessageLength sets the maximal message length allowed by this node
+func (api *PublicWhisperAPI) SetMaxMessageLength(val int) error {
+	if api.whisper == nil {
+		return whisperOfflineErr
+	}
+	return api.whisper.SetMaxMessageLength(val)
+}
+
+// SetMinimumPoW sets the minimal PoW required by this node
+func (api *PublicWhisperAPI) SetMinimumPoW(val float64) error {
+	if api.whisper == nil {
+		return whisperOfflineErr
+	}
+	return api.whisper.SetMinimumPoW(val)
+}
+
+// AllowP2PMessagesFromPeer marks specific peer trusted, which will allow it
 // to send historic (expired) messages.
-func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error {
+func (api *PublicWhisperAPI) AllowP2PMessagesFromPeer(enode string) error {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return whisperOfflineErr
 	}
-	return api.whisper.MarkPeerTrusted(peerID)
+	n, err := discover.ParseNode(enode)
+	if err != nil {
+		return errors.New("failed to parse enode of trusted peer: " + err.Error())
+	}
+	return api.whisper.AllowP2PMessagesFromPeer(n.ID[:])
 }
 
-// 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 hexutil.Bytes, data hexutil.Bytes) 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
+// HasKeyPair checks if the whisper node is configured with the private key
 // of the specified public pair.
-func (api *PublicWhisperAPI) HasIdentity(identity string) (bool, error) {
+func (api *PublicWhisperAPI) HasKeyPair(id string) (bool, error) {
 	if api.whisper == nil {
-		return false, whisperOffLineErr
+		return false, whisperOfflineErr
 	}
-	return api.whisper.HasIdentity(identity), nil
+	return api.whisper.HasKeyPair(id), nil
 }
 
-// DeleteIdentity deletes the specifies key if it exists.
-func (api *PublicWhisperAPI) DeleteIdentity(identity string) error {
+// DeleteKeyPair deletes the specifies key if it exists.
+func (api *PublicWhisperAPI) DeleteKeyPair(id string) (bool, error) {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return false, whisperOfflineErr
 	}
-	api.whisper.DeleteIdentity(identity)
-	return nil
+	return api.whisper.DeleteKeyPair(id), nil
 }
 
-// NewIdentity generates a new cryptographic identity for the client, and injects
+// NewKeyPair generates a new cryptographic identity for the client, and injects
 // it into the known identities for message decryption.
-func (api *PublicWhisperAPI) NewIdentity() (string, error) {
+func (api *PublicWhisperAPI) NewKeyPair() (string, error) {
 	if api.whisper == nil {
-		return "", whisperOffLineErr
+		return "", whisperOfflineErr
 	}
-	identity := api.whisper.NewIdentity()
-	return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil
+	return api.whisper.NewKeyPair()
 }
 
-// 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 {
+// GetPublicKey returns the public key for identity id
+func (api *PublicWhisperAPI) GetPublicKey(id string) (hexutil.Bytes, error) {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return nil, whisperOfflineErr
+	}
+	key, err := api.whisper.GetPrivateKey(id)
+	if err != nil {
+		return nil, err
 	}
-	return api.whisper.GenerateSymKey(name)
+	return crypto.FromECDSAPub(&key.PublicKey), nil
 }
 
-// AddSymKey stores the key under the 'name' id.
-func (api *PublicWhisperAPI) AddSymKey(name string, key hexutil.Bytes) error {
+// GetPrivateKey returns the private key for identity id
+func (api *PublicWhisperAPI) GetPrivateKey(id string) (string, error) {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return "", whisperOfflineErr
 	}
-	return api.whisper.AddSymKey(name, key)
+	key, err := api.whisper.GetPrivateKey(id)
+	if err != nil {
+		return "", err
+	}
+	return common.ToHex(crypto.FromECDSA(key)), nil
 }
 
-// HasSymKey returns true if there is a key associated with the name string.
-// Otherwise returns false.
-func (api *PublicWhisperAPI) HasSymKey(name string) (bool, error) {
+// GenerateSymmetricKey generates a random symmetric key and stores it under id,
+// which is then returned. Will be used in the future for session key exchange.
+func (api *PublicWhisperAPI) GenerateSymmetricKey() (string, error) {
 	if api.whisper == nil {
-		return false, whisperOffLineErr
+		return "", whisperOfflineErr
 	}
-	res := api.whisper.HasSymKey(name)
-	return res, nil
+	return api.whisper.GenerateSymKey()
 }
 
-// DeleteSymKey deletes the key associated with the name string if it exists.
-func (api *PublicWhisperAPI) DeleteSymKey(name string) error {
+// AddSymmetricKeyDirect stores the key, and returns its id.
+func (api *PublicWhisperAPI) AddSymmetricKeyDirect(key hexutil.Bytes) (string, error) {
 	if api.whisper == nil {
-		return whisperOffLineErr
+		return "", whisperOfflineErr
 	}
-	api.whisper.DeleteSymKey(name)
-	return nil
+	return api.whisper.AddSymKeyDirect(key)
 }
 
-// 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) (string, error) {
+// AddSymmetricKeyFromPassword generates the key from password, stores it, and returns its id.
+func (api *PublicWhisperAPI) AddSymmetricKeyFromPassword(password string) (string, error) {
 	if api.whisper == nil {
-		return "", whisperOffLineErr
+		return "", whisperOfflineErr
 	}
+	return api.whisper.AddSymKeyFromPassword(password)
+}
 
-	filter := Filter{
-		Src:       crypto.ToECDSAPub(common.FromHex(args.From)),
-		KeySym:    api.whisper.GetSymKey(args.KeyName),
-		PoW:       args.PoW,
-		Messages:  make(map[common.Hash]*ReceivedMessage),
-		AcceptP2P: args.AcceptP2P,
-	}
-	if len(filter.KeySym) > 0 {
-		filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
+// HasSymmetricKey returns true if there is a key associated with the given id.
+// Otherwise returns false.
+func (api *PublicWhisperAPI) HasSymmetricKey(id string) (bool, error) {
+	if api.whisper == nil {
+		return false, whisperOfflineErr
 	}
-	filter.Topics = append(filter.Topics, args.Topics...)
+	res := api.whisper.HasSymKey(id)
+	return res, nil
+}
 
-	if len(args.Topics) == 0 && len(args.KeyName) != 0 {
-		info := "NewFilter: at least one topic must be specified"
-		log.Error(fmt.Sprintf(info))
-		return "", errors.New(info)
+// GetSymmetricKey returns the symmetric key associated with the given id.
+func (api *PublicWhisperAPI) GetSymmetricKey(name string) (hexutil.Bytes, error) {
+	if api.whisper == nil {
+		return nil, whisperOfflineErr
+	}
+	b, err := api.whisper.GetSymKey(name)
+	if err != nil {
+		return nil, err
 	}
+	return b, nil
+}
 
-	if len(args.KeyName) != 0 && len(filter.KeySym) == 0 {
-		info := "NewFilter: key was not found by name: " + args.KeyName
-		log.Error(fmt.Sprintf(info))
-		return "", errors.New(info)
+// DeleteSymmetricKey deletes the key associated with the name string if it exists.
+func (api *PublicWhisperAPI) DeleteSymmetricKey(name string) (bool, error) {
+	if api.whisper == nil {
+		return false, whisperOfflineErr
 	}
+	res := api.whisper.DeleteSymKey(name)
+	return res, nil
+}
 
-	if len(args.To) == 0 && len(filter.KeySym) == 0 {
-		info := "NewFilter: filter must contain either symmetric or asymmetric key"
-		log.Error(fmt.Sprintf(info))
-		return "", errors.New(info)
+// Subscribe creates and registers a new filter to watch for inbound whisper messages.
+// Returns the ID of the newly created filter.
+func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) {
+	if api.whisper == nil {
+		return "", whisperOfflineErr
 	}
 
-	if len(args.To) != 0 && len(filter.KeySym) != 0 {
-		info := "NewFilter: filter must not contain both symmetric and asymmetric key"
-		log.Error(fmt.Sprintf(info))
-		return "", errors.New(info)
+	filter := Filter{
+		Src:      crypto.ToECDSAPub(common.FromHex(args.SignedWith)),
+		PoW:      args.MinPoW,
+		Messages: make(map[common.Hash]*ReceivedMessage),
+		AllowP2P: args.AllowP2P,
 	}
 
-	if len(args.To) > 0 {
-		dst := crypto.ToECDSAPub(common.FromHex(args.To))
-		if !ValidatePublicKey(dst) {
-			info := "NewFilter: Invalid 'To' address"
-			log.Error(fmt.Sprintf(info))
-			return "", errors.New(info)
-		}
-		filter.KeyAsym = api.whisper.GetIdentity(string(args.To))
-		if filter.KeyAsym == nil {
-			info := "NewFilter: non-existent identity provided"
-			log.Error(fmt.Sprintf(info))
-			return "", errors.New(info)
+	var err error
+	for i, bt := range args.Topics {
+		if len(bt) == 0 || len(bt) > 4 {
+			return "", errors.New(fmt.Sprintf("subscribe: topic %d has wrong size: %d", i, len(bt)))
 		}
+		filter.Topics = append(filter.Topics, bt)
 	}
 
-	if len(args.From) > 0 {
+	if err = ValidateKeyID(args.Key); err != nil {
+		return "", errors.New("subscribe: " + err.Error())
+	}
+
+	if len(args.SignedWith) > 0 {
 		if !ValidatePublicKey(filter.Src) {
-			info := "NewFilter: Invalid 'From' address"
-			log.Error(fmt.Sprintf(info))
-			return "", errors.New(info)
+			return "", errors.New("subscribe: invalid 'SignedWith' field")
+		}
+	}
+
+	if args.Symmetric {
+		if len(args.Topics) == 0 {
+			return "", errors.New("subscribe: at least one topic must be specified with symmetric encryption")
+		}
+		symKey, err := api.whisper.GetSymKey(args.Key)
+		if err != nil {
+			return "", errors.New("subscribe: invalid key ID")
+		}
+		if !validateSymmetricKey(symKey) {
+			return "", errors.New("subscribe: retrieved key is invalid")
+		}
+		filter.KeySym = symKey
+		filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
+	} else {
+		filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key)
+		if err != nil {
+			return "", errors.New("subscribe: invalid key ID")
+		}
+		if filter.KeyAsym == nil {
+			return "", errors.New("subscribe: non-existent identity provided")
 		}
 	}
 
-	return api.whisper.Watch(&filter)
+	return api.whisper.Subscribe(&filter)
 }
 
-// UninstallFilter disables and removes an existing filter.
-func (api *PublicWhisperAPI) UninstallFilter(filterId string) {
-	api.whisper.Unwatch(filterId)
+// Unsubscribe disables and removes an existing filter.
+func (api *PublicWhisperAPI) Unsubscribe(id string) {
+	api.whisper.Unsubscribe(id)
 }
 
-// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval.
-func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage {
+// GetSubscriptionMessages retrieves all the new messages matched by a filter since the last retrieval.
+func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*WhisperMessage {
 	f := api.whisper.GetFilter(filterId)
 	if f != nil {
 		newMail := f.Retrieve()
@@ -240,7 +279,8 @@ func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage
 	return toWhisperMessages(nil)
 }
 
-// GetMessages retrieves all the known messages that match a specific filter.
+// GetMessages retrieves all the floating messages that match a specific filter.
+// It is likely to be called once per session, right after Subscribe call.
 func (api *PublicWhisperAPI) GetMessages(filterId string) []*WhisperMessage {
 	all := api.whisper.Messages(filterId)
 	return toWhisperMessages(all)
@@ -258,139 +298,107 @@ func toWhisperMessages(messages []*ReceivedMessage) []*WhisperMessage {
 // 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
+		return whisperOfflineErr
 	}
 
+	var err error
 	params := MessageParams{
 		TTL:      args.TTL,
-		Dst:      crypto.ToECDSAPub(common.FromHex(args.To)),
-		KeySym:   api.whisper.GetSymKey(args.KeyName),
-		Topic:    args.Topic,
+		WorkTime: args.PowTime,
+		PoW:      args.PowTarget,
 		Payload:  args.Payload,
 		Padding:  args.Padding,
-		WorkTime: args.WorkTime,
-		PoW:      args.PoW,
 	}
 
-	if len(args.From) > 0 {
-		pub := crypto.ToECDSAPub(common.FromHex(args.From))
-		if !ValidatePublicKey(pub) {
-			info := "Post: Invalid 'From' address"
-			log.Error(fmt.Sprintf(info))
-			return errors.New(info)
+	if len(args.Key) == 0 {
+		return errors.New("post: key is missing")
+	}
+
+	if len(args.SignWith) > 0 {
+		params.Src, err = api.whisper.GetPrivateKey(args.SignWith)
+		if err != nil {
+			return err
 		}
-		params.Src = api.whisper.GetIdentity(string(args.From))
 		if params.Src == nil {
-			info := "Post: non-existent identity provided"
-			log.Error(fmt.Sprintf(info))
-			return errors.New(info)
+			return errors.New("post: empty identity")
 		}
 	}
 
-	filter := api.whisper.GetFilter(args.FilterID)
-	if filter == nil && len(args.FilterID) > 0 {
-		info := fmt.Sprintf("Post: wrong filter id %s", args.FilterID)
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
+	if len(args.Topic) == TopicLength {
+		params.Topic = BytesToTopic(args.Topic)
+	} else if len(args.Topic) != 0 {
+		return errors.New(fmt.Sprintf("post: wrong topic size %d", len(args.Topic)))
 	}
 
-	if filter != nil {
-		// get the missing fields from the filter
-		if params.KeySym == nil && filter.KeySym != nil {
-			params.KeySym = filter.KeySym
+	if args.Type == "sym" {
+		if err = ValidateKeyID(args.Key); err != nil {
+			return err
 		}
-		if params.Src == nil && filter.Src != nil {
-			params.Src = filter.KeyAsym
+		params.KeySym, err = api.whisper.GetSymKey(args.Key)
+		if err != nil {
+			return err
 		}
-		if (params.Topic == TopicType{}) {
-			sz := len(filter.Topics)
-			if sz < 1 {
-				info := fmt.Sprintf("Post: no topics in filter # %s", args.FilterID)
-				log.Error(fmt.Sprintf(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]
-			}
+		if !validateSymmetricKey(params.KeySym) {
+			return errors.New("post: key for symmetric encryption is invalid")
 		}
-	}
-
-	// validate
-	if len(args.KeyName) != 0 && len(params.KeySym) == 0 {
-		info := "Post: key was not found by name: " + args.KeyName
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
-	}
-
-	if len(args.To) == 0 && len(params.KeySym) == 0 {
-		info := "Post: message must be encrypted either symmetrically or asymmetrically"
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
-	}
-
-	if len(args.To) != 0 && len(params.KeySym) != 0 {
-		info := "Post: ambigous encryption method requested"
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
-	}
-
-	if len(args.To) > 0 {
+		if len(params.Topic) == 0 {
+			return errors.New("post: topic is missing for symmetric encryption")
+		}
+	} else if args.Type == "asym" {
+		params.Dst = crypto.ToECDSAPub(common.FromHex(args.Key))
 		if !ValidatePublicKey(params.Dst) {
-			info := "Post: Invalid 'To' address"
-			log.Error(fmt.Sprintf(info))
-			return errors.New(info)
+			return errors.New("post: public key for asymmetric encryption is invalid")
 		}
+	} else {
+		return errors.New("post: wrong type (sym/asym)")
 	}
 
 	// encrypt and send
 	message := NewSentMessage(&params)
+	if message == nil {
+		return errors.New("post: failed create new message, probably due to failed rand function (OS level)")
+	}
 	envelope, err := message.Wrap(&params)
 	if err != nil {
-		log.Error(fmt.Sprintf(err.Error()))
 		return err
 	}
-	if len(envelope.Data) > MaxMessageLength {
-		info := "Post: message is too big"
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
-	}
-	if (envelope.Topic == TopicType{} && envelope.IsSymmetric()) {
-		info := "Post: topic is missing for symmetric encryption"
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
+	if envelope.size() > api.whisper.maxMsgLength {
+		return errors.New("post: message is too big")
 	}
 
-	if args.PeerID != nil {
-		return api.whisper.SendP2PMessage(args.PeerID, envelope)
+	if len(args.TargetPeer) != 0 {
+		n, err := discover.ParseNode(args.TargetPeer)
+		if err != nil {
+			return errors.New("post: failed to parse enode of target peer: " + err.Error())
+		}
+		return api.whisper.SendP2PMessage(n.ID[:], envelope)
+	} else if args.PowTarget < api.whisper.minPoW {
+		return errors.New("post: target PoW is less than minimum PoW, the message can not be sent")
 	}
 
 	return api.whisper.Send(envelope)
 }
 
 type PostArgs struct {
-	TTL      uint32        `json:"ttl"`
-	From     string        `json:"from"`
-	To       string        `json:"to"`
-	KeyName  string        `json:"keyname"`
-	Topic    TopicType     `json:"topic"`
-	Padding  hexutil.Bytes `json:"padding"`
-	Payload  hexutil.Bytes `json:"payload"`
-	WorkTime uint32        `json:"worktime"`
-	PoW      float64       `json:"pow"`
-	FilterID string        `json:"filterID"`
-	PeerID   hexutil.Bytes `json:"peerID"`
+	Type       string        `json:"type"`       // "sym"/"asym" (symmetric or asymmetric)
+	TTL        uint32        `json:"ttl"`        // time-to-live in seconds
+	SignWith   string        `json:"signWith"`   // id of the signing key
+	Key        string        `json:"key"`        // id of encryption key
+	Topic      hexutil.Bytes `json:"topic"`      // topic (4 bytes)
+	Padding    hexutil.Bytes `json:"padding"`    // optional padding bytes
+	Payload    hexutil.Bytes `json:"payload"`    // payload to be encrypted
+	PowTime    uint32        `json:"powTime"`    // maximal time in seconds to be spent on PoW
+	PowTarget  float64       `json:"powTarget"`  // minimal PoW required for this message
+	TargetPeer string        `json:"targetPeer"` // peer id (for p2p message only)
 }
 
 type WhisperFilterArgs struct {
-	To        string      `json:"to"`
-	From      string      `json:"from"`
-	KeyName   string      `json:"keyname"`
-	PoW       float64     `json:"pow"`
-	Topics    []TopicType `json:"topics"`
-	AcceptP2P bool        `json:"p2p"`
+	Symmetric  bool     // encryption type
+	Key        string   // id of the key to be used for decryption
+	SignedWith string   // public key of the sender to be verified
+	MinPoW     float64  // minimal PoW requirement
+	Topics     [][]byte // list of topics (up to 4 bytes each) to match
+	AllowP2P   bool     // indicates wheather direct p2p messages are allowed for this filter
 }
 
 // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
@@ -398,22 +406,30 @@ type WhisperFilterArgs struct {
 func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
 	// Unmarshal the JSON message and sanity check
 	var obj struct {
-		To        string        `json:"to"`
-		From      string        `json:"from"`
-		KeyName   string        `json:"keyname"`
-		PoW       float64       `json:"pow"`
-		Topics    []interface{} `json:"topics"`
-		AcceptP2P bool          `json:"p2p"`
+		Type       string        `json:"type"`
+		Key        string        `json:"key"`
+		SignedWith string        `json:"signedWith"`
+		MinPoW     float64       `json:"minPoW"`
+		Topics     []interface{} `json:"topics"`
+		AllowP2P   bool          `json:"allowP2P"`
 	}
 	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
+	switch obj.Type {
+	case "sym":
+		args.Symmetric = true
+	case "asym":
+		args.Symmetric = false
+	default:
+		return errors.New("wrong type (sym/asym)")
+	}
+
+	args.Key = obj.Key
+	args.SignedWith = obj.SignedWith
+	args.MinPoW = obj.MinPoW
+	args.AllowP2P = obj.AllowP2P
 
 	// Construct the topic array
 	if obj.Topics != nil {
@@ -428,13 +444,13 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
 				return fmt.Errorf("topic[%d] is not a string", i)
 			}
 		}
-		topicsDecoded := make([]TopicType, len(topics))
+		topicsDecoded := make([][]byte, len(topics))
 		for j, s := range topics {
 			x := common.FromHex(s)
-			if x == nil || len(x) != TopicLength {
+			if x == nil || len(x) > TopicLength {
 				return fmt.Errorf("topic[%d] is invalid", j)
 			}
-			topicsDecoded[j] = BytesToTopic(x)
+			topicsDecoded[j] = x
 		}
 		args.Topics = topicsDecoded
 	}
@@ -444,34 +460,34 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
 
 // WhisperMessage is the RPC representation of a whisper message.
 type WhisperMessage struct {
-	Topic   string  `json:"topic"`
-	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"`
+	Topic     string  `json:"topic"`
+	Payload   string  `json:"payload"`
+	Padding   string  `json:"padding"`
+	Src       string  `json:"signedWith"`
+	Dst       string  `json:"recipientPublicKey"`
+	Timestamp uint32  `json:"timestamp"`
+	TTL       uint32  `json:"ttl"`
+	PoW       float64 `json:"pow"`
+	Hash      string  `json:"hash"`
 }
 
 // NewWhisperMessage converts an internal message into an API version.
 func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage {
 	msg := WhisperMessage{
-		Topic:   common.ToHex(message.Topic[:]),
-		Payload: common.ToHex(message.Payload),
-		Padding: common.ToHex(message.Padding),
-		Sent:    message.Sent,
-		TTL:     message.TTL,
-		PoW:     message.PoW,
-		Hash:    common.ToHex(message.EnvelopeHash.Bytes()),
+		Topic:     common.ToHex(message.Topic[:]),
+		Payload:   common.ToHex(message.Payload),
+		Padding:   common.ToHex(message.Padding),
+		Timestamp: message.Sent,
+		TTL:       message.TTL,
+		PoW:       message.PoW,
+		Hash:      common.ToHex(message.EnvelopeHash.Bytes()),
 	}
 
 	if message.Dst != nil {
-		msg.To = common.ToHex(crypto.FromECDSAPub(message.Dst))
+		msg.Dst = common.ToHex(crypto.FromECDSAPub(message.Dst))
 	}
 	if isMessageSigned(message.Raw[0]) {
-		msg.From = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey()))
+		msg.Src = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey()))
 	}
 	return &msg
 }
diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go
index ea0a2c40bcb870e740f1f1ed36fec50fbb742af4..9207c6f109d61f45c76d923c222898c70f256475 100644
--- a/whisper/whisperv5/api_test.go
+++ b/whisper/whisperv5/api_test.go
@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
 )
 
 func TestBasic(t *testing.T) {
@@ -42,12 +43,12 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("wrong version: %d.", ver)
 	}
 
-	mail := api.GetFilterChanges("non-existent-id")
+	mail := api.GetSubscriptionMessages("non-existent-id")
 	if len(mail) != 0 {
 		t.Fatalf("failed GetFilterChanges: premature result")
 	}
 
-	exist, err := api.HasIdentity(id)
+	exist, err := api.HasKeyPair(id)
 	if err != nil {
 		t.Fatalf("failed initial HasIdentity: %s.", err)
 	}
@@ -55,12 +56,15 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("failed initial HasIdentity: false positive.")
 	}
 
-	err = api.DeleteIdentity(id)
+	success, err := api.DeleteKeyPair(id)
 	if err != nil {
 		t.Fatalf("failed DeleteIdentity: %s.", err)
 	}
+	if success {
+		t.Fatalf("deleted non-existing identity: false positive.")
+	}
 
-	pub, err := api.NewIdentity()
+	pub, err := api.NewKeyPair()
 	if err != nil {
 		t.Fatalf("failed NewIdentity: %s.", err)
 	}
@@ -68,7 +72,7 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("failed NewIdentity: empty")
 	}
 
-	exist, err = api.HasIdentity(pub)
+	exist, err = api.HasKeyPair(pub)
 	if err != nil {
 		t.Fatalf("failed HasIdentity: %s.", err)
 	}
@@ -76,12 +80,15 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("failed HasIdentity: false negative.")
 	}
 
-	err = api.DeleteIdentity(pub)
+	success, err = api.DeleteKeyPair(pub)
 	if err != nil {
 		t.Fatalf("failed to delete second identity: %s.", err)
 	}
+	if !success {
+		t.Fatalf("failed to delete second identity.")
+	}
 
-	exist, err = api.HasIdentity(pub)
+	exist, err = api.HasKeyPair(pub)
 	if err != nil {
 		t.Fatalf("failed HasIdentity(): %s.", err)
 	}
@@ -92,7 +99,7 @@ func TestBasic(t *testing.T) {
 	id = "arbitrary text"
 	id2 := "another arbitrary string"
 
-	exist, err = api.HasSymKey(id)
+	exist, err = api.HasSymmetricKey(id)
 	if err != nil {
 		t.Fatalf("failed HasSymKey: %s.", err)
 	}
@@ -100,12 +107,12 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("failed HasSymKey: false positive.")
 	}
 
-	err = api.GenerateSymKey(id)
+	id, err = api.GenerateSymmetricKey()
 	if err != nil {
 		t.Fatalf("failed GenerateSymKey: %s.", err)
 	}
 
-	exist, err = api.HasSymKey(id)
+	exist, err = api.HasSymmetricKey(id)
 	if err != nil {
 		t.Fatalf("failed HasSymKey(): %s.", err)
 	}
@@ -113,17 +120,18 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("failed HasSymKey(): false negative.")
 	}
 
-	err = api.AddSymKey(id, []byte("some stuff here"))
-	if err == nil {
+	const password = "some stuff here"
+	id, err = api.AddSymmetricKeyFromPassword(password)
+	if err != nil {
 		t.Fatalf("failed AddSymKey: %s.", err)
 	}
 
-	err = api.AddSymKey(id2, []byte("some stuff here"))
+	id2, err = api.AddSymmetricKeyFromPassword(password)
 	if err != nil {
 		t.Fatalf("failed AddSymKey: %s.", err)
 	}
 
-	exist, err = api.HasSymKey(id2)
+	exist, err = api.HasSymmetricKey(id2)
 	if err != nil {
 		t.Fatalf("failed HasSymKey(id2): %s.", err)
 	}
@@ -131,12 +139,28 @@ func TestBasic(t *testing.T) {
 		t.Fatalf("failed HasSymKey(id2): false negative.")
 	}
 
-	err = api.DeleteSymKey(id)
+	k1, err := api.GetSymmetricKey(id)
+	if err != nil {
+		t.Fatalf("failed GetSymKey(id): %s.", err)
+	}
+	k2, err := api.GetSymmetricKey(id2)
+	if err != nil {
+		t.Fatalf("failed GetSymKey(id2): %s.", err)
+	}
+
+	if !bytes.Equal(k1, k2) {
+		t.Fatalf("installed keys are not equal")
+	}
+
+	exist, err = api.DeleteSymmetricKey(id)
 	if err != nil {
 		t.Fatalf("failed DeleteSymKey(id): %s.", err)
 	}
+	if !exist {
+		t.Fatalf("failed DeleteSymKey(id): false negative.")
+	}
 
-	exist, err = api.HasSymKey(id)
+	exist, err = api.HasSymmetricKey(id)
 	if err != nil {
 		t.Fatalf("failed HasSymKey(id): %s.", err)
 	}
@@ -147,12 +171,12 @@ func TestBasic(t *testing.T) {
 
 func TestUnmarshalFilterArgs(t *testing.T) {
 	s := []byte(`{
-	"to":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d",
-	"from":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
-	"keyname":"testname",
-	"pow":2.34,
+	"type":"sym",
+	"key":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d",
+	"signedWith":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
+	"minPoW":2.34,
 	"topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"],
-	"p2p":true
+	"allowP2P":true
 	}`)
 
 	var f WhisperFilterArgs
@@ -161,59 +185,58 @@ func TestUnmarshalFilterArgs(t *testing.T) {
 		t.Fatalf("failed UnmarshalJSON: %s.", err)
 	}
 
-	if f.To != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" {
-		t.Fatalf("wrong To: %x.", f.To)
+	if !f.Symmetric {
+		t.Fatalf("wrong type.")
 	}
-	if f.From != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" {
-		t.Fatalf("wrong From: %x.", f.To)
+	if f.Key != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" {
+		t.Fatalf("wrong key: %s.", f.Key)
 	}
-	if f.KeyName != "testname" {
-		t.Fatalf("wrong KeyName: %s.", f.KeyName)
+	if f.SignedWith != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" {
+		t.Fatalf("wrong SignedWith: %s.", f.SignedWith)
 	}
-	if f.PoW != 2.34 {
-		t.Fatalf("wrong pow: %f.", f.PoW)
+	if f.MinPoW != 2.34 {
+		t.Fatalf("wrong MinPoW: %f.", f.MinPoW)
 	}
-	if !f.AcceptP2P {
-		t.Fatalf("wrong AcceptP2P: %v.", f.AcceptP2P)
+	if !f.AllowP2P {
+		t.Fatalf("wrong AllowP2P.")
 	}
 	if len(f.Topics) != 4 {
 		t.Fatalf("wrong topics number: %d.", len(f.Topics))
 	}
 
 	i := 0
-	if f.Topics[i] != (TopicType{0x00, 0x00, 0x00, 0x00}) {
+	if !bytes.Equal(f.Topics[i], []byte{0x00, 0x00, 0x00, 0x00}) {
 		t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
 	}
 
 	i++
-	if f.Topics[i] != (TopicType{0x00, 0x7f, 0x80, 0xff}) {
+	if !bytes.Equal(f.Topics[i], []byte{0x00, 0x7f, 0x80, 0xff}) {
 		t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
 	}
 
 	i++
-	if f.Topics[i] != (TopicType{0xff, 0x80, 0x7f, 0x00}) {
+	if !bytes.Equal(f.Topics[i], []byte{0xff, 0x80, 0x7f, 0x00}) {
 		t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
 	}
 
 	i++
-	if f.Topics[i] != (TopicType{0xf2, 0x6e, 0x77, 0x79}) {
+	if !bytes.Equal(f.Topics[i], []byte{0xf2, 0x6e, 0x77, 0x79}) {
 		t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
 	}
 }
 
 func TestUnmarshalPostArgs(t *testing.T) {
 	s := []byte(`{
+	"type":"sym",
 	"ttl":12345,
-	"from":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d",
-	"to":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
-	"keyname":"shh_test",
+	"signWith":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d",
+	"key":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
 	"topic":"0xf26e7779",
 	"padding":"0x74686973206973206D79207465737420737472696E67",
 	"payload":"0x7061796C6F61642073686F756C642062652070736575646F72616E646F6D",
-	"worktime":777,
-	"pow":3.1416,
-	"filterid":"test-filter-id",
-	"peerid":"0xf26e7779"
+	"powTime":777,
+	"powTarget":3.1416,
+	"targetPeer":"enode://915533f667b1369793ebb9bda022416b1295235a1420799cd87a969467372546d808ebf59c5c9ce23f103d59b61b97df8af91f0908552485975397181b993461@127.0.0.1:12345"
 	}`)
 
 	var a PostArgs
@@ -222,19 +245,20 @@ func TestUnmarshalPostArgs(t *testing.T) {
 		t.Fatalf("failed UnmarshalJSON: %s.", err)
 	}
 
+	if a.Type != "sym" {
+		t.Fatalf("wrong Type: %s.", a.Type)
+	}
 	if a.TTL != 12345 {
 		t.Fatalf("wrong ttl: %d.", a.TTL)
 	}
-	if a.From != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" {
-		t.Fatalf("wrong From: %x.", a.To)
-	}
-	if a.To != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" {
-		t.Fatalf("wrong To: %x.", a.To)
+	if a.SignWith != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" {
+		t.Fatalf("wrong From: %s.", a.SignWith)
 	}
-	if a.KeyName != "shh_test" {
-		t.Fatalf("wrong KeyName: %s.", a.KeyName)
+	if a.Key != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" {
+		t.Fatalf("wrong Key: %s.", a.Key)
 	}
-	if a.Topic != (TopicType{0xf2, 0x6e, 0x77, 0x79}) {
+
+	if BytesToTopic(a.Topic) != (TopicType{0xf2, 0x6e, 0x77, 0x79}) {
 		t.Fatalf("wrong topic: %x.", a.Topic)
 	}
 	if string(a.Padding) != "this is my test string" {
@@ -243,31 +267,34 @@ func TestUnmarshalPostArgs(t *testing.T) {
 	if string(a.Payload) != "payload should be pseudorandom" {
 		t.Fatalf("wrong Payload: %s.", string(a.Payload))
 	}
-	if a.WorkTime != 777 {
-		t.Fatalf("wrong WorkTime: %d.", a.WorkTime)
-	}
-	if a.PoW != 3.1416 {
-		t.Fatalf("wrong pow: %f.", a.PoW)
+	if a.PowTime != 777 {
+		t.Fatalf("wrong PowTime: %d.", a.PowTime)
 	}
-	if a.FilterID != "test-filter-id" {
-		t.Fatalf("wrong FilterID: %s.", a.FilterID)
+	if a.PowTarget != 3.1416 {
+		t.Fatalf("wrong PowTarget: %f.", a.PowTarget)
 	}
-	if !bytes.Equal(a.PeerID[:], a.Topic[:]) {
-		t.Fatalf("wrong PeerID: %x.", a.PeerID)
+	if a.TargetPeer != "enode://915533f667b1369793ebb9bda022416b1295235a1420799cd87a969467372546d808ebf59c5c9ce23f103d59b61b97df8af91f0908552485975397181b993461@127.0.0.1:12345" {
+		t.Fatalf("wrong PeerID: %s.", a.TargetPeer)
 	}
 }
 
-func waitForMessage(api *PublicWhisperAPI, id string, target int) bool {
-	for i := 0; i < 64; i++ {
-		all := api.GetMessages(id)
-		if len(all) >= target {
-			return true
+func waitForMessages(api *PublicWhisperAPI, id string, target int) []*WhisperMessage {
+	// timeout: 2 seconds
+	result := make([]*WhisperMessage, 0, target)
+	for i := 0; i < 100; i++ {
+		mail := api.GetSubscriptionMessages(id)
+		if len(mail) > 0 {
+			for _, m := range mail {
+				result = append(result, m)
+			}
+			if len(result) >= target {
+				break
+			}
 		}
-		time.Sleep(time.Millisecond * 16)
+		time.Sleep(time.Millisecond * 20)
 	}
 
-	// timeout 1024 milliseconds
-	return false
+	return result
 }
 
 func TestIntegrationAsym(t *testing.T) {
@@ -280,7 +307,7 @@ func TestIntegrationAsym(t *testing.T) {
 	api.Start()
 	defer api.Stop()
 
-	sig, err := api.NewIdentity()
+	sig, err := api.NewKeyPair()
 	if err != nil {
 		t.Fatalf("failed NewIdentity: %s.", err)
 	}
@@ -288,7 +315,7 @@ func TestIntegrationAsym(t *testing.T) {
 		t.Fatalf("wrong signature")
 	}
 
-	exist, err := api.HasIdentity(sig)
+	exist, err := api.HasKeyPair(sig)
 	if err != nil {
 		t.Fatalf("failed HasIdentity: %s.", err)
 	}
@@ -296,7 +323,12 @@ func TestIntegrationAsym(t *testing.T) {
 		t.Fatalf("failed HasIdentity: false negative.")
 	}
 
-	key, err := api.NewIdentity()
+	sigPubKey, err := api.GetPublicKey(sig)
+	if err != nil {
+		t.Fatalf("failed GetPublicKey: %s.", err)
+	}
+
+	key, err := api.NewKeyPair()
 	if err != nil {
 		t.Fatalf("failed NewIdentity(): %s.", err)
 	}
@@ -304,42 +336,46 @@ func TestIntegrationAsym(t *testing.T) {
 		t.Fatalf("wrong key")
 	}
 
+	dstPubKey, err := api.GetPublicKey(key)
+	if err != nil {
+		t.Fatalf("failed GetPublicKey: %s.", err)
+	}
+
 	var topics [2]TopicType
 	topics[0] = TopicType{0x00, 0x64, 0x00, 0xff}
 	topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79}
 	var f WhisperFilterArgs
-	f.To = key
-	f.From = sig
-	f.Topics = topics[:]
-	f.PoW = MinimumPoW / 2
-	f.AcceptP2P = true
+	f.Symmetric = false
+	f.Key = key
+	f.SignedWith = sigPubKey.String()
+	f.Topics = make([][]byte, 2)
+	f.Topics[0] = topics[0][:]
+	f.Topics[1] = topics[1][:]
+	f.MinPoW = DefaultMinimumPoW / 2
+	f.AllowP2P = true
 
-	id, err := api.NewFilter(f)
+	id, err := api.Subscribe(f)
 	if err != nil {
 		t.Fatalf("failed to create new filter: %s.", err)
 	}
 
 	var p PostArgs
+	p.Type = "asym"
 	p.TTL = 2
-	p.From = f.From
-	p.To = f.To
+	p.SignWith = sig
+	p.Key = dstPubKey.String()
 	p.Padding = []byte("test string")
 	p.Payload = []byte("extended test string")
-	p.PoW = MinimumPoW
-	p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79}
-	p.WorkTime = 2
+	p.PowTarget = DefaultMinimumPoW
+	p.PowTime = 2
+	p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79} // topics[1]
 
 	err = api.Post(p)
 	if err != nil {
 		t.Errorf("failed to post message: %s.", err)
 	}
 
-	ok := waitForMessage(api, id, 1)
-	if !ok {
-		t.Fatalf("failed to receive first message: timeout.")
-	}
-
-	mail := api.GetFilterChanges(id)
+	mail := waitForMessages(api, id, 1)
 	if len(mail) != 1 {
 		t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail))
 	}
@@ -356,12 +392,7 @@ func TestIntegrationAsym(t *testing.T) {
 		t.Fatalf("failed to post next message: %s.", err)
 	}
 
-	ok = waitForMessage(api, id, 2)
-	if !ok {
-		t.Fatalf("failed to receive second message: timeout.")
-	}
-
-	mail = api.GetFilterChanges(id)
+	mail = waitForMessages(api, id, 1)
 	if len(mail) != 1 {
 		t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail))
 	}
@@ -382,21 +413,25 @@ func TestIntegrationSym(t *testing.T) {
 	api.Start()
 	defer api.Stop()
 
-	keyname := "schluessel"
-	err := api.GenerateSymKey(keyname)
+	symKeyID, err := api.GenerateSymmetricKey()
 	if err != nil {
 		t.Fatalf("failed GenerateSymKey: %s.", err)
 	}
 
-	sig, err := api.NewIdentity()
+	sig, err := api.NewKeyPair()
 	if err != nil {
-		t.Fatalf("failed NewIdentity: %s.", err)
+		t.Fatalf("failed NewKeyPair: %s.", err)
 	}
 	if len(sig) == 0 {
 		t.Fatalf("wrong signature")
 	}
 
-	exist, err := api.HasIdentity(sig)
+	sigPubKey, err := api.GetPublicKey(sig)
+	if err != nil {
+		t.Fatalf("failed GetPublicKey: %s.", err)
+	}
+
+	exist, err := api.HasKeyPair(sig)
 	if err != nil {
 		t.Fatalf("failed HasIdentity: %s.", err)
 	}
@@ -408,38 +443,37 @@ func TestIntegrationSym(t *testing.T) {
 	topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff}
 	topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79}
 	var f WhisperFilterArgs
-	f.KeyName = keyname
-	f.Topics = topics[:]
-	f.PoW = 0.324
-	f.From = sig
-	f.AcceptP2P = false
+	f.Symmetric = true
+	f.Key = symKeyID
+	f.Topics = make([][]byte, 2)
+	f.Topics[0] = topics[0][:]
+	f.Topics[1] = topics[1][:]
+	f.MinPoW = 0.324
+	f.SignedWith = sigPubKey.String()
+	f.AllowP2P = false
 
-	id, err := api.NewFilter(f)
+	id, err := api.Subscribe(f)
 	if err != nil {
 		t.Fatalf("failed to create new filter: %s.", err)
 	}
 
 	var p PostArgs
+	p.Type = "sym"
 	p.TTL = 1
-	p.KeyName = keyname
-	p.From = f.From
+	p.Key = symKeyID
+	p.SignWith = sig
 	p.Padding = []byte("test string")
 	p.Payload = []byte("extended test string")
-	p.PoW = MinimumPoW
-	p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79}
-	p.WorkTime = 2
+	p.PowTarget = DefaultMinimumPoW
+	p.PowTime = 2
+	p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79}
 
 	err = api.Post(p)
 	if err != nil {
 		t.Fatalf("failed to post first message: %s.", err)
 	}
 
-	ok := waitForMessage(api, id, 1)
-	if !ok {
-		t.Fatalf("failed to receive first message: timeout.")
-	}
-
-	mail := api.GetFilterChanges(id)
+	mail := waitForMessages(api, id, 1)
 	if len(mail) != 1 {
 		t.Fatalf("failed GetFilterChanges: got %d messages.", len(mail))
 	}
@@ -456,12 +490,7 @@ func TestIntegrationSym(t *testing.T) {
 		t.Fatalf("failed to post second message: %s.", err)
 	}
 
-	ok = waitForMessage(api, id, 2)
-	if !ok {
-		t.Fatalf("failed to receive second message: timeout.")
-	}
-
-	mail = api.GetFilterChanges(id)
+	mail = waitForMessages(api, id, 1)
 	if len(mail) != 1 {
 		t.Fatalf("failed second GetFilterChanges: got %d messages.", len(mail))
 	}
@@ -482,21 +511,20 @@ func TestIntegrationSymWithFilter(t *testing.T) {
 	api.Start()
 	defer api.Stop()
 
-	keyname := "schluessel"
-	err := api.GenerateSymKey(keyname)
+	symKeyID, err := api.GenerateSymmetricKey()
 	if err != nil {
 		t.Fatalf("failed to GenerateSymKey: %s.", err)
 	}
 
-	sig, err := api.NewIdentity()
+	sigKeyID, err := api.NewKeyPair()
 	if err != nil {
 		t.Fatalf("failed NewIdentity: %s.", err)
 	}
-	if len(sig) == 0 {
+	if len(sigKeyID) == 0 {
 		t.Fatalf("wrong signature.")
 	}
 
-	exist, err := api.HasIdentity(sig)
+	exist, err := api.HasKeyPair(sigKeyID)
 	if err != nil {
 		t.Fatalf("failed HasIdentity: %s.", err)
 	}
@@ -504,42 +532,46 @@ func TestIntegrationSymWithFilter(t *testing.T) {
 		t.Fatalf("failed HasIdentity: does not exist.")
 	}
 
+	sigPubKey, err := api.GetPublicKey(sigKeyID)
+	if err != nil {
+		t.Fatalf("failed GetPublicKey: %s.", err)
+	}
+
 	var topics [2]TopicType
 	topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff}
 	topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79}
 	var f WhisperFilterArgs
-	f.KeyName = keyname
-	f.Topics = topics[:]
-	f.PoW = 0.324
-	f.From = sig
-	f.AcceptP2P = false
+	f.Symmetric = true
+	f.Key = symKeyID
+	f.Topics = make([][]byte, 2)
+	f.Topics[0] = topics[0][:]
+	f.Topics[1] = topics[1][:]
+	f.MinPoW = 0.324
+	f.SignedWith = sigPubKey.String()
+	f.AllowP2P = false
 
-	id, err := api.NewFilter(f)
+	id, err := api.Subscribe(f)
 	if err != nil {
 		t.Fatalf("failed to create new filter: %s.", err)
 	}
 
 	var p PostArgs
+	p.Type = "sym"
 	p.TTL = 1
-	p.FilterID = id
-	p.From = sig
+	p.Key = symKeyID
+	p.SignWith = sigKeyID
 	p.Padding = []byte("test string")
 	p.Payload = []byte("extended test string")
-	p.PoW = MinimumPoW
-	p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79}
-	p.WorkTime = 2
+	p.PowTarget = DefaultMinimumPoW
+	p.PowTime = 2
+	p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79}
 
 	err = api.Post(p)
 	if err != nil {
 		t.Fatalf("failed to post message: %s.", err)
 	}
 
-	ok := waitForMessage(api, id, 1)
-	if !ok {
-		t.Fatalf("failed to receive first message: timeout.")
-	}
-
-	mail := api.GetFilterChanges(id)
+	mail := waitForMessages(api, id, 1)
 	if len(mail) != 1 {
 		t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail))
 	}
@@ -556,12 +588,7 @@ func TestIntegrationSymWithFilter(t *testing.T) {
 		t.Fatalf("failed to post next message: %s.", err)
 	}
 
-	ok = waitForMessage(api, id, 2)
-	if !ok {
-		t.Fatalf("failed to receive second message: timeout.")
-	}
-
-	mail = api.GetFilterChanges(id)
+	mail = waitForMessages(api, id, 1)
 	if len(mail) != 1 {
 		t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail))
 	}
@@ -571,3 +598,83 @@ func TestIntegrationSymWithFilter(t *testing.T) {
 		t.Fatalf("failed to decrypt second message: %s.", text)
 	}
 }
+
+func TestKey(t *testing.T) {
+	w := New()
+	api := NewPublicWhisperAPI(w)
+	if api == nil {
+		t.Fatalf("failed to create API.")
+	}
+
+	k, err := api.AddSymmetricKeyFromPassword("wwww")
+	if err != nil {
+		t.Fatalf("failed to create key: %s.", err)
+	}
+
+	s, err := api.GetSymmetricKey(k)
+	if err != nil {
+		t.Fatalf("failed to get sym key: %s.", err)
+	}
+
+	k2, err := api.AddSymmetricKeyDirect(s)
+	if err != nil {
+		t.Fatalf("failed to add sym key: %s.", err)
+	}
+
+	s2, err := api.GetSymmetricKey(k2)
+	if err != nil {
+		t.Fatalf("failed to get sym key: %s.", err)
+	}
+
+	if s.String() != "0x448652d595bd6ec00b2a9ea220ad6c26592d9bf4cf79023d3c1b30cb681e6e07" {
+		t.Fatalf("wrong key from password: %s", s.String())
+	}
+
+	if !bytes.Equal(s, s2) {
+		t.Fatalf("wrong key")
+	}
+}
+
+func TestSubscribe(t *testing.T) {
+	var err error
+	var s string
+
+	w := New()
+	api := NewPublicWhisperAPI(w)
+	if api == nil {
+		t.Fatalf("failed to create API.")
+	}
+
+	symKeyID, err := api.GenerateSymmetricKey()
+	if err != nil {
+		t.Fatalf("failed to GenerateSymKey: %s.", err)
+	}
+
+	var f WhisperFilterArgs
+	f.Symmetric = true
+	f.Key = symKeyID
+	f.Topics = make([][]byte, 5)
+	f.Topics[0] = []byte{0x21}
+	f.Topics[1] = []byte{0xd2, 0xe3}
+	f.Topics[2] = []byte{0x64, 0x75, 0x76}
+	f.Topics[3] = []byte{0xf8, 0xe9, 0xa0, 0xba}
+	f.Topics[4] = []byte{0xcb, 0x3c, 0xdd, 0xee, 0xff}
+
+	s, err = api.Subscribe(f)
+	if err == nil {
+		t.Fatalf("Subscribe: false positive.")
+	}
+
+	f.Topics[4] = []byte{}
+	if err == nil {
+		t.Fatalf("Subscribe: false positive again.")
+	}
+
+	f.Topics[4] = []byte{0x00}
+	s, err = api.Subscribe(f)
+	if err != nil {
+		t.Fatalf("failed to subscribe: %s.", err)
+	} else {
+		api.Unsubscribe(s)
+	}
+}
diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go
index 70c7008a73c4223496250ae36d0aa73abb62c086..d60868f6702fd577a3988a1ffcf46e84ef5678b3 100644
--- a/whisper/whisperv5/doc.go
+++ b/whisper/whisperv5/doc.go
@@ -54,9 +54,10 @@ const (
 	aesKeyLength      = 32
 	saltLength        = 12
 	AESNonceMaxLength = 12
+	keyIdSize         = 32
 
-	MaxMessageLength = 0x0FFFFF // todo: remove this restriction after testing. this should be regulated by PoW.
-	MinimumPoW       = 10.0     // todo: review after testing.
+	DefaultMaxMessageLength = 1024 * 1024
+	DefaultMinimumPoW       = 1.0 // todo: review after testing.
 
 	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
diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go
index 5d882d5dc2826567b23ee1df30b63c721495a2fd..dffa7b28622f196a3ec021fa9f092bc13fdd5257 100644
--- a/whisper/whisperv5/envelope.go
+++ b/whisper/whisperv5/envelope.go
@@ -21,7 +21,6 @@ package whisperv5
 import (
 	"crypto/ecdsa"
 	"encoding/binary"
-	"errors"
 	"fmt"
 	gmath "math"
 	"math/big"
@@ -83,7 +82,7 @@ func (e *Envelope) isAsymmetric() bool {
 }
 
 func (e *Envelope) Ver() uint64 {
-	return bytesToIntLittleEndian(e.Version)
+	return bytesToUintLittleEndian(e.Version)
 }
 
 // Seal closes the envelope by spending the requested amount of time as a proof
@@ -95,6 +94,9 @@ func (e *Envelope) Seal(options *MessageParams) error {
 		e.Expiry += options.WorkTime
 	} else {
 		target = e.powToFirstBit(options.PoW)
+		if target < 1 {
+			target = 1
+		}
 	}
 
 	buf := make([]byte, 64)
@@ -118,7 +120,7 @@ func (e *Envelope) Seal(options *MessageParams) error {
 	}
 
 	if target > 0 && bestBit < target {
-		return errors.New("Failed to reach the PoW target, insufficient work time")
+		return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
 	}
 
 	return nil
diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go
index ffa5ae94632cba1af3a8da1d28d4b4ac72accbfe..03101d4a424ab4b16be1aa7b77738195d8158f76 100644
--- a/whisper/whisperv5/filter.go
+++ b/whisper/whisperv5/filter.go
@@ -18,7 +18,6 @@ package whisperv5
 
 import (
 	"crypto/ecdsa"
-	crand "crypto/rand"
 	"fmt"
 	"sync"
 
@@ -30,9 +29,9 @@ 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
+	Topics     [][]byte          // 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
+	AllowP2P   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
@@ -52,47 +51,35 @@ func NewFilters(w *Whisper) *Filters {
 	}
 }
 
-func (fs *Filters) generateRandomID() (id string, err error) {
-	buf := make([]byte, 20)
-	for i := 0; i < 3; i++ {
-		_, err = crand.Read(buf)
-		if err != nil {
-			continue
-		}
-		if !validateSymmetricKey(buf) {
-			err = fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data")
-			continue
-		}
-		id = common.Bytes2Hex(buf)
-		if fs.watchers[id] != nil {
-			err = fmt.Errorf("error in generateRandomID: generated same ID twice")
-			continue
-		}
-		return id, err
-	}
-
-	return "", err
-}
-
 func (fs *Filters) Install(watcher *Filter) (string, error) {
 	if watcher.Messages == nil {
 		watcher.Messages = make(map[common.Hash]*ReceivedMessage)
 	}
 
+	id, err := GenerateRandomID()
+	if err != nil {
+		return "", err
+	}
+
 	fs.mutex.Lock()
 	defer fs.mutex.Unlock()
 
-	id, err := fs.generateRandomID()
-	if err == nil {
-		fs.watchers[id] = watcher
+	if fs.watchers[id] != nil {
+		return "", fmt.Errorf("failed to generate unique ID")
 	}
+
+	fs.watchers[id] = watcher
 	return id, err
 }
 
-func (fs *Filters) Uninstall(id string) {
+func (fs *Filters) Uninstall(id string) bool {
 	fs.mutex.Lock()
 	defer fs.mutex.Unlock()
-	delete(fs.watchers, id)
+	if fs.watchers[id] != nil {
+		delete(fs.watchers, id)
+		return true
+	}
+	return false
 }
 
 func (fs *Filters) Get(id string) *Filter {
@@ -102,11 +89,16 @@ func (fs *Filters) Get(id string) *Filter {
 }
 
 func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
-	fs.mutex.RLock()
 	var msg *ReceivedMessage
-	for j, watcher := range fs.watchers {
-		if p2pMessage && !watcher.AcceptP2P {
-			log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), j))
+
+	fs.mutex.RLock()
+	defer fs.mutex.RUnlock()
+
+	i := -1 // only used for logging info
+	for _, watcher := range fs.watchers {
+		i++
+		if p2pMessage && !watcher.AllowP2P {
+			log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i))
 			continue
 		}
 
@@ -118,22 +110,32 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
 			if match {
 				msg = env.Open(watcher)
 				if msg == nil {
-					log.Trace(fmt.Sprintf("msg [%x], filter [%s]: failed to open", env.Hash(), j))
+					log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", i)
 				}
 			} else {
-				log.Trace(fmt.Sprintf("msg [%x], filter [%s]: does not match", env.Hash(), j))
+				log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i)
 			}
 		}
 
 		if match && msg != nil {
+			log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
 			watcher.Trigger(msg)
 		}
 	}
-	fs.mutex.RUnlock() // we need to unlock before calling addDecryptedMessage
+}
 
-	if msg != nil {
-		fs.whisper.addDecryptedMessage(msg)
+func (f *Filter) processEnvelope(env *Envelope) *ReceivedMessage {
+	if f.MatchEnvelope(env) {
+		msg := env.Open(f)
+		if msg != nil {
+			return msg
+		} else {
+			log.Trace("processing envelope: failed to open", "hash", env.Hash().Hex())
+		}
+	} else {
+		log.Trace("processing envelope: does not match", "hash", env.Hash().Hex())
 	}
+	return nil
 }
 
 func (f *Filter) expectsAsymmetricEncryption() bool {
@@ -200,20 +202,33 @@ func (f *Filter) MatchTopic(topic TopicType) bool {
 		return true
 	}
 
-	for _, t := range f.Topics {
-		if t == topic {
+	for _, bt := range f.Topics {
+		if matchSingleTopic(topic, bt) {
 			return true
 		}
 	}
 	return false
 }
 
+func matchSingleTopic(topic TopicType, bt []byte) bool {
+	if len(bt) > 4 {
+		bt = bt[:4]
+	}
+
+	for j, b := range bt {
+		if topic[j] != b {
+			return false
+		}
+	}
+	return true
+}
+
 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
+	// 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
index 1cf85b8d7ed461a7dfe1dff258c293f5eda5c2e2..ae21d173967455f62671dca7ed7ebf6fbe3fd8ae 100644
--- a/whisper/whisperv5/filter_test.go
+++ b/whisper/whisperv5/filter_test.go
@@ -53,8 +53,9 @@ func generateFilter(t *testing.T, symmetric bool) (*Filter, error) {
 	f.Messages = make(map[common.Hash]*ReceivedMessage)
 
 	const topicNum = 8
-	f.Topics = make([]TopicType, topicNum)
+	f.Topics = make([][]byte, topicNum)
 	for i := 0; i < topicNum; i++ {
+		f.Topics[i] = make([]byte, 4)
 		mrand.Read(f.Topics[i][:])
 		f.Topics[i][0] = 0x01
 	}
@@ -108,7 +109,7 @@ func TestInstallFilters(t *testing.T) {
 			t.Fatalf("seed %d: failed to install filter: %s", seed, err)
 		}
 		tst[i].id = j
-		if len(j) != 40 {
+		if len(j) != keyIdSize*2 {
 			t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j))
 		}
 	}
@@ -194,8 +195,8 @@ func TestMatchEnvelope(t *testing.T) {
 
 	// encrypt symmetrically
 	i := mrand.Int() % 4
-	fsym.Topics[i] = params.Topic
-	fasym.Topics[i] = params.Topic
+	fsym.Topics[i] = params.Topic[:]
+	fasym.Topics[i] = params.Topic[:]
 	msg = NewSentMessage(params)
 	env, err = msg.Wrap(params)
 	if err != nil {
@@ -320,7 +321,7 @@ func TestMatchMessageSym(t *testing.T) {
 
 	const index = 1
 	params.KeySym = f.KeySym
-	params.Topic = f.Topics[index]
+	params.Topic = BytesToTopic(f.Topics[index])
 
 	sentMessage := NewSentMessage(params)
 	env, err := sentMessage.Wrap(params)
@@ -413,7 +414,7 @@ func TestMatchMessageAsym(t *testing.T) {
 	}
 
 	const index = 1
-	params.Topic = f.Topics[index]
+	params.Topic = BytesToTopic(f.Topics[index])
 	params.Dst = &f.KeyAsym.PublicKey
 	keySymOrig := params.KeySym
 	params.KeySym = nil
@@ -491,7 +492,7 @@ func cloneFilter(orig *Filter) *Filter {
 	clone.KeySym = orig.KeySym
 	clone.Topics = orig.Topics
 	clone.PoW = orig.PoW
-	clone.AcceptP2P = orig.AcceptP2P
+	clone.AllowP2P = orig.AllowP2P
 	clone.SymKeyHash = orig.SymKeyHash
 	return &clone
 }
@@ -504,7 +505,7 @@ func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope {
 	}
 
 	params.KeySym = f.KeySym
-	params.Topic = f.Topics[2]
+	params.Topic = BytesToTopic(f.Topics[2])
 	sentMessage := NewSentMessage(params)
 	env, err := sentMessage.Wrap(params)
 	if err != nil {
@@ -655,7 +656,7 @@ func TestWatchers(t *testing.T) {
 	if f == nil {
 		t.Fatalf("failed to get the filter with seed %d.", seed)
 	}
-	f.AcceptP2P = true
+	f.AllowP2P = true
 	total = 0
 	filters.NotifyWatchers(envelopes[0], true)
 
@@ -668,3 +669,40 @@ func TestWatchers(t *testing.T) {
 		t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total)
 	}
 }
+
+func TestVariableTopics(t *testing.T) {
+	InitSingleTest()
+
+	var match bool
+	params, err := generateMessageParams()
+	if err != nil {
+		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
+	}
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
+	}
+
+	f, err := generateFilter(t, true)
+	if err != nil {
+		t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
+	}
+
+	for i := 0; i < 4; i++ {
+		arr := make([]byte, i+1, 4)
+		copy(arr, env.Topic[:i+1])
+
+		f.Topics[4] = arr
+		match = f.MatchEnvelope(env)
+		if !match {
+			t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i)
+		}
+
+		f.Topics[4][i]++
+		match = f.MatchEnvelope(env)
+		if match {
+			t.Fatalf("MatchEnvelope symmetric with seed %d, step %d: false positive.", seed, i)
+		}
+	}
+}
diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go
index 5f964b072ad06fe3969a1514e2a475bfd6a8b925..9b9c389a6a6c9c2757edcd7f2d51ad99c4c0347c 100644
--- a/whisper/whisperv5/message.go
+++ b/whisper/whisperv5/message.go
@@ -25,8 +25,6 @@ import (
 	crand "crypto/rand"
 	"crypto/sha256"
 	"errors"
-	"fmt"
-	mrand "math/rand"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -102,14 +100,18 @@ 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)
+	err := msg.appendPadding(params)
+	if err != nil {
+		log.Error("failed to create NewSentMessage", "err", err)
+		return nil
+	}
 	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) {
+func (msg *SentMessage) appendPadding(params *MessageParams) error {
 	total := len(params.Payload) + 1
 	if params.Src != nil {
 		total += signatureLength
@@ -128,7 +130,10 @@ func (msg *SentMessage) appendPadding(params *MessageParams) {
 			panic("please fix the padding algorithm before releasing new version")
 		}
 		buf := make([]byte, padSize)
-		mrand.Read(buf[1:])
+		_, err := crand.Read(buf[1:])
+		if err != nil {
+			return err
+		}
 		buf[0] = byte(padSize)
 		if params.Padding != nil {
 			copy(buf[1:], params.Padding)
@@ -136,6 +141,7 @@ func (msg *SentMessage) appendPadding(params *MessageParams) {
 		msg.Raw = append(msg.Raw, buf...)
 		msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size
 	}
+	return nil
 }
 
 // sign calculates and sets the cryptographic signature for the message,
@@ -143,7 +149,7 @@ func (msg *SentMessage) appendPadding(params *MessageParams) {
 func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
 	if isMessageSigned(msg.Raw[0]) {
 		// this should not happen, but no reason to panic
-		log.Error(fmt.Sprintf("Trying to sign a message which was already signed"))
+		log.Error("failed to sign the message: already signed")
 		return nil
 	}
 
@@ -161,7 +167,7 @@ func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error {
 // 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")
+		return errors.New("invalid public key provided for asymmetric encryption")
 	}
 	encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil)
 	if err == nil {
@@ -215,17 +221,6 @@ func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte,
 }
 
 // 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
@@ -236,17 +231,13 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
 			return nil, err
 		}
 	}
-	if len(msg.Raw) > MaxMessageLength {
-		log.Error(fmt.Sprintf("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")
+		err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
 	}
 
 	if err != nil {
@@ -258,7 +249,6 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
 	if err != nil {
 		return nil, err
 	}
-
 	return envelope, nil
 }
 
@@ -279,9 +269,8 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []by
 		return err
 	}
 	if len(nonce) != aesgcm.NonceSize() {
-		info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize())
-		log.Error(fmt.Sprintf(info))
-		return errors.New(info)
+		log.Error("decrypting the message", "AES nonce size", len(nonce))
+		return errors.New("wrong AES nonce size")
 	}
 	decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)
 	if err != nil {
@@ -336,7 +325,7 @@ 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]))
+		paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz]))
 		if paddingSize < sz || paddingSize+1 > end {
 			return 0, false
 		}
@@ -351,7 +340,7 @@ func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
 
 	pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
 	if err != nil {
-		log.Error(fmt.Sprintf("Could not get public key from signature: %v", err))
+		log.Error("failed to recover public key from signature", "err", err)
 		return nil
 	}
 	return pub
diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go
index 315401aea643501e4cde9e1ea3a9b030727381df..184c4ebf8f06d654df44c2c7a1040a26f14fa653 100644
--- a/whisper/whisperv5/peer.go
+++ b/whisper/whisperv5/peer.go
@@ -55,13 +55,13 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
 // into the network.
 func (p *Peer) start() {
 	go p.update()
-	log.Debug(fmt.Sprintf("%v: whisper started", p.peer))
+	log.Trace("start", "peer", p.ID())
 }
 
 // stop terminates the peer updater, stopping message forwarding to it.
 func (p *Peer) stop() {
 	close(p.quit)
-	log.Debug(fmt.Sprintf("%v: whisper stopped", p.peer))
+	log.Trace("stop", "peer", p.ID())
 }
 
 // handshake sends the protocol initiation status message to the remote peer and
@@ -78,19 +78,19 @@ func (p *Peer) handshake() error {
 		return err
 	}
 	if packet.Code != statusCode {
-		return fmt.Errorf("peer sent %x before status packet", packet.Code)
+		return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), 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)
+		return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err)
 	}
 	if peerVersion != ProtocolVersion {
-		return fmt.Errorf("protocol version mismatch %d != %d", peerVersion, ProtocolVersion)
+		return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), 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 fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err)
 	}
 	return nil
 }
@@ -110,7 +110,7 @@ func (p *Peer) update() {
 
 		case <-transmit.C:
 			if err := p.broadcast(); err != nil {
-				log.Info(fmt.Sprintf("%v: broadcast failed: %v", p.peer, err))
+				log.Trace("broadcast failed", "reason", err, "peer", p.ID())
 				return
 			}
 
@@ -165,7 +165,7 @@ func (p *Peer) broadcast() error {
 	if err := p2p.Send(p.ws, messagesCode, transmit); err != nil {
 		return err
 	}
-	log.Trace(fmt.Sprint(p.peer, "broadcasted", len(transmit), "message(s)"))
+	log.Trace("broadcast", "num. messages", len(transmit))
 	return nil
 }
 
diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go
index e3073bc6c8ec6fbd1685e297c5f653c111f4c287..a79b6ad144d1253ea46f94a27a3997fa7b619866 100644
--- a/whisper/whisperv5/peer_test.go
+++ b/whisper/whisperv5/peer_test.go
@@ -114,12 +114,13 @@ func initialize(t *testing.T) {
 	for i := 0; i < NumNodes; i++ {
 		var node TestNode
 		node.shh = New()
-		node.shh.test = true
+		node.shh.SetMinimumPoW(0.00000001)
 		node.shh.Start(nil)
 		topics := make([]TopicType, 0)
 		topics = append(topics, sharedTopic)
-		f := Filter{KeySym: sharedKey, Topics: topics}
-		node.filerId, err = node.shh.Watch(&f)
+		f := Filter{KeySym: sharedKey}
+		f.Topics = [][]byte{topics[0][:]}
+		node.filerId, err = node.shh.Subscribe(&f)
 		if err != nil {
 			t.Fatalf("failed to install the filter: %s.", err)
 		}
@@ -166,7 +167,7 @@ func stopServers() {
 	for i := 0; i < NumNodes; i++ {
 		n := nodes[i]
 		if n != nil {
-			n.shh.Unwatch(n.filerId)
+			n.shh.Unsubscribe(n.filerId)
 			n.shh.Stop()
 			n.server.Stop()
 		}
@@ -257,7 +258,7 @@ func sendMsg(t *testing.T, expected bool, id int) {
 		return
 	}
 
-	opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001}
+	opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1}
 	if !expected {
 		opt.KeySym[0]++
 		opt.Topic[0]++
@@ -267,12 +268,12 @@ func sendMsg(t *testing.T, expected bool, id int) {
 	msg := NewSentMessage(&opt)
 	envelope, err := msg.Wrap(&opt)
 	if err != nil {
-		t.Fatalf("failed to seal message.")
+		t.Fatalf("failed to seal message: %s", err)
 	}
 
 	err = nodes[id].shh.Send(envelope)
 	if err != nil {
-		t.Fatalf("failed to send message.")
+		t.Fatalf("failed to send message: %s", err)
 	}
 }
 
diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go
index 5062f7b6bc9eafcd468b839ce84f5579d75dc0c7..c4d5d04a743c3e3a61e8abd04e0d2df98dc87a0e 100644
--- a/whisper/whisperv5/whisper.go
+++ b/whisper/whisperv5/whisper.go
@@ -31,59 +31,62 @@ import (
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/syndtr/goleveldb/leveldb/errors"
 	"golang.org/x/crypto/pbkdf2"
 	set "gopkg.in/fatih/set.v0"
 )
 
 type Statistics struct {
-	messagesCleared int
-	memoryCleared   int
-	totalMemoryUsed int
+	messagesCleared      int
+	memoryCleared        int
+	memoryUsed           int
+	cycles               int
+	totalMessagesCleared int
 }
 
 // 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
+	protocol p2p.Protocol // Protocol description and parameters
+	filters  *Filters     // Message filters installed with Subscribe function
 
-	privateKeys map[string]*ecdsa.PrivateKey
-	symKeys     map[string][]byte
-	keyMu       sync.RWMutex
+	privateKeys map[string]*ecdsa.PrivateKey // Private key storage
+	symKeys     map[string][]byte            // Symmetric key storage
+	keyMu       sync.RWMutex                 // Mutex associated with key storages
 
-	envelopes   map[common.Hash]*Envelope        // Pool of envelopes 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
+	envelopes   map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node
+	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
+	messageQueue chan *Envelope // Message queue for normal whisper messages
+	p2pMsgQueue  chan *Envelope // Message queue for peer-to-peer messages (not to be forwarded any further)
+	quit         chan struct{}  // Channel used for graceful exit
 
-	messageQueue chan *Envelope
-	p2pMsgQueue  chan *Envelope
-	quit         chan struct{}
+	minPoW       float64 // Minimal PoW required by the whisper node
+	maxMsgLength int     // Maximal message length allowed by the whisper node
+	overflow     bool    // Indicator of message queue overflow
 
-	stats Statistics
+	stats Statistics // Statistics of whisper node
 
-	overflow bool
-	test     bool
+	mailServer MailServer // MailServer interface
 }
 
 // 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 New() *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{}),
 		messageQueue: make(chan *Envelope, messageQueueLimit),
 		p2pMsgQueue:  make(chan *Envelope, messageQueueLimit),
 		quit:         make(chan struct{}),
+		minPoW:       DefaultMinimumPoW,
+		maxMsgLength: DefaultMaxMessageLength,
 	}
 	whisper.filters = NewFilters(whisper)
 
@@ -110,6 +113,8 @@ func (w *Whisper) APIs() []rpc.API {
 	}
 }
 
+// RegisterServer registers MailServer interface.
+// MailServer will process all the incoming messages with p2pRequestCode.
 func (w *Whisper) RegisterServer(server MailServer) {
 	w.mailServer = server
 }
@@ -124,6 +129,25 @@ func (w *Whisper) Version() uint {
 	return w.protocol.Version
 }
 
+// SetMaxMessageLength sets the maximal message length allowed by this node
+func (w *Whisper) SetMaxMessageLength(val int) error {
+	if val <= 0 {
+		return fmt.Errorf("invalid message length: %d", val)
+	}
+	w.maxMsgLength = val
+	return nil
+}
+
+// SetMinimumPoW sets the minimal PoW required by this node
+func (w *Whisper) SetMinimumPoW(val float64) error {
+	if val <= 0.0 {
+		return fmt.Errorf("invalid PoW: %f", val)
+	}
+	w.minPoW = val
+	return nil
+}
+
+// getPeer retrieves peer by ID
 func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
 	w.peerMu.Lock()
 	defer w.peerMu.Unlock()
@@ -136,9 +160,9 @@ func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
 	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 {
+// AllowP2PMessagesFromPeer marks specific peer trusted,
+// which will allow it to send historic (expired) messages.
+func (w *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error {
 	p, err := w.getPeer(peerID)
 	if err != nil {
 		return err
@@ -147,6 +171,11 @@ func (w *Whisper) MarkPeerTrusted(peerID []byte) error {
 	return nil
 }
 
+// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
+// which is known to implement MailServer interface, and is supposed to process this
+// request and respond with a number of peer-to-peer messages (possibly expired),
+// which are not supposed to be forwarded any further.
+// The whisper protocol is agnostic of the format and contents of envelope.
 func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error {
 	p, err := w.getPeer(peerID)
 	if err != nil {
@@ -156,153 +185,226 @@ func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) err
 	return p2p.Send(p.ws, p2pRequestCode, envelope)
 }
 
+// SendP2PMessage sends a peer-to-peer message to a specific peer.
 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)
+	return w.SendP2PDirect(p, envelope)
 }
 
+// SendP2PDirect sends a peer-to-peer message to a specific peer.
 func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error {
 	return p2p.Send(peer.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 {
+// NewKeyPair generates a new cryptographic identity for the client, and injects
+// it into the known identities for message decryption. Returns ID of the new key pair.
+func (w *Whisper) NewKeyPair() (string, error) {
 	key, err := crypto.GenerateKey()
 	if err != nil || !validatePrivateKey(key) {
 		key, err = crypto.GenerateKey() // retry once
 	}
 	if err != nil {
-		panic(err)
+		return "", err
 	}
 	if !validatePrivateKey(key) {
-		panic("Failed to generate valid key")
+		return "", fmt.Errorf("failed to generate valid key")
 	}
+
+	id, err := GenerateRandomID()
+	if err != nil {
+		return "", fmt.Errorf("failed to generate ID: %s", err)
+	}
+
 	w.keyMu.Lock()
 	defer w.keyMu.Unlock()
-	w.privateKeys[common.ToHex(crypto.FromECDSAPub(&key.PublicKey))] = key
-	return key
+
+	if w.privateKeys[id] != nil {
+		return "", fmt.Errorf("failed to generate unique ID")
+	}
+	w.privateKeys[id] = key
+	return id, nil
 }
 
-// DeleteIdentity deletes the specified key if it exists.
-func (w *Whisper) DeleteIdentity(key string) {
+// DeleteKeyPair deletes the specified key if it exists.
+func (w *Whisper) DeleteKeyPair(key string) bool {
 	w.keyMu.Lock()
 	defer w.keyMu.Unlock()
-	delete(w.privateKeys, key)
+
+	if w.privateKeys[key] != nil {
+		delete(w.privateKeys, key)
+		return true
+	}
+	return false
 }
 
-// HasIdentity checks if the the whisper node is configured with the private key
+// HasKeyPair checks if the the whisper node is configured with the private key
 // of the specified public pair.
-func (w *Whisper) HasIdentity(pubKey string) bool {
+func (w *Whisper) HasKeyPair(id string) bool {
 	w.keyMu.RLock()
 	defer w.keyMu.RUnlock()
-	return w.privateKeys[pubKey] != nil
+	return w.privateKeys[id] != nil
 }
 
-// GetIdentity retrieves the private key of the specified public identity.
-func (w *Whisper) GetIdentity(pubKey string) *ecdsa.PrivateKey {
+// GetPrivateKey retrieves the private key of the specified identity.
+func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
 	w.keyMu.RLock()
 	defer w.keyMu.RUnlock()
-	return w.privateKeys[pubKey]
+	key := w.privateKeys[id]
+	if key == nil {
+		return nil, fmt.Errorf("invalid id")
+	}
+	return key, nil
 }
 
-func (w *Whisper) GenerateSymKey(name string) error {
+// GenerateSymKey generates a random symmetric key and stores it under id,
+// which is then returned. Will be used in the future for session key exchange.
+func (w *Whisper) GenerateSymKey() (string, error) {
 	const size = aesKeyLength * 2
 	buf := make([]byte, size)
 	_, err := crand.Read(buf)
 	if err != nil {
-		return err
+		return "", err
 	} else if !validateSymmetricKey(buf) {
-		return fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data")
+		return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data")
 	}
 
 	key := buf[:aesKeyLength]
 	salt := buf[aesKeyLength:]
 	derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion)
 	if err != nil {
-		return err
+		return "", err
 	} else if !validateSymmetricKey(derived) {
-		return fmt.Errorf("failed to derive valid key")
+		return "", fmt.Errorf("failed to derive valid key")
+	}
+
+	id, err := GenerateRandomID()
+	if err != nil {
+		return "", fmt.Errorf("failed to generate ID: %s", err)
 	}
 
 	w.keyMu.Lock()
 	defer w.keyMu.Unlock()
 
-	if w.symKeys[name] != nil {
-		return fmt.Errorf("Key with name [%s] already exists", name)
+	if w.symKeys[id] != nil {
+		return "", fmt.Errorf("failed to generate unique ID")
 	}
-	w.symKeys[name] = derived
-	return nil
+	w.symKeys[id] = derived
+	return id, nil
 }
 
-func (w *Whisper) AddSymKey(name string, key []byte) error {
-	if w.HasSymKey(name) {
-		return fmt.Errorf("Key with name [%s] already exists", name)
+// AddSymKeyDirect stores the key, and returns its id.
+func (w *Whisper) AddSymKeyDirect(key []byte) (string, error) {
+	if len(key) != aesKeyLength {
+		return "", fmt.Errorf("wrong key size: %d", len(key))
 	}
 
-	derived, err := deriveKeyMaterial(key, EnvelopeVersion)
+	id, err := GenerateRandomID()
 	if err != nil {
-		return err
+		return "", fmt.Errorf("failed to generate ID: %s", 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)
+	if w.symKeys[id] != nil {
+		return "", fmt.Errorf("failed to generate unique ID")
 	}
-	w.symKeys[name] = derived
-	return nil
+	w.symKeys[id] = key
+	return id, nil
 }
 
-func (w *Whisper) HasSymKey(name string) bool {
+// AddSymKeyFromPassword generates the key from password, stores it, and returns its id.
+func (w *Whisper) AddSymKeyFromPassword(password string) (string, error) {
+	id, err := GenerateRandomID()
+	if err != nil {
+		return "", fmt.Errorf("failed to generate ID: %s", err)
+	}
+	if w.HasSymKey(id) {
+		return "", fmt.Errorf("failed to generate unique ID")
+	}
+
+	derived, err := deriveKeyMaterial([]byte(password), EnvelopeVersion)
+	if err != nil {
+		return "", err
+	}
+
+	w.keyMu.Lock()
+	defer w.keyMu.Unlock()
+
+	// double check is necessary, because deriveKeyMaterial() is very slow
+	if w.symKeys[id] != nil {
+		return "", fmt.Errorf("critical error: failed to generate unique ID")
+	}
+	w.symKeys[id] = derived
+	return id, nil
+}
+
+// HasSymKey returns true if there is a key associated with the given id.
+// Otherwise returns false.
+func (w *Whisper) HasSymKey(id string) bool {
 	w.keyMu.RLock()
 	defer w.keyMu.RUnlock()
-	return w.symKeys[name] != nil
+	return w.symKeys[id] != nil
 }
 
-func (w *Whisper) DeleteSymKey(name string) {
+// DeleteSymKey deletes the key associated with the name string if it exists.
+func (w *Whisper) DeleteSymKey(id string) bool {
 	w.keyMu.Lock()
 	defer w.keyMu.Unlock()
-	delete(w.symKeys, name)
+	if w.symKeys[id] != nil {
+		delete(w.symKeys, id)
+		return true
+	}
+	return false
 }
 
-func (w *Whisper) GetSymKey(name string) []byte {
+// GetSymKey returns the symmetric key associated with the given id.
+func (w *Whisper) GetSymKey(id string) ([]byte, error) {
 	w.keyMu.RLock()
 	defer w.keyMu.RUnlock()
-	return w.symKeys[name]
+	if w.symKeys[id] != nil {
+		return w.symKeys[id], nil
+	}
+	return nil, fmt.Errorf("non-existent key ID")
 }
 
-// Watch installs a new message handler to run in case a matching packet arrives
-// from the whisper network.
-func (w *Whisper) Watch(f *Filter) (string, error) {
+// Subscribe installs a new message handler used for filtering, decrypting
+// and subsequent storing of incoming messages.
+func (w *Whisper) Subscribe(f *Filter) (string, error) {
 	return w.filters.Install(f)
 }
 
+// GetFilter returns the filter by id.
 func (w *Whisper) GetFilter(id string) *Filter {
 	return w.filters.Get(id)
 }
 
-// Unwatch removes an installed message handler.
-func (w *Whisper) Unwatch(id string) {
-	w.filters.Uninstall(id)
+// Unsubscribe removes an installed message handler.
+func (w *Whisper) Unsubscribe(id string) error {
+	ok := w.filters.Uninstall(id)
+	if !ok {
+		return fmt.Errorf("Unsubscribe: Invalid ID")
+	}
+	return nil
 }
 
 // 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 {
-	_, err := w.add(envelope)
+	ok, err := w.add(envelope)
+	if !ok {
+		return fmt.Errorf("failed to add envelope")
+	}
 	return err
 }
 
 // Start implements node.Service, starting the background data propagation thread
 // of the Whisper protocol.
 func (w *Whisper) Start(*p2p.Server) error {
-	log.Info(fmt.Sprint("Whisper started"))
+	log.Info("started whisper v." + ProtocolVersionStr)
 	go w.update()
 
 	numCPU := runtime.NumCPU()
@@ -317,11 +419,11 @@ func (w *Whisper) Start(*p2p.Server) error {
 // of the Whisper protocol.
 func (w *Whisper) Stop() error {
 	close(w.quit)
-	log.Info(fmt.Sprint("Whisper stopped"))
+	log.Info("whisper stopped")
 	return nil
 }
 
-// handlePeer is called by the underlying P2P layer when the whisper sub-protocol
+// 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
@@ -353,26 +455,31 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
 		// fetch the next packet
 		packet, err := rw.ReadMsg()
 		if err != nil {
+			log.Warn("message loop", "peer", p.peer.ID(), "err", err)
 			return err
 		}
+		if packet.Size > uint32(wh.maxMsgLength) {
+			log.Warn("oversized message received", "peer", p.peer.ID())
+			return errors.New("oversized message received")
+		}
 
 		switch packet.Code {
 		case statusCode:
 			// this should not happen, but no need to panic; just ignore this message.
-			log.Warn(fmt.Sprintf("%v: unxepected status message received", p.peer))
+			log.Warn("unxepected status message received", "peer", p.peer.ID())
 		case messagesCode:
 			// decode the contained envelopes
 			var envelopes []*Envelope
 			if err := packet.Decode(&envelopes); err != nil {
-				log.Warn(fmt.Sprintf("%v: failed to decode envelope: [%v], peer will be disconnected", p.peer, err))
-				return fmt.Errorf("garbage received")
+				log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err)
+				return errors.New("invalid envelope")
 			}
 			// inject all envelopes into the internal pool
 			for _, envelope := range envelopes {
 				cached, err := wh.add(envelope)
 				if err != nil {
-					log.Warn(fmt.Sprintf("%v: bad envelope received: [%v], peer will be disconnected", p.peer, err))
-					return fmt.Errorf("invalid envelope")
+					log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err)
+					return errors.New("invalid envelope")
 				}
 				if cached {
 					p.mark(envelope)
@@ -386,8 +493,8 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
 			if p.trusted {
 				var envelope Envelope
 				if err := packet.Decode(&envelope); err != nil {
-					log.Warn(fmt.Sprintf("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err))
-					return fmt.Errorf("garbage received (directMessage)")
+					log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
+					return errors.New("invalid direct message")
 				}
 				wh.postEvent(&envelope, true)
 			}
@@ -396,8 +503,8 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
 			if wh.mailServer != nil {
 				var request Envelope
 				if err := packet.Decode(&request); err != nil {
-					log.Warn(fmt.Sprintf("%v: failed to decode p2p request message: [%v], peer will be disconnected", p.peer, err))
-					return fmt.Errorf("garbage received (p2p request)")
+					log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
+					return errors.New("invalid p2p request")
 				}
 				wh.mailServer.DeliverMail(p, &request)
 			}
@@ -430,12 +537,12 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
 		if envelope.Expiry+SynchAllowance*2 < now {
 			return false, fmt.Errorf("very old message")
 		} else {
-			log.Debug(fmt.Sprintf("expired envelope dropped [%x]", envelope.Hash()))
+			log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex())
 			return false, nil // drop envelope without error
 		}
 	}
 
-	if len(envelope.Data) > MaxMessageLength {
+	if envelope.size() > wh.maxMsgLength {
 		return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash())
 	}
 
@@ -453,8 +560,8 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
 		return false, fmt.Errorf("oversized salt [%x]", envelope.Hash())
 	}
 
-	if envelope.PoW() < MinimumPoW && !wh.test {
-		log.Debug(fmt.Sprintf("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash()))
+	if envelope.PoW() < wh.minPoW {
+		log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex())
 		return false, nil // drop envelope without error
 	}
 
@@ -474,10 +581,10 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
 	wh.poolMu.Unlock()
 
 	if alreadyCached {
-		log.Trace(fmt.Sprintf("whisper envelope already cached [%x]\n", envelope.Hash()))
+		log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex())
 	} else {
-		log.Trace(fmt.Sprintf("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope))
-		wh.stats.totalMemoryUsed += envelope.size()
+		log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex())
+		wh.stats.memoryUsed += envelope.size()
 		wh.postEvent(envelope, false) // notify the local node about the new message
 		if wh.mailServer != nil {
 			wh.mailServer.Archive(envelope)
@@ -508,11 +615,12 @@ func (w *Whisper) checkOverflow() {
 	if queueSize == messageQueueLimit {
 		if !w.overflow {
 			w.overflow = true
-			log.Warn(fmt.Sprint("message queue overflow"))
+			log.Warn("message queue overflow")
 		}
 	} else if queueSize <= messageQueueLimit/2 {
 		if w.overflow {
 			w.overflow = false
+			log.Warn("message queue overflow fixed (back to normal)")
 		}
 	}
 }
@@ -558,19 +666,17 @@ func (w *Whisper) expire() {
 	w.poolMu.Lock()
 	defer w.poolMu.Unlock()
 
-	w.stats.clear()
+	w.stats.reset()
 	now := uint32(time.Now().Unix())
 	for expiry, hashSet := range w.expirations {
 		if expiry < now {
-			w.stats.messagesCleared++
-
 			// Dump all expired messages and remove timestamp
 			hashSet.Each(func(v interface{}) bool {
 				sz := w.envelopes[v.(common.Hash)].size()
-				w.stats.memoryCleared += sz
-				w.stats.totalMemoryUsed -= sz
 				delete(w.envelopes, v.(common.Hash))
-				delete(w.messages, v.(common.Hash))
+				w.stats.messagesCleared++
+				w.stats.memoryCleared += sz
+				w.stats.memoryUsed -= sz
 				return true
 			})
 			w.expirations[expiry].Clear()
@@ -579,12 +685,21 @@ func (w *Whisper) expire() {
 	}
 }
 
+// Stats returns the whisper node statistics.
 func (w *Whisper) Stats() string {
-	return fmt.Sprintf("Latest expiry cycle cleared %d messages (%d bytes). Memory usage: %d bytes.",
-		w.stats.messagesCleared, w.stats.memoryCleared, w.stats.totalMemoryUsed)
+	result := fmt.Sprintf("Memory usage: %d bytes. Average messages cleared per expiry cycle: %d. Total messages cleared: %d.",
+		w.stats.memoryUsed, w.stats.totalMessagesCleared/w.stats.cycles, w.stats.totalMessagesCleared)
+	if w.stats.messagesCleared > 0 {
+		result += fmt.Sprintf(" Latest expiry cycle cleared %d messages (%d bytes).",
+			w.stats.messagesCleared, w.stats.memoryCleared)
+	}
+	if w.overflow {
+		result += " Message queue state: overflow."
+	}
+	return result
 }
 
-// envelopes retrieves all the messages currently pooled by the node.
+// Envelopes retrieves all the messages currently pooled by the node.
 func (w *Whisper) Envelopes() []*Envelope {
 	w.poolMu.RLock()
 	defer w.poolMu.RUnlock()
@@ -596,15 +711,17 @@ func (w *Whisper) Envelopes() []*Envelope {
 	return all
 }
 
-// Messages retrieves all the decrypted messages matching a filter id.
+// Messages iterates through all currently floating envelopes
+// and retrieves all the messages, that this filter could decrypt.
 func (w *Whisper) Messages(id string) []*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) {
+		for _, env := range w.envelopes {
+			msg := filter.processEnvelope(env)
+			if msg != nil {
 				result = append(result, msg)
 			}
 		}
@@ -612,6 +729,7 @@ func (w *Whisper) Messages(id string) []*ReceivedMessage {
 	return result
 }
 
+// isEnvelopeCached checks if envelope with specific hash has already been received and cached.
 func (w *Whisper) isEnvelopeCached(hash common.Hash) bool {
 	w.poolMu.Lock()
 	defer w.poolMu.Unlock()
@@ -620,22 +738,30 @@ func (w *Whisper) isEnvelopeCached(hash common.Hash) bool {
 	return exist
 }
 
-func (w *Whisper) addDecryptedMessage(msg *ReceivedMessage) {
-	w.poolMu.Lock()
-	defer w.poolMu.Unlock()
-
-	w.messages[msg.EnvelopeHash] = msg
-}
+// reset resets the node's statistics after each expiry cycle.
+func (s *Statistics) reset() {
+	s.cycles++
+	s.totalMessagesCleared += s.messagesCleared
 
-func (s *Statistics) clear() {
 	s.memoryCleared = 0
 	s.messagesCleared = 0
 }
 
+// ValidateKeyID checks the format of key id.
+func ValidateKeyID(id string) error {
+	const target = keyIdSize * 2
+	if len(id) != target {
+		return fmt.Errorf("wrong size of key ID (expected %d bytes, got %d)", target, len(id))
+	}
+	return nil
+}
+
+// ValidatePublicKey checks the format of the given public key.
 func ValidatePublicKey(k *ecdsa.PublicKey) bool {
 	return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
 }
 
+// validatePrivateKey checks the format of the given private key.
 func validatePrivateKey(k *ecdsa.PrivateKey) bool {
 	if k == nil || k.D == nil || k.D.Sign() == 0 {
 		return false
@@ -648,6 +774,7 @@ func validateSymmetricKey(k []byte) bool {
 	return len(k) > 0 && !containsOnlyZeros(k)
 }
 
+// containsOnlyZeros checks if the data contain only zeros.
 func containsOnlyZeros(data []byte) bool {
 	for _, b := range data {
 		if b != 0 {
@@ -657,7 +784,8 @@ func containsOnlyZeros(data []byte) bool {
 	return true
 }
 
-func bytesToIntLittleEndian(b []byte) (res uint64) {
+// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer.
+func bytesToUintLittleEndian(b []byte) (res uint64) {
 	mul := uint64(1)
 	for i := 0; i < len(b); i++ {
 		res += uint64(b[i]) * mul
@@ -666,7 +794,8 @@ func bytesToIntLittleEndian(b []byte) (res uint64) {
 	return res
 }
 
-func BytesToIntBigEndian(b []byte) (res uint64) {
+// BytesToUintBigEndian converts the slice to 64-bit unsigned integer.
+func BytesToUintBigEndian(b []byte) (res uint64) {
 	for i := 0; i < len(b); i++ {
 		res *= 256
 		res += uint64(b[i])
@@ -674,7 +803,7 @@ func BytesToIntBigEndian(b []byte) (res uint64) {
 	return res
 }
 
-// DeriveSymmetricKey derives symmetric key material from the key or password.
+// deriveKeyMaterial 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 {
@@ -686,3 +815,17 @@ func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error
 		return nil, unknownVersionError(version)
 	}
 }
+
+// GenerateRandomID generates a random string, which is then returned to be used as a key id
+func GenerateRandomID() (id string, err error) {
+	buf := make([]byte, keyIdSize)
+	_, err = crand.Read(buf)
+	if err != nil {
+		return "", err
+	}
+	if !validateSymmetricKey(buf) {
+		return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data")
+	}
+	id = common.Bytes2Hex(buf)
+	return id, err
+}
diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go
index 8d63d443cf1e9464f595f7958afa9e5636a3579d..d5668259e5693ef434e7765db2a008a01e42a475 100644
--- a/whisper/whisperv5/whisper_test.go
+++ b/whisper/whisperv5/whisper_test.go
@@ -21,9 +21,6 @@ import (
 	mrand "math/rand"
 	"testing"
 	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
 )
 
 func TestWhisperBasic(t *testing.T) {
@@ -55,16 +52,19 @@ func TestWhisperBasic(t *testing.T) {
 	if peer != nil {
 		t.Fatal("found peer for random key.")
 	}
-	if err := w.MarkPeerTrusted(peerID); err == nil {
+	if err := w.AllowP2PMessagesFromPeer(peerID); err == nil {
 		t.Fatalf("failed MarkPeerTrusted.")
 	}
 	exist := w.HasSymKey("non-existing")
 	if exist {
 		t.Fatalf("failed HasSymKey.")
 	}
-	key := w.GetSymKey("non-existing")
+	key, err := w.GetSymKey("non-existing")
+	if err == nil {
+		t.Fatalf("failed GetSymKey(non-existing): false positive.")
+	}
 	if key != nil {
-		t.Fatalf("failed GetSymKey.")
+		t.Fatalf("failed GetSymKey: false positive.")
 	}
 	mail := w.Envelopes()
 	if len(mail) != 0 {
@@ -80,7 +80,7 @@ func TestWhisperBasic(t *testing.T) {
 	if _, err := deriveKeyMaterial(peerID, ver); err != unknownVersionError(ver) {
 		t.Fatalf("failed deriveKeyMaterial with param = %v: %s.", peerID, err)
 	}
-	derived, err := deriveKeyMaterial(peerID, 0)
+	derived, err = deriveKeyMaterial(peerID, 0)
 	if err != nil {
 		t.Fatalf("failed second deriveKeyMaterial with param = %v: %s.", peerID, err)
 	}
@@ -92,8 +92,8 @@ func TestWhisperBasic(t *testing.T) {
 	}
 
 	buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0}
-	le := bytesToIntLittleEndian(buf)
-	be := BytesToIntBigEndian(buf)
+	le := bytesToUintLittleEndian(buf)
+	be := BytesToUintBigEndian(buf)
 	if le != uint64(0x280e5ff) {
 		t.Fatalf("failed bytesToIntLittleEndian: %d.", le)
 	}
@@ -101,7 +101,14 @@ func TestWhisperBasic(t *testing.T) {
 		t.Fatalf("failed BytesToIntBigEndian: %d.", be)
 	}
 
-	pk := w.NewIdentity()
+	id, err := w.NewKeyPair()
+	if err != nil {
+		t.Fatalf("failed to generate new key pair: %s.", err)
+	}
+	pk, err := w.GetPrivateKey(id)
+	if err != nil {
+		t.Fatalf("failed to retrieve new key pair: %s.", err)
+	}
 	if !validatePrivateKey(pk) {
 		t.Fatalf("failed validatePrivateKey: %v.", pk)
 	}
@@ -112,67 +119,112 @@ func TestWhisperBasic(t *testing.T) {
 
 func TestWhisperIdentityManagement(t *testing.T) {
 	w := New()
-	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) {
-		t.Fatalf("failed HasIdentity(pub1).")
+	id1, err := w.NewKeyPair()
+	if err != nil {
+		t.Fatalf("failed to generate new key pair: %s.", err)
 	}
-	if !w.HasIdentity(pub2) {
-		t.Fatalf("failed HasIdentity(pub2).")
+	id2, err := w.NewKeyPair()
+	if err != nil {
+		t.Fatalf("failed to generate new key pair: %s.", err)
 	}
-	if pk1 != id1 {
-		t.Fatalf("failed GetIdentity(pub1).")
+	pk1, err := w.GetPrivateKey(id1)
+	if err != nil {
+		t.Fatalf("failed to retrieve the key pair: %s.", err)
+	}
+	pk2, err := w.GetPrivateKey(id2)
+	if err != nil {
+		t.Fatalf("failed to retrieve the key pair: %s.", err)
+	}
+
+	if !w.HasKeyPair(id1) {
+		t.Fatalf("failed HasIdentity(pk1).")
+	}
+	if !w.HasKeyPair(id2) {
+		t.Fatalf("failed HasIdentity(pk2).")
+	}
+	if pk1 == nil {
+		t.Fatalf("failed GetIdentity(pk1).")
+	}
+	if pk2 == nil {
+		t.Fatalf("failed GetIdentity(pk2).")
+	}
+
+	if !validatePrivateKey(pk1) {
+		t.Fatalf("pk1 is invalid.")
 	}
-	if pk2 != id2 {
-		t.Fatalf("failed GetIdentity(pub2).")
+	if !validatePrivateKey(pk2) {
+		t.Fatalf("pk2 is invalid.")
 	}
 
 	// Delete one identity
-	w.DeleteIdentity(pub1)
-	pk1 = w.GetIdentity(pub1)
-	pk2 = w.GetIdentity(pub2)
-	if w.HasIdentity(pub1) {
+	done := w.DeleteKeyPair(id1)
+	if !done {
+		t.Fatalf("failed to delete id1.")
+	}
+	pk1, err = w.GetPrivateKey(id1)
+	if err == nil {
+		t.Fatalf("retrieve the key pair: false positive.")
+	}
+	pk2, err = w.GetPrivateKey(id2)
+	if err != nil {
+		t.Fatalf("failed to retrieve the key pair: %s.", err)
+	}
+	if w.HasKeyPair(id1) {
 		t.Fatalf("failed DeleteIdentity(pub1): still exist.")
 	}
-	if !w.HasIdentity(pub2) {
+	if !w.HasKeyPair(id2) {
 		t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.")
 	}
 	if pk1 != nil {
 		t.Fatalf("failed DeleteIdentity(pub1): first key still exist.")
 	}
-	if pk2 != id2 {
+	if pk2 == nil {
 		t.Fatalf("failed DeleteIdentity(pub1): second key does not exist.")
 	}
 
 	// Delete again non-existing identity
-	w.DeleteIdentity(pub1)
-	pk1 = w.GetIdentity(pub1)
-	pk2 = w.GetIdentity(pub2)
-	if w.HasIdentity(pub1) {
+	done = w.DeleteKeyPair(id1)
+	if done {
+		t.Fatalf("delete id1: false positive.")
+	}
+	pk1, err = w.GetPrivateKey(id1)
+	if err == nil {
+		t.Fatalf("retrieve the key pair: false positive.")
+	}
+	pk2, err = w.GetPrivateKey(id2)
+	if err != nil {
+		t.Fatalf("failed to retrieve the key pair: %s.", err)
+	}
+	if w.HasKeyPair(id1) {
 		t.Fatalf("failed delete non-existing identity: exist.")
 	}
-	if !w.HasIdentity(pub2) {
+	if !w.HasKeyPair(id2) {
 		t.Fatalf("failed delete non-existing identity: pub2 does not exist.")
 	}
 	if pk1 != nil {
 		t.Fatalf("failed delete non-existing identity: first key exist.")
 	}
-	if pk2 != id2 {
+	if pk2 == nil {
 		t.Fatalf("failed delete non-existing identity: second key does not exist.")
 	}
 
 	// Delete second identity
-	w.DeleteIdentity(pub2)
-	pk1 = w.GetIdentity(pub1)
-	pk2 = w.GetIdentity(pub2)
-	if w.HasIdentity(pub1) {
+	done = w.DeleteKeyPair(id2)
+	if !done {
+		t.Fatalf("failed to delete id2.")
+	}
+	pk1, err = w.GetPrivateKey(id1)
+	if err == nil {
+		t.Fatalf("retrieve the key pair: false positive.")
+	}
+	pk2, err = w.GetPrivateKey(id2)
+	if err == nil {
+		t.Fatalf("retrieve the key pair: false positive.")
+	}
+	if w.HasKeyPair(id1) {
 		t.Fatalf("failed delete second identity: first identity exist.")
 	}
-	if w.HasIdentity(pub2) {
+	if w.HasKeyPair(id2) {
 		t.Fatalf("failed delete second identity: still exist.")
 	}
 	if pk1 != nil {
@@ -186,23 +238,30 @@ func TestWhisperIdentityManagement(t *testing.T) {
 func TestWhisperSymKeyManagement(t *testing.T) {
 	InitSingleTest()
 
+	var err error
 	var k1, k2 []byte
 	w := New()
 	id1 := string("arbitrary-string-1")
 	id2 := string("arbitrary-string-2")
 
-	err := w.GenerateSymKey(id1)
+	id1, err = w.GenerateSymKey()
 	if err != nil {
 		t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err)
 	}
 
-	k1 = w.GetSymKey(id1)
-	k2 = w.GetSymKey(id2)
+	k1, err = w.GetSymKey(id1)
+	if err != nil {
+		t.Fatalf("failed GetSymKey(id1).")
+	}
+	k2, err = w.GetSymKey(id2)
+	if err == nil {
+		t.Fatalf("failed GetSymKey(id2): false positive.")
+	}
 	if !w.HasSymKey(id1) {
 		t.Fatalf("failed HasSymKey(id1).")
 	}
 	if w.HasSymKey(id2) {
-		t.Fatalf("failed HasSymKey(id2).")
+		t.Fatalf("failed HasSymKey(id2): false positive.")
 	}
 	if k1 == nil {
 		t.Fatalf("first key does not exist.")
@@ -212,37 +271,49 @@ func TestWhisperSymKeyManagement(t *testing.T) {
 	}
 
 	// add existing id, nothing should change
-	randomKey := make([]byte, 16)
+	randomKey := make([]byte, aesKeyLength)
 	mrand.Read(randomKey)
-	err = w.AddSymKey(id1, randomKey)
-	if err == nil {
-		t.Fatalf("failed AddSymKey with seed %d.", seed)
+	id1, err = w.AddSymKeyDirect(randomKey)
+	if err != nil {
+		t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err)
 	}
 
-	k1 = w.GetSymKey(id1)
-	k2 = w.GetSymKey(id2)
+	k1, err = w.GetSymKey(id1)
+	if err != nil {
+		t.Fatalf("failed w.GetSymKey(id1).")
+	}
+	k2, err = w.GetSymKey(id2)
+	if err == nil {
+		t.Fatalf("failed w.GetSymKey(id2): false positive.")
+	}
 	if !w.HasSymKey(id1) {
 		t.Fatalf("failed w.HasSymKey(id1).")
 	}
 	if w.HasSymKey(id2) {
-		t.Fatalf("failed w.HasSymKey(id2).")
+		t.Fatalf("failed w.HasSymKey(id2): false positive.")
 	}
 	if k1 == nil {
 		t.Fatalf("first key does not exist.")
 	}
-	if bytes.Equal(k1, randomKey) {
-		t.Fatalf("k1 == randomKey.")
+	if !bytes.Equal(k1, randomKey) {
+		t.Fatalf("k1 != randomKey.")
 	}
 	if k2 != nil {
 		t.Fatalf("second key already exist.")
 	}
 
-	err = w.AddSymKey(id2, randomKey) // add non-existing (yet)
+	id2, err = w.AddSymKeyDirect(randomKey)
 	if err != nil {
 		t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err)
 	}
-	k1 = w.GetSymKey(id1)
-	k2 = w.GetSymKey(id2)
+	k1, err = w.GetSymKey(id1)
+	if err != nil {
+		t.Fatalf("failed w.GetSymKey(id1).")
+	}
+	k2, err = w.GetSymKey(id2)
+	if err != nil {
+		t.Fatalf("failed w.GetSymKey(id2).")
+	}
 	if !w.HasSymKey(id1) {
 		t.Fatalf("HasSymKey(id1) failed.")
 	}
@@ -255,11 +326,11 @@ func TestWhisperSymKeyManagement(t *testing.T) {
 	if k2 == nil {
 		t.Fatalf("k2 does not exist.")
 	}
-	if bytes.Equal(k1, k2) {
-		t.Fatalf("k1 == k2.")
+	if !bytes.Equal(k1, k2) {
+		t.Fatalf("k1 != k2.")
 	}
-	if bytes.Equal(k1, randomKey) {
-		t.Fatalf("k1 == randomKey.")
+	if !bytes.Equal(k1, randomKey) {
+		t.Fatalf("k1 != randomKey.")
 	}
 	if len(k1) != aesKeyLength {
 		t.Fatalf("wrong length of k1.")
@@ -269,8 +340,17 @@ func TestWhisperSymKeyManagement(t *testing.T) {
 	}
 
 	w.DeleteSymKey(id1)
-	k1 = w.GetSymKey(id1)
-	k2 = w.GetSymKey(id2)
+	k1, err = w.GetSymKey(id1)
+	if err == nil {
+		t.Fatalf("failed w.GetSymKey(id1): false positive.")
+	}
+	if k1 != nil {
+		t.Fatalf("failed GetSymKey(id1): false positive.")
+	}
+	k2, err = w.GetSymKey(id2)
+	if err != nil {
+		t.Fatalf("failed w.GetSymKey(id2).")
+	}
 	if w.HasSymKey(id1) {
 		t.Fatalf("failed to delete first key: still exist.")
 	}
@@ -286,8 +366,17 @@ func TestWhisperSymKeyManagement(t *testing.T) {
 
 	w.DeleteSymKey(id1)
 	w.DeleteSymKey(id2)
-	k1 = w.GetSymKey(id1)
-	k2 = w.GetSymKey(id2)
+	k1, err = w.GetSymKey(id1)
+	if err == nil {
+		t.Fatalf("failed w.GetSymKey(id1): false positive.")
+	}
+	k2, err = w.GetSymKey(id2)
+	if err == nil {
+		t.Fatalf("failed w.GetSymKey(id2): false positive.")
+	}
+	if k1 != nil || k2 != nil {
+		t.Fatalf("k1 or k2 is not nil")
+	}
 	if w.HasSymKey(id1) {
 		t.Fatalf("failed to delete second key: first key exist.")
 	}
@@ -300,13 +389,63 @@ func TestWhisperSymKeyManagement(t *testing.T) {
 	if k2 != nil {
 		t.Fatalf("failed to delete second key: second key is not nil.")
 	}
+
+	randomKey = make([]byte, aesKeyLength+1)
+	mrand.Read(randomKey)
+	id1, err = w.AddSymKeyDirect(randomKey)
+	if err == nil {
+		t.Fatalf("added the key with wrong size, seed %d.", seed)
+	}
+
+	const password = "arbitrary data here"
+	id1, err = w.AddSymKeyFromPassword(password)
+	if err != nil {
+		t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err)
+	}
+	id2, err = w.AddSymKeyFromPassword(password)
+	if err != nil {
+		t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err)
+	}
+	k1, err = w.GetSymKey(id1)
+	if err != nil {
+		t.Fatalf("failed w.GetSymKey(id1).")
+	}
+	k2, err = w.GetSymKey(id2)
+	if err != nil {
+		t.Fatalf("failed w.GetSymKey(id2).")
+	}
+	if !w.HasSymKey(id1) {
+		t.Fatalf("HasSymKey(id1) failed.")
+	}
+	if !w.HasSymKey(id2) {
+		t.Fatalf("HasSymKey(id2) failed.")
+	}
+	if k1 == nil {
+		t.Fatalf("k1 does not exist.")
+	}
+	if k2 == nil {
+		t.Fatalf("k2 does not exist.")
+	}
+	if !bytes.Equal(k1, k2) {
+		t.Fatalf("k1 != k2.")
+	}
+	if len(k1) != aesKeyLength {
+		t.Fatalf("wrong length of k1.")
+	}
+	if len(k2) != aesKeyLength {
+		t.Fatalf("wrong length of k2.")
+	}
+	if !validateSymmetricKey(k2) {
+		t.Fatalf("key validation failed.")
+	}
 }
 
 func TestExpiry(t *testing.T) {
 	InitSingleTest()
 
 	w := New()
-	w.test = true
+	w.SetMinimumPoW(0.0000001)
+	defer w.SetMinimumPoW(DefaultMinimumPoW)
 	w.Start(nil)
 	defer w.Stop()
 
@@ -354,3 +493,87 @@ func TestExpiry(t *testing.T) {
 		t.Fatalf("expire failed, seed: %d.", seed)
 	}
 }
+
+func TestCustomization(t *testing.T) {
+	InitSingleTest()
+
+	w := New()
+	defer w.SetMinimumPoW(DefaultMinimumPoW)
+	defer w.SetMaxMessageLength(DefaultMaxMessageLength)
+	w.Start(nil)
+	defer w.Stop()
+
+	const smallPoW = 0.00001
+
+	f, err := generateFilter(t, true)
+	params, err := generateMessageParams()
+	if err != nil {
+		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
+	}
+
+	params.KeySym = f.KeySym
+	params.Topic = BytesToTopic(f.Topics[2])
+	params.PoW = smallPoW
+	params.TTL = 3600 * 24 // one day
+	msg := NewSentMessage(params)
+	env, err := msg.Wrap(params)
+	if err != nil {
+		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
+	}
+
+	err = w.Send(env)
+	if err == nil {
+		t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed)
+	}
+
+	w.SetMinimumPoW(smallPoW / 2)
+	err = w.Send(env)
+	if err != nil {
+		t.Fatalf("failed to send envelope with seed %d: %s.", seed, err)
+	}
+
+	params.TTL++
+	msg = NewSentMessage(params)
+	env, err = msg.Wrap(params)
+	if err != nil {
+		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
+	}
+	w.SetMaxMessageLength(env.size() - 1)
+	err = w.Send(env)
+	if err == nil {
+		t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed)
+	}
+
+	w.SetMaxMessageLength(DefaultMaxMessageLength)
+	err = w.Send(env)
+	if err != nil {
+		t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err)
+	}
+
+	// wait till received or timeout
+	var received bool
+	for j := 0; j < 20; j++ {
+		time.Sleep(100 * time.Millisecond)
+		if len(w.Envelopes()) > 1 {
+			received = true
+			break
+		}
+	}
+
+	if !received {
+		t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
+	}
+
+	// check w.messages()
+	id, err := w.Subscribe(f)
+	time.Sleep(5 * time.Millisecond)
+	mail := f.Retrieve()
+	if len(mail) > 0 {
+		t.Fatalf("received premature mail")
+	}
+
+	mail = w.Messages(id)
+	if len(mail) != 2 {
+		t.Fatalf("failed to get whisper messages")
+	}
+}