This commit is contained in:
gluk256 2017-03-22 19:44:26 +00:00 committed by GitHub
commit 644cd9d27c
13 changed files with 1312 additions and 641 deletions

View file

@ -27,7 +27,9 @@ import (
"encoding/hex" "encoding/hex"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -46,7 +48,6 @@ import (
) )
const quitCommand = "~Q" const quitCommand = "~Q"
const symKeyName = "da919ea33001b04dfc630522e33078ec0df11"
// singletons // singletons
var ( var (
@ -64,7 +65,8 @@ var (
pub *ecdsa.PublicKey pub *ecdsa.PublicKey
asymKey *ecdsa.PrivateKey asymKey *ecdsa.PrivateKey
nodeid *ecdsa.PrivateKey nodeid *ecdsa.PrivateKey
topic whisper.TopicType topic []byte
asymKeyID string
filterID string filterID string
symPass string symPass string
msPassword string msPassword string
@ -80,12 +82,14 @@ var (
asymmetricMode = flag.Bool("a", false, "use asymmetric encryption") asymmetricMode = flag.Bool("a", false, "use asymmetric encryption")
testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics") testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics")
generateKey = flag.Bool("k", false, "generate and show the private key") generateKey = flag.Bool("k", false, "generate and show the private key")
fileExMode = flag.Bool("x", false, "file exchange mode")
argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level") argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level")
argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
argWorkTime = flag.Uint("work", 5, "work time 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)") argMaxSize = flag.Int("maxsize", whisper.DefaultMaxMessageLength, "max size of message")
argServerPoW = flag.Float64("mspow", whisper.MinimumPoW, "PoW requirement for Mail Server request") 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)") 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") argPub = flag.String("pub", "", "public key for asymmetric encryption")
@ -93,6 +97,7 @@ var (
argIDFile = flag.String("idfile", "", "file name with node id (private key)") 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)") 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)") 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() { func main() {
@ -124,7 +129,7 @@ func processArgs() {
if err != nil { if err != nil {
utils.Fatalf("Failed to parse the topic: %s", err) utils.Fatalf("Failed to parse the topic: %s", err)
} }
topic = whisper.BytesToTopic(x) topic = x
} }
if *asymmetricMode && len(*argPub) > 0 { 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 { if *echoMode {
echo() echo()
} }
@ -199,9 +212,40 @@ func initialize() {
shh = whisper.New() 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 { 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 maxPeers := 80
@ -213,7 +257,8 @@ func initialize() {
Config: p2p.Config{ Config: p2p.Config{
PrivateKey: nodeid, PrivateKey: nodeid,
MaxPeers: maxPeers, MaxPeers: maxPeers,
Name: common.MakeName("whisper-go", "5.0"), Discovery: true,
Name: common.MakeName("wnode", "5.0"),
Protocols: shh.Protocols(), Protocols: shh.Protocols(),
ListenAddr: *argIP, ListenAddr: *argIP,
NAT: nat.Any(), NAT: nat.Any(),
@ -288,8 +333,14 @@ func configureNode() {
} }
} }
shh.AddSymKey(symKeyName, []byte(symPass)) symKeyID, err := shh.AddSymKeyFromPassword(symPass)
symKey = shh.GetSymKey(symKeyName) 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 { if len(*argTopic) == 0 {
generateTopic([]byte(symPass)) generateTopic([]byte(symPass))
} }
@ -304,8 +355,8 @@ func configureNode() {
filter := whisper.Filter{ filter := whisper.Filter{
KeySym: symKey, KeySym: symKey,
KeyAsym: asymKey, KeyAsym: asymKey,
Topics: []whisper.TopicType{topic}, Topics: [][]byte{topic},
AcceptP2P: p2pAccept, AllowP2P: p2pAccept,
} }
filterID, err = shh.Watch(&filter) filterID, err = shh.Watch(&filter)
if err != nil { if err != nil {
@ -351,6 +402,8 @@ func run() {
if *requestMail { if *requestMail {
requestExpiredMessagesLoop() requestExpiredMessagesLoop()
} else if *fileExMode {
sendFilesLoop()
} else { } else {
sendLoop() 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 { func scanLine(prompt string) string {
if len(prompt) > 0 { if len(prompt) > 0 {
fmt.Print(prompt) fmt.Print(prompt)
@ -402,13 +480,13 @@ func scanUint(prompt string) uint32 {
return uint32(i) return uint32(i)
} }
func sendMsg(payload []byte) { func sendMsg(payload []byte) common.Hash {
params := whisper.MessageParams{ params := whisper.MessageParams{
Src: asymKey, Src: asymKey,
Dst: pub, Dst: pub,
KeySym: symKey, KeySym: symKey,
Payload: payload, Payload: payload,
Topic: topic, Topic: whisper.BytesToTopic(topic),
TTL: uint32(*argTTL), TTL: uint32(*argTTL),
PoW: *argPoW, PoW: *argPoW,
WorkTime: uint32(*argWorkTime), WorkTime: uint32(*argWorkTime),
@ -418,13 +496,16 @@ func sendMsg(payload []byte) {
envelope, err := msg.Wrap(&params) envelope, err := msg.Wrap(&params)
if err != nil { if err != nil {
fmt.Printf("failed to seal message: %v \n", err) fmt.Printf("failed to seal message: %v \n", err)
return return common.Hash{}
} }
err = shh.Send(envelope) err = shh.Send(envelope)
if err != nil { if err != nil {
fmt.Printf("failed to send message: %v \n", err) fmt.Printf("failed to send message: %v \n", err)
return common.Hash{}
} }
return envelope.Hash()
} }
func messageLoop() { func messageLoop() {
@ -440,8 +521,12 @@ func messageLoop() {
case <-ticker.C: case <-ticker.C:
messages := f.Retrieve() messages := f.Retrieve()
for _, msg := range messages { for _, msg := range messages {
if *fileExMode || len(msg.Payload) > 2048 {
saveMessageInFile(msg)
} else {
printMessageInfo(msg) printMessageInfo(msg)
} }
}
case <-done: case <-done:
return return
} }
@ -464,19 +549,47 @@ func printMessageInfo(msg *whisper.ReceivedMessage) {
} }
} }
func saveMessageInFile(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(*argSaveDir) > 0 {
fullpath := filepath.Join(*argSaveDir, 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() { func requestExpiredMessagesLoop() {
var key, peerID []byte var key, peerID []byte
var timeLow, timeUpp uint32 var timeLow, timeUpp uint32
var t string var t string
var xt, empty whisper.TopicType var xt, empty whisper.TopicType
err := shh.AddSymKey(mailserver.MailServerKeyName, []byte(msPassword)) keyID, err := shh.AddSymKeyFromPassword(msPassword)
if err != nil { if err != nil {
utils.Fatalf("Failed to create symmetric key for mail request: %s", err) 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) peerID = extractIdFromEnode(*argEnode)
shh.MarkPeerTrusted(peerID) shh.AllowP2PMessagesFromPeer(peerID)
for { for {
timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")

View file

@ -31,8 +31,6 @@ import (
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
) )
const MailServerKeyName = "958e04ab302fb36ad2616a352cbac79d"
type WMailServer struct { type WMailServer struct {
db *leveldb.DB db *leveldb.DB
w *whisper.Whisper w *whisper.Whisper
@ -75,11 +73,14 @@ func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, p
s.w = shh s.w = shh
s.pow = pow s.pow = pow
err = s.w.AddSymKey(MailServerKeyName, []byte(password)) MailServerKeyID, err := s.w.AddSymKeyFromPassword(password)
if err != nil { if err != nil {
utils.Fatalf("Failed to create symmetric key for MailServer: %s", err) 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() { func (s *WMailServer) Close() {

View file

@ -30,8 +30,8 @@ import (
) )
const powRequirement = 0.00001 const powRequirement = 0.00001
const keyName = "6d604bac5401ce9a6b995f1b45a4ab"
var keyID string
var shh *whisper.Whisper var shh *whisper.Whisper
var seed = time.Now().Unix() var seed = time.Now().Unix()
@ -90,7 +90,7 @@ func TestMailServer(t *testing.T) {
server.Init(shh, dir, password, powRequirement) server.Init(shh, dir, password, powRequirement)
defer server.Close() defer server.Close()
err = shh.AddSymKey(keyName, []byte(password)) keyID, err = shh.AddSymKeyFromPassword(password)
if err != nil { if err != nil {
t.Fatalf("Failed to create symmetric key for mail request: %s", err) 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) { 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 birth := env.Expiry - env.TTL
p := &ServerTestParams{ p := &ServerTestParams{
topic: env.Topic, topic: env.Topic,
@ -167,8 +174,13 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
binary.BigEndian.PutUint32(data[4:], p.upp) binary.BigEndian.PutUint32(data[4:], p.upp)
copy(data[8:], p.topic[:]) 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{ params := &whisper.MessageParams{
KeySym: shh.GetSymKey(keyName), KeySym: key,
Topic: p.topic, Topic: p.topic,
Payload: data, Payload: data,
PoW: powRequirement * 2, PoW: powRequirement * 2,

View file

@ -20,12 +20,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
mathrand "math/rand"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "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")
@ -65,20 +65,40 @@ func (api *PublicWhisperAPI) Version() (hexutil.Uint, error) {
} }
// Stats returns the Whisper statistics for diagnostics. // Stats returns the Whisper statistics for diagnostics.
func (api *PublicWhisperAPI) Stats() (string, error) { func (api *PublicWhisperAPI) Info() (string, error) {
if api.whisper == nil { if api.whisper == nil {
return "", whisperOffLineErr return "", whisperOffLineErr
} }
return api.whisper.Stats(), nil return api.whisper.Stats(), nil
} }
// MarkPeerTrusted marks specific peer trusted, which will allow it func (api *PublicWhisperAPI) SetMaxMessageLength(val int) error {
// to send historic (expired) messages.
func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error {
if api.whisper == nil { if api.whisper == nil {
return whisperOffLineErr return whisperOffLineErr
} }
return api.whisper.MarkPeerTrusted(peerID) return api.whisper.SetMaxMessageLength(val)
}
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) AllowP2PMessagesFromPeer(enode string) error {
if api.whisper == nil {
return whisperOffLineErr
}
n, err := discover.ParseNode(enode)
if err != nil {
info := "Failed to parse enode of trusted peer: " + err.Error()
log.Error(info)
return errors.New(info)
}
return api.whisper.AllowP2PMessagesFromPeer(n.ID[:])
} }
// RequestHistoricMessages requests the peer to deliver the old (expired) messages. // RequestHistoricMessages requests the peer to deliver the old (expired) messages.
@ -94,130 +114,179 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error {
// HasIdentity checks if the whisper node is configured with the private key // HasIdentity checks if the whisper node is configured with the private key
// of the specified public pair. // 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 { 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. // DeleteIdentity deletes the specifies key if it exists.
func (api *PublicWhisperAPI) DeleteIdentity(identity string) error { func (api *PublicWhisperAPI) DeleteKeyPair(id string) (bool, error) {
if api.whisper == nil {
return whisperOffLineErr
}
api.whisper.DeleteIdentity(identity)
return nil
}
// NewIdentity generates a new cryptographic identity for the client, and injects
// it into the known identities for message decryption.
func (api *PublicWhisperAPI) NewIdentity() (string, error) {
if api.whisper == nil {
return "", whisperOffLineErr
}
identity := api.whisper.NewIdentity()
return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil
}
// GenerateSymKey generates a random symmetric key and stores it under
// the 'name' id. Will be used in the future for session key exchange.
func (api *PublicWhisperAPI) GenerateSymKey(name string) error {
if api.whisper == nil {
return whisperOffLineErr
}
return api.whisper.GenerateSymKey(name)
}
// AddSymKey stores the key under the 'name' id.
func (api *PublicWhisperAPI) AddSymKey(name string, key hexutil.Bytes) error {
if api.whisper == nil {
return whisperOffLineErr
}
return api.whisper.AddSymKey(name, key)
}
// HasSymKey returns true if there is a key associated with the name string.
// Otherwise returns false.
func (api *PublicWhisperAPI) HasSymKey(name string) (bool, error) {
if api.whisper == nil { if api.whisper == nil {
return false, whisperOffLineErr return false, whisperOffLineErr
} }
res := api.whisper.HasSymKey(name) success := api.whisper.DeleteKeyPair(id)
return success, nil
}
// NewKeyPair generates a new cryptographic identity for the client, and injects
// it into the known identities for message decryption.
func (api *PublicWhisperAPI) NewKeyPair() (string, error) {
if api.whisper == nil {
return "", whisperOffLineErr
}
return api.whisper.NewKeyPair()
}
// GetPublicKey returns the public key for identity id
func (api *PublicWhisperAPI) GetPublicKey(id string) (string, error) {
if api.whisper == nil {
return "", whisperOffLineErr
}
key, err := api.whisper.GetPrivateKey(id)
if err != nil {
return "", err
}
return common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), nil
}
// GetPrivateKey returns the private key for identity id
func (api *PublicWhisperAPI) GetPrivateKey(id string) (string, error) {
if api.whisper == nil {
return "", whisperOffLineErr
}
key, err := api.whisper.GetPrivateKey(id)
if err != nil {
return "", err
}
return common.ToHex(crypto.FromECDSA(key)), nil
}
// 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 (api *PublicWhisperAPI) GenerateSymmetricKey() (string, error) {
if api.whisper == nil {
return "", whisperOffLineErr
}
return api.whisper.GenerateSymKey()
}
// AddSymKeyDirect stores the key, and returns its id.
func (api *PublicWhisperAPI) AddSymmetricKeyDirect(key hexutil.Bytes) (string, error) {
if api.whisper == nil {
return "", whisperOffLineErr
}
return api.whisper.AddSymKeyDirect(key)
}
// AddSymKeyFromPassword 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 api.whisper.AddSymKeyFromPassword(password)
}
// HasSymKey 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
}
res := api.whisper.HasSymKey(id)
return res, nil return res, nil
} }
// DeleteSymKey deletes the key associated with the name string if it exists. func (api *PublicWhisperAPI) GetSymmetricKey(name string) (string, error) {
func (api *PublicWhisperAPI) DeleteSymKey(name string) error {
if api.whisper == nil { if api.whisper == nil {
return whisperOffLineErr return "", whisperOffLineErr
}
api.whisper.DeleteSymKey(name)
return nil
} }
// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages. b, err := api.whisper.GetSymKey(name)
// Returns the ID of the newly created Filter. if err != nil {
func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { return "", err
}
return fmt.Sprintf("%x", b), nil
}
// DeleteSymKey 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
}
// 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 { if api.whisper == nil {
return "", whisperOffLineErr return "", whisperOffLineErr
} }
filter := Filter{ filter := Filter{
Src: crypto.ToECDSAPub(common.FromHex(args.From)), Src: crypto.ToECDSAPub(common.FromHex(args.SignedWith)),
KeySym: api.whisper.GetSymKey(args.KeyName), PoW: args.MinPoW,
PoW: args.PoW,
Messages: make(map[common.Hash]*ReceivedMessage), Messages: make(map[common.Hash]*ReceivedMessage),
AcceptP2P: args.AcceptP2P, AllowP2P: args.AllowP2P,
} }
if len(filter.KeySym) > 0 {
filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
}
filter.Topics = append(filter.Topics, args.Topics...)
if len(args.Topics) == 0 && len(args.KeyName) != 0 { for i, bt := range args.Topics {
info := "NewFilter: at least one topic must be specified" if len(bt) == 0 || len(bt) > 4 {
log.Error(fmt.Sprintf(info)) info := fmt.Sprintf("Subscribe: topic %d has wrong size: %d", i, len(bt))
log.Error(info)
return "", errors.New(info)
}
filter.Topics = append(filter.Topics, bt)
}
err := ValidateKeyID(args.Key)
if err != nil {
info := "Subscribe: " + err.Error()
log.Error(info)
return "", errors.New(info) return "", errors.New(info)
} }
if len(args.KeyName) != 0 && len(filter.KeySym) == 0 { if len(args.SignedWith) > 0 {
info := "NewFilter: key was not found by name: " + args.KeyName
log.Error(fmt.Sprintf(info))
return "", errors.New(info)
}
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)
}
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)
}
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)
}
}
if len(args.From) > 0 {
if !ValidatePublicKey(filter.Src) { if !ValidatePublicKey(filter.Src) {
info := "NewFilter: Invalid 'From' address" info := "Subscribe: Invalid 'SignedWith' field"
log.Error(fmt.Sprintf(info)) log.Error(info)
return "", errors.New(info)
}
}
if args.Symmetric {
if len(args.Topics) == 0 {
info := "Subscribe: at least one topic must be specified with symmetric encryption"
log.Error(info)
return "", errors.New(info)
}
symKey, err := api.whisper.GetSymKey(args.Key)
if err != nil {
info := "Subscribe: invalid key ID"
log.Error(info)
return "", errors.New(info)
}
if !validateSymmetricKey(symKey) {
info := "Subscribe: retrieved key is invalid"
log.Error(info)
return "", errors.New(info)
}
filter.KeySym = symKey
filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
} else {
filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key)
if err != nil {
info := "Subscribe: invalid key ID"
log.Error(info)
return "", errors.New(info)
}
if filter.KeyAsym == nil {
info := "Subscribe: non-existent identity provided"
log.Error(info)
return "", errors.New(info) return "", errors.New(info)
} }
} }
@ -225,13 +294,13 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) {
return api.whisper.Watch(&filter) return api.whisper.Watch(&filter)
} }
// UninstallFilter disables and removes an existing filter. // Unsubscribe disables and removes an existing filter.
func (api *PublicWhisperAPI) UninstallFilter(filterId string) { func (api *PublicWhisperAPI) Unsubscribe(id string) {
api.whisper.Unwatch(filterId) api.whisper.Unsubscribe(id)
} }
// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval. // GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval.
func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage { func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*WhisperMessage {
f := api.whisper.GetFilter(filterId) f := api.whisper.GetFilter(filterId)
if f != nil { if f != nil {
newMail := f.Retrieve() newMail := f.Retrieve()
@ -261,136 +330,126 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error {
return whisperOffLineErr return whisperOffLineErr
} }
var err error
params := MessageParams{ params := MessageParams{
TTL: args.TTL, TTL: args.TTL,
Dst: crypto.ToECDSAPub(common.FromHex(args.To)), WorkTime: args.PowTime,
KeySym: api.whisper.GetSymKey(args.KeyName), PoW: args.PowTarget,
Topic: args.Topic,
Payload: args.Payload, Payload: args.Payload,
Padding: args.Padding, Padding: args.Padding,
WorkTime: args.WorkTime,
PoW: args.PoW,
} }
if len(args.From) > 0 { if len(args.Key) == 0 {
pub := crypto.ToECDSAPub(common.FromHex(args.From)) info := "Post: key is missing"
if !ValidatePublicKey(pub) { log.Error(info)
info := "Post: Invalid 'From' address"
log.Error(fmt.Sprintf(info))
return errors.New(info) return errors.New(info)
} }
params.Src = api.whisper.GetIdentity(string(args.From))
if len(args.SignWith) > 0 {
params.Src, err = api.whisper.GetPrivateKey(args.SignWith)
if err != nil {
log.Error(err.Error())
return err
}
if params.Src == nil { if params.Src == nil {
info := "Post: non-existent identity provided" info := "Post: empty identity"
log.Error(fmt.Sprintf(info)) log.Error(info)
return errors.New(info) return errors.New(info)
} }
} }
filter := api.whisper.GetFilter(args.FilterID) if len(args.Topic) == TopicLength {
if filter == nil && len(args.FilterID) > 0 { params.Topic = BytesToTopic(args.Topic)
info := fmt.Sprintf("Post: wrong filter id %s", args.FilterID) } else if len(args.Topic) != 0 {
log.Error(fmt.Sprintf(info)) info := fmt.Sprintf("Post: wrong topic size %d", len(args.Topic))
log.Error(info)
return errors.New(info) return errors.New(info)
} }
if filter != nil { if args.Type == "sym" {
// get the missing fields from the filter err = ValidateKeyID(args.Key)
if params.KeySym == nil && filter.KeySym != nil { if err != nil {
params.KeySym = filter.KeySym log.Error(err.Error())
return err
} }
if params.Src == nil && filter.Src != nil { params.KeySym, err = api.whisper.GetSymKey(args.Key)
params.Src = filter.KeyAsym if err != nil {
log.Error(err.Error())
return err
} }
if (params.Topic == TopicType{}) { if !validateSymmetricKey(params.KeySym) {
sz := len(filter.Topics) info := "Post: key for symmetric encryption is invalid"
if sz < 1 { log.Error(info)
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]
}
}
}
// 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) return errors.New(info)
} }
if len(params.Topic) == 0 {
if len(args.To) == 0 && len(params.KeySym) == 0 { info := "Post: topic is missing for symmetric encryption"
info := "Post: message must be encrypted either symmetrically or asymmetrically" log.Error(info)
log.Error(fmt.Sprintf(info))
return errors.New(info) return errors.New(info)
} }
} else if args.Type == "asym" {
if len(args.To) != 0 && len(params.KeySym) != 0 { params.Dst = crypto.ToECDSAPub(common.FromHex(args.Key))
info := "Post: ambigous encryption method requested"
log.Error(fmt.Sprintf(info))
return errors.New(info)
}
if len(args.To) > 0 {
if !ValidatePublicKey(params.Dst) { if !ValidatePublicKey(params.Dst) {
info := "Post: Invalid 'To' address" info := "Post: public key for asymmetric encryption is invalid"
log.Error(fmt.Sprintf(info)) log.Error(info)
return errors.New(info) return errors.New(info)
} }
} else {
info := "Post: wrong type (sym/asym)"
log.Error(info)
return errors.New(info)
} }
// encrypt and send // encrypt and send
message := NewSentMessage(&params) message := NewSentMessage(&params)
envelope, err := message.Wrap(&params) envelope, err := message.Wrap(&params)
if err != nil { if err != nil {
log.Error(fmt.Sprintf(err.Error())) log.Error(err.Error())
return err return err
} }
if len(envelope.Data) > MaxMessageLength { if envelope.size() > api.whisper.maxMsgLength {
info := "Post: message is too big" info := "Post: message is too big"
log.Error(fmt.Sprintf(info)) log.Error(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) return errors.New(info)
} }
if args.PeerID != nil { if len(args.TargetPeer) != 0 {
return api.whisper.SendP2PMessage(args.PeerID, envelope) n, err := discover.ParseNode(args.TargetPeer)
if err != nil {
info := "Post: failed to parse enode of target peer: " + err.Error()
log.Error(info)
return errors.New(info)
}
return api.whisper.SendP2PMessage(n.ID[:], envelope)
} else if args.PowTarget < api.whisper.minPoW {
info := "Post: target PoW is less than minimum PoW, the message can not be sent"
log.Error(info)
return errors.New(info)
} }
return api.whisper.Send(envelope) return api.whisper.Send(envelope)
} }
type PostArgs struct { type PostArgs struct {
Type string `json:"type"`
TTL uint32 `json:"ttl"` TTL uint32 `json:"ttl"`
From string `json:"from"` SignWith string `json:"signWith"`
To string `json:"to"` Key string `json:"key"`
KeyName string `json:"keyname"` Topic hexutil.Bytes `json:"topic"`
Topic TopicType `json:"topic"`
Padding hexutil.Bytes `json:"padding"` Padding hexutil.Bytes `json:"padding"`
Payload hexutil.Bytes `json:"payload"` Payload hexutil.Bytes `json:"payload"`
WorkTime uint32 `json:"worktime"` PowTime uint32 `json:"powTime"`
PoW float64 `json:"pow"` PowTarget float64 `json:"powTarget"`
FilterID string `json:"filterID"` TargetPeer string `json:"targetPeer"`
PeerID hexutil.Bytes `json:"peerID"`
} }
type WhisperFilterArgs struct { type WhisperFilterArgs struct {
To string `json:"to"` Symmetric bool
From string `json:"from"` Key string
KeyName string `json:"keyname"` SignedWith string
PoW float64 `json:"pow"` MinPoW float64
Topics []TopicType `json:"topics"` Topics [][]byte
AcceptP2P bool `json:"p2p"` AllowP2P bool
} }
// UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
@ -398,22 +457,29 @@ type WhisperFilterArgs struct {
func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
// Unmarshal the JSON message and sanity check // Unmarshal the JSON message and sanity check
var obj struct { var obj struct {
To string `json:"to"` Type string `json:"type"`
From string `json:"from"` Key string `json:"key"`
KeyName string `json:"keyname"` SignedWith string `json:"signedWith"`
PoW float64 `json:"pow"` MinPoW float64 `json:"minPoW"`
Topics []interface{} `json:"topics"` Topics []interface{} `json:"topics"`
AcceptP2P bool `json:"p2p"` AllowP2P bool `json:"allowP2P"`
} }
if err := json.Unmarshal(b, &obj); err != nil { if err := json.Unmarshal(b, &obj); err != nil {
return err return err
} }
args.To = obj.To if obj.Type == "sym" {
args.From = obj.From args.Symmetric = true
args.KeyName = obj.KeyName } else if obj.Type == "asym" {
args.PoW = obj.PoW args.Symmetric = false
args.AcceptP2P = obj.AcceptP2P } else {
return fmt.Errorf("Wrong type (sym/asym")
}
args.Key = obj.Key
args.SignedWith = obj.SignedWith
args.MinPoW = obj.MinPoW
args.AllowP2P = obj.AllowP2P
// Construct the topic array // Construct the topic array
if obj.Topics != nil { if obj.Topics != nil {
@ -428,13 +494,13 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
return fmt.Errorf("topic[%d] is not a string", i) 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 { for j, s := range topics {
x := common.FromHex(s) x := common.FromHex(s)
if x == nil || len(x) != TopicLength { if x == nil || len(x) > TopicLength {
return fmt.Errorf("topic[%d] is invalid", j) return fmt.Errorf("topic[%d] is invalid", j)
} }
topicsDecoded[j] = BytesToTopic(x) topicsDecoded[j] = x
} }
args.Topics = topicsDecoded args.Topics = topicsDecoded
} }
@ -447,9 +513,9 @@ type WhisperMessage struct {
Topic string `json:"topic"` Topic string `json:"topic"`
Payload string `json:"payload"` Payload string `json:"payload"`
Padding string `json:"padding"` Padding string `json:"padding"`
From string `json:"from"` Src string `json:"signedWith"`
To string `json:"to"` Dst string `json:"receipientPublicKey"`
Sent uint32 `json:"sent"` Timestamp uint32 `json:"timestamp"`
TTL uint32 `json:"ttl"` TTL uint32 `json:"ttl"`
PoW float64 `json:"pow"` PoW float64 `json:"pow"`
Hash string `json:"hash"` Hash string `json:"hash"`
@ -461,17 +527,17 @@ func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage {
Topic: common.ToHex(message.Topic[:]), Topic: common.ToHex(message.Topic[:]),
Payload: common.ToHex(message.Payload), Payload: common.ToHex(message.Payload),
Padding: common.ToHex(message.Padding), Padding: common.ToHex(message.Padding),
Sent: message.Sent, Timestamp: message.Sent,
TTL: message.TTL, TTL: message.TTL,
PoW: message.PoW, PoW: message.PoW,
Hash: common.ToHex(message.EnvelopeHash.Bytes()), Hash: common.ToHex(message.EnvelopeHash.Bytes()),
} }
if message.Dst != nil { 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]) { if isMessageSigned(message.Raw[0]) {
msg.From = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())) msg.Src = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey()))
} }
return &msg return &msg
} }

View file

@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
@ -42,12 +43,12 @@ func TestBasic(t *testing.T) {
t.Fatalf("wrong version: %d.", ver) t.Fatalf("wrong version: %d.", ver)
} }
mail := api.GetFilterChanges("non-existent-id") mail := api.GetSubscriptionMessages("non-existent-id")
if len(mail) != 0 { if len(mail) != 0 {
t.Fatalf("failed GetFilterChanges: premature result") t.Fatalf("failed GetFilterChanges: premature result")
} }
exist, err := api.HasIdentity(id) exist, err := api.HasKeyPair(id)
if err != nil { if err != nil {
t.Fatalf("failed initial HasIdentity: %s.", err) t.Fatalf("failed initial HasIdentity: %s.", err)
} }
@ -55,12 +56,15 @@ func TestBasic(t *testing.T) {
t.Fatalf("failed initial HasIdentity: false positive.") t.Fatalf("failed initial HasIdentity: false positive.")
} }
err = api.DeleteIdentity(id) success, err := api.DeleteKeyPair(id)
if err != nil { if err != nil {
t.Fatalf("failed DeleteIdentity: %s.", err) 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 { if err != nil {
t.Fatalf("failed NewIdentity: %s.", err) t.Fatalf("failed NewIdentity: %s.", err)
} }
@ -68,7 +72,7 @@ func TestBasic(t *testing.T) {
t.Fatalf("failed NewIdentity: empty") t.Fatalf("failed NewIdentity: empty")
} }
exist, err = api.HasIdentity(pub) exist, err = api.HasKeyPair(pub)
if err != nil { if err != nil {
t.Fatalf("failed HasIdentity: %s.", err) t.Fatalf("failed HasIdentity: %s.", err)
} }
@ -76,12 +80,15 @@ func TestBasic(t *testing.T) {
t.Fatalf("failed HasIdentity: false negative.") t.Fatalf("failed HasIdentity: false negative.")
} }
err = api.DeleteIdentity(pub) success, err = api.DeleteKeyPair(pub)
if err != nil { if err != nil {
t.Fatalf("failed to delete second identity: %s.", err) 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 { if err != nil {
t.Fatalf("failed HasIdentity(): %s.", err) t.Fatalf("failed HasIdentity(): %s.", err)
} }
@ -92,7 +99,7 @@ func TestBasic(t *testing.T) {
id = "arbitrary text" id = "arbitrary text"
id2 := "another arbitrary string" id2 := "another arbitrary string"
exist, err = api.HasSymKey(id) exist, err = api.HasSymmetricKey(id)
if err != nil { if err != nil {
t.Fatalf("failed HasSymKey: %s.", err) t.Fatalf("failed HasSymKey: %s.", err)
} }
@ -100,12 +107,12 @@ func TestBasic(t *testing.T) {
t.Fatalf("failed HasSymKey: false positive.") t.Fatalf("failed HasSymKey: false positive.")
} }
err = api.GenerateSymKey(id) id, err = api.GenerateSymmetricKey()
if err != nil { if err != nil {
t.Fatalf("failed GenerateSymKey: %s.", err) t.Fatalf("failed GenerateSymKey: %s.", err)
} }
exist, err = api.HasSymKey(id) exist, err = api.HasSymmetricKey(id)
if err != nil { if err != nil {
t.Fatalf("failed HasSymKey(): %s.", err) t.Fatalf("failed HasSymKey(): %s.", err)
} }
@ -113,17 +120,18 @@ func TestBasic(t *testing.T) {
t.Fatalf("failed HasSymKey(): false negative.") t.Fatalf("failed HasSymKey(): false negative.")
} }
err = api.AddSymKey(id, []byte("some stuff here")) const password = "some stuff here"
if err == nil { id, err = api.AddSymmetricKeyFromPassword(password)
t.Fatalf("failed AddSymKey: %s.", err)
}
err = api.AddSymKey(id2, []byte("some stuff here"))
if err != nil { if err != nil {
t.Fatalf("failed AddSymKey: %s.", err) t.Fatalf("failed AddSymKey: %s.", err)
} }
exist, err = api.HasSymKey(id2) id2, err = api.AddSymmetricKeyFromPassword(password)
if err != nil {
t.Fatalf("failed AddSymKey: %s.", err)
}
exist, err = api.HasSymmetricKey(id2)
if err != nil { if err != nil {
t.Fatalf("failed HasSymKey(id2): %s.", err) t.Fatalf("failed HasSymKey(id2): %s.", err)
} }
@ -131,12 +139,28 @@ func TestBasic(t *testing.T) {
t.Fatalf("failed HasSymKey(id2): false negative.") 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 k1 != k2 {
t.Fatalf("installed keys are not equal")
}
exist, err = api.DeleteSymmetricKey(id)
if err != nil { if err != nil {
t.Fatalf("failed DeleteSymKey(id): %s.", err) 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 { if err != nil {
t.Fatalf("failed HasSymKey(id): %s.", err) t.Fatalf("failed HasSymKey(id): %s.", err)
} }
@ -147,12 +171,12 @@ func TestBasic(t *testing.T) {
func TestUnmarshalFilterArgs(t *testing.T) { func TestUnmarshalFilterArgs(t *testing.T) {
s := []byte(`{ s := []byte(`{
"to":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", "type":"sym",
"from":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "key":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d",
"keyname":"testname", "signedWith":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
"pow":2.34, "minPoW":2.34,
"topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"], "topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"],
"p2p":true "allowP2P":true
}`) }`)
var f WhisperFilterArgs var f WhisperFilterArgs
@ -161,59 +185,58 @@ func TestUnmarshalFilterArgs(t *testing.T) {
t.Fatalf("failed UnmarshalJSON: %s.", err) t.Fatalf("failed UnmarshalJSON: %s.", err)
} }
if f.To != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { if !f.Symmetric {
t.Fatalf("wrong To: %x.", f.To) t.Fatalf("wrong type.")
} }
if f.From != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { if f.Key != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" {
t.Fatalf("wrong From: %x.", f.To) t.Fatalf("wrong key: %s.", f.Key)
} }
if f.KeyName != "testname" { if f.SignedWith != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" {
t.Fatalf("wrong KeyName: %s.", f.KeyName) t.Fatalf("wrong SignedWith: %s.", f.SignedWith)
} }
if f.PoW != 2.34 { if f.MinPoW != 2.34 {
t.Fatalf("wrong pow: %f.", f.PoW) t.Fatalf("wrong MinPoW: %f.", f.MinPoW)
} }
if !f.AcceptP2P { if !f.AllowP2P {
t.Fatalf("wrong AcceptP2P: %v.", f.AcceptP2P) t.Fatalf("wrong AllowP2P.")
} }
if len(f.Topics) != 4 { if len(f.Topics) != 4 {
t.Fatalf("wrong topics number: %d.", len(f.Topics)) t.Fatalf("wrong topics number: %d.", len(f.Topics))
} }
i := 0 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]) t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
} }
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]) t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
} }
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]) t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
} }
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]) t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i])
} }
} }
func TestUnmarshalPostArgs(t *testing.T) { func TestUnmarshalPostArgs(t *testing.T) {
s := []byte(`{ s := []byte(`{
"type":"sym",
"ttl":12345, "ttl":12345,
"from":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", "signWith":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d",
"to":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "key":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83",
"keyname":"shh_test",
"topic":"0xf26e7779", "topic":"0xf26e7779",
"padding":"0x74686973206973206D79207465737420737472696E67", "padding":"0x74686973206973206D79207465737420737472696E67",
"payload":"0x7061796C6F61642073686F756C642062652070736575646F72616E646F6D", "payload":"0x7061796C6F61642073686F756C642062652070736575646F72616E646F6D",
"worktime":777, "powTime":777,
"pow":3.1416, "powTarget":3.1416,
"filterid":"test-filter-id", "targetPeer":"enode://915533f667b1369793ebb9bda022416b1295235a1420799cd87a969467372546d808ebf59c5c9ce23f103d59b61b97df8af91f0908552485975397181b993461@127.0.0.1:12345"
"peerid":"0xf26e7779"
}`) }`)
var a PostArgs var a PostArgs
@ -222,19 +245,20 @@ func TestUnmarshalPostArgs(t *testing.T) {
t.Fatalf("failed UnmarshalJSON: %s.", err) t.Fatalf("failed UnmarshalJSON: %s.", err)
} }
if a.Type != "sym" {
t.Fatalf("wrong Type: %s.", a.Type)
}
if a.TTL != 12345 { if a.TTL != 12345 {
t.Fatalf("wrong ttl: %d.", a.TTL) t.Fatalf("wrong ttl: %d.", a.TTL)
} }
if a.From != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { if a.SignWith != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" {
t.Fatalf("wrong From: %x.", a.To) t.Fatalf("wrong From: %s.", a.SignWith)
} }
if a.To != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { if a.Key != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" {
t.Fatalf("wrong To: %x.", a.To) t.Fatalf("wrong Key: %s.", a.Key)
} }
if a.KeyName != "shh_test" {
t.Fatalf("wrong KeyName: %s.", a.KeyName) if BytesToTopic(a.Topic) != (TopicType{0xf2, 0x6e, 0x77, 0x79}) {
}
if a.Topic != (TopicType{0xf2, 0x6e, 0x77, 0x79}) {
t.Fatalf("wrong topic: %x.", a.Topic) t.Fatalf("wrong topic: %x.", a.Topic)
} }
if string(a.Padding) != "this is my test string" { 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" { if string(a.Payload) != "payload should be pseudorandom" {
t.Fatalf("wrong Payload: %s.", string(a.Payload)) t.Fatalf("wrong Payload: %s.", string(a.Payload))
} }
if a.WorkTime != 777 { if a.PowTime != 777 {
t.Fatalf("wrong WorkTime: %d.", a.WorkTime) t.Fatalf("wrong PowTime: %d.", a.PowTime)
} }
if a.PoW != 3.1416 { if a.PowTarget != 3.1416 {
t.Fatalf("wrong pow: %f.", a.PoW) t.Fatalf("wrong PowTarget: %f.", a.PowTarget)
} }
if a.FilterID != "test-filter-id" { if a.TargetPeer != "enode://915533f667b1369793ebb9bda022416b1295235a1420799cd87a969467372546d808ebf59c5c9ce23f103d59b61b97df8af91f0908552485975397181b993461@127.0.0.1:12345" {
t.Fatalf("wrong FilterID: %s.", a.FilterID) t.Fatalf("wrong PeerID: %s.", a.TargetPeer)
}
if !bytes.Equal(a.PeerID[:], a.Topic[:]) {
t.Fatalf("wrong PeerID: %x.", a.PeerID)
} }
} }
func waitForMessage(api *PublicWhisperAPI, id string, target int) bool { func waitForMessages(api *PublicWhisperAPI, id string, target int) []*WhisperMessage {
for i := 0; i < 64; i++ { // timeout: 2 seconds
all := api.GetMessages(id) result := make([]*WhisperMessage, 0, target)
if len(all) >= target { for i := 0; i < 100; i++ {
return true mail := api.GetSubscriptionMessages(id)
if len(mail) > 0 {
for _, m := range mail {
result = append(result, m)
} }
time.Sleep(time.Millisecond * 16) if len(result) >= target {
break
}
}
time.Sleep(time.Millisecond * 20)
} }
// timeout 1024 milliseconds return result
return false
} }
func TestIntegrationAsym(t *testing.T) { func TestIntegrationAsym(t *testing.T) {
@ -280,7 +307,7 @@ func TestIntegrationAsym(t *testing.T) {
api.Start() api.Start()
defer api.Stop() defer api.Stop()
sig, err := api.NewIdentity() sig, err := api.NewKeyPair()
if err != nil { if err != nil {
t.Fatalf("failed NewIdentity: %s.", err) t.Fatalf("failed NewIdentity: %s.", err)
} }
@ -288,7 +315,7 @@ func TestIntegrationAsym(t *testing.T) {
t.Fatalf("wrong signature") t.Fatalf("wrong signature")
} }
exist, err := api.HasIdentity(sig) exist, err := api.HasKeyPair(sig)
if err != nil { if err != nil {
t.Fatalf("failed HasIdentity: %s.", err) t.Fatalf("failed HasIdentity: %s.", err)
} }
@ -296,7 +323,12 @@ func TestIntegrationAsym(t *testing.T) {
t.Fatalf("failed HasIdentity: false negative.") 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 { if err != nil {
t.Fatalf("failed NewIdentity(): %s.", err) t.Fatalf("failed NewIdentity(): %s.", err)
} }
@ -304,42 +336,46 @@ func TestIntegrationAsym(t *testing.T) {
t.Fatalf("wrong key") t.Fatalf("wrong key")
} }
dstPubKey, err := api.GetPublicKey(key)
if err != nil {
t.Fatalf("failed GetPublicKey: %s.", err)
}
var topics [2]TopicType var topics [2]TopicType
topics[0] = TopicType{0x00, 0x64, 0x00, 0xff} topics[0] = TopicType{0x00, 0x64, 0x00, 0xff}
topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79}
var f WhisperFilterArgs var f WhisperFilterArgs
f.To = key f.Symmetric = false
f.From = sig f.Key = key
f.Topics = topics[:] f.SignedWith = sigPubKey
f.PoW = MinimumPoW / 2 f.Topics = make([][]byte, 2)
f.AcceptP2P = true 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 { if err != nil {
t.Fatalf("failed to create new filter: %s.", err) t.Fatalf("failed to create new filter: %s.", err)
} }
var p PostArgs var p PostArgs
p.Type = "asym"
p.TTL = 2 p.TTL = 2
p.From = f.From p.SignWith = sig
p.To = f.To p.Key = dstPubKey
p.Padding = []byte("test string") p.Padding = []byte("test string")
p.Payload = []byte("extended test string") p.Payload = []byte("extended test string")
p.PoW = MinimumPoW p.PowTarget = DefaultMinimumPoW
p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} p.PowTime = 2
p.WorkTime = 2 p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79} // topics[1]
err = api.Post(p) err = api.Post(p)
if err != nil { if err != nil {
t.Errorf("failed to post message: %s.", err) t.Errorf("failed to post message: %s.", err)
} }
ok := waitForMessage(api, id, 1) mail := waitForMessages(api, id, 1)
if !ok {
t.Fatalf("failed to receive first message: timeout.")
}
mail := api.GetFilterChanges(id)
if len(mail) != 1 { if len(mail) != 1 {
t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) 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) t.Fatalf("failed to post next message: %s.", err)
} }
ok = waitForMessage(api, id, 2) mail = waitForMessages(api, id, 1)
if !ok {
t.Fatalf("failed to receive second message: timeout.")
}
mail = api.GetFilterChanges(id)
if len(mail) != 1 { if len(mail) != 1 {
t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail))
} }
@ -382,21 +413,25 @@ func TestIntegrationSym(t *testing.T) {
api.Start() api.Start()
defer api.Stop() defer api.Stop()
keyname := "schluessel" symKeyID, err := api.GenerateSymmetricKey()
err := api.GenerateSymKey(keyname)
if err != nil { if err != nil {
t.Fatalf("failed GenerateSymKey: %s.", err) t.Fatalf("failed GenerateSymKey: %s.", err)
} }
sig, err := api.NewIdentity() sig, err := api.NewKeyPair()
if err != nil { if err != nil {
t.Fatalf("failed NewIdentity: %s.", err) t.Fatalf("failed NewKeyPair: %s.", err)
} }
if len(sig) == 0 { if len(sig) == 0 {
t.Fatalf("wrong signature") 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 { if err != nil {
t.Fatalf("failed HasIdentity: %s.", err) t.Fatalf("failed HasIdentity: %s.", err)
} }
@ -408,38 +443,37 @@ func TestIntegrationSym(t *testing.T) {
topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff} topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff}
topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79}
var f WhisperFilterArgs var f WhisperFilterArgs
f.KeyName = keyname f.Symmetric = true
f.Topics = topics[:] f.Key = symKeyID
f.PoW = 0.324 f.Topics = make([][]byte, 2)
f.From = sig f.Topics[0] = topics[0][:]
f.AcceptP2P = false f.Topics[1] = topics[1][:]
f.MinPoW = 0.324
f.SignedWith = sigPubKey
f.AllowP2P = false
id, err := api.NewFilter(f) id, err := api.Subscribe(f)
if err != nil { if err != nil {
t.Fatalf("failed to create new filter: %s.", err) t.Fatalf("failed to create new filter: %s.", err)
} }
var p PostArgs var p PostArgs
p.Type = "sym"
p.TTL = 1 p.TTL = 1
p.KeyName = keyname p.Key = symKeyID
p.From = f.From p.SignWith = sig
p.Padding = []byte("test string") p.Padding = []byte("test string")
p.Payload = []byte("extended test string") p.Payload = []byte("extended test string")
p.PoW = MinimumPoW p.PowTarget = DefaultMinimumPoW
p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} p.PowTime = 2
p.WorkTime = 2 p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79}
err = api.Post(p) err = api.Post(p)
if err != nil { if err != nil {
t.Fatalf("failed to post first message: %s.", err) t.Fatalf("failed to post first message: %s.", err)
} }
ok := waitForMessage(api, id, 1) mail := waitForMessages(api, id, 1)
if !ok {
t.Fatalf("failed to receive first message: timeout.")
}
mail := api.GetFilterChanges(id)
if len(mail) != 1 { if len(mail) != 1 {
t.Fatalf("failed GetFilterChanges: got %d messages.", len(mail)) 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) t.Fatalf("failed to post second message: %s.", err)
} }
ok = waitForMessage(api, id, 2) mail = waitForMessages(api, id, 1)
if !ok {
t.Fatalf("failed to receive second message: timeout.")
}
mail = api.GetFilterChanges(id)
if len(mail) != 1 { if len(mail) != 1 {
t.Fatalf("failed second GetFilterChanges: got %d messages.", len(mail)) t.Fatalf("failed second GetFilterChanges: got %d messages.", len(mail))
} }
@ -482,21 +511,20 @@ func TestIntegrationSymWithFilter(t *testing.T) {
api.Start() api.Start()
defer api.Stop() defer api.Stop()
keyname := "schluessel" symKeyID, err := api.GenerateSymmetricKey()
err := api.GenerateSymKey(keyname)
if err != nil { if err != nil {
t.Fatalf("failed to GenerateSymKey: %s.", err) t.Fatalf("failed to GenerateSymKey: %s.", err)
} }
sig, err := api.NewIdentity() sigKeyID, err := api.NewKeyPair()
if err != nil { if err != nil {
t.Fatalf("failed NewIdentity: %s.", err) t.Fatalf("failed NewIdentity: %s.", err)
} }
if len(sig) == 0 { if len(sigKeyID) == 0 {
t.Fatalf("wrong signature.") t.Fatalf("wrong signature.")
} }
exist, err := api.HasIdentity(sig) exist, err := api.HasKeyPair(sigKeyID)
if err != nil { if err != nil {
t.Fatalf("failed HasIdentity: %s.", err) t.Fatalf("failed HasIdentity: %s.", err)
} }
@ -504,42 +532,46 @@ func TestIntegrationSymWithFilter(t *testing.T) {
t.Fatalf("failed HasIdentity: does not exist.") 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 var topics [2]TopicType
topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff} topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff}
topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79}
var f WhisperFilterArgs var f WhisperFilterArgs
f.KeyName = keyname f.Symmetric = true
f.Topics = topics[:] f.Key = symKeyID
f.PoW = 0.324 f.Topics = make([][]byte, 2)
f.From = sig f.Topics[0] = topics[0][:]
f.AcceptP2P = false f.Topics[1] = topics[1][:]
f.MinPoW = 0.324
f.SignedWith = sigPubKey
f.AllowP2P = false
id, err := api.NewFilter(f) id, err := api.Subscribe(f)
if err != nil { if err != nil {
t.Fatalf("failed to create new filter: %s.", err) t.Fatalf("failed to create new filter: %s.", err)
} }
var p PostArgs var p PostArgs
p.Type = "sym"
p.TTL = 1 p.TTL = 1
p.FilterID = id p.Key = symKeyID
p.From = sig p.SignWith = sigKeyID
p.Padding = []byte("test string") p.Padding = []byte("test string")
p.Payload = []byte("extended test string") p.Payload = []byte("extended test string")
p.PoW = MinimumPoW p.PowTarget = DefaultMinimumPoW
p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} p.PowTime = 2
p.WorkTime = 2 p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79}
err = api.Post(p) err = api.Post(p)
if err != nil { if err != nil {
t.Fatalf("failed to post message: %s.", err) t.Fatalf("failed to post message: %s.", err)
} }
ok := waitForMessage(api, id, 1) mail := waitForMessages(api, id, 1)
if !ok {
t.Fatalf("failed to receive first message: timeout.")
}
mail := api.GetFilterChanges(id)
if len(mail) != 1 { if len(mail) != 1 {
t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) 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) t.Fatalf("failed to post next message: %s.", err)
} }
ok = waitForMessage(api, id, 2) mail = waitForMessages(api, id, 1)
if !ok {
t.Fatalf("failed to receive second message: timeout.")
}
mail = api.GetFilterChanges(id)
if len(mail) != 1 { if len(mail) != 1 {
t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail))
} }
@ -571,3 +598,84 @@ func TestIntegrationSymWithFilter(t *testing.T) {
t.Fatalf("failed to decrypt second message: %s.", text) 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)
}
b := common.FromHex(s)
k2, err := api.AddSymmetricKeyDirect(b)
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 != "448652d595bd6ec00b2a9ea220ad6c26592d9bf4cf79023d3c1b30cb681e6e07" {
t.Fatalf("wrong key from password")
}
if 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)
}
}

View file

@ -54,9 +54,10 @@ const (
aesKeyLength = 32 aesKeyLength = 32
saltLength = 12 saltLength = 12
AESNonceMaxLength = 12 AESNonceMaxLength = 12
keyIdSize = 32
MaxMessageLength = 0x0FFFFF // todo: remove this restriction after testing. this should be regulated by PoW. DefaultMaxMessageLength = 1024 * 1024
MinimumPoW = 10.0 // todo: review after testing. DefaultMinimumPoW = 1.0 // todo: review after testing.
padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature 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 padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility

View file

@ -21,7 +21,6 @@ package whisperv5
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
gmath "math" gmath "math"
"math/big" "math/big"
@ -95,6 +94,9 @@ func (e *Envelope) Seal(options *MessageParams) error {
e.Expiry += options.WorkTime e.Expiry += options.WorkTime
} else { } else {
target = e.powToFirstBit(options.PoW) target = e.powToFirstBit(options.PoW)
if target < 1 {
target = 1
}
} }
buf := make([]byte, 64) buf := make([]byte, 64)
@ -118,7 +120,7 @@ func (e *Envelope) Seal(options *MessageParams) error {
} }
if target > 0 && bestBit < target { 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 return nil

View file

@ -18,7 +18,6 @@ package whisperv5
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
crand "crypto/rand"
"fmt" "fmt"
"sync" "sync"
@ -30,9 +29,9 @@ type Filter struct {
Src *ecdsa.PublicKey // Sender of the message Src *ecdsa.PublicKey // Sender of the message
KeyAsym *ecdsa.PrivateKey // Private Key of recipient KeyAsym *ecdsa.PrivateKey // Private Key of recipient
KeySym []byte // Key associated with the Topic 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 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 SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
Messages map[common.Hash]*ReceivedMessage 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) { func (fs *Filters) Install(watcher *Filter) (string, error) {
if watcher.Messages == nil { if watcher.Messages == nil {
watcher.Messages = make(map[common.Hash]*ReceivedMessage) watcher.Messages = make(map[common.Hash]*ReceivedMessage)
} }
id, err := GenerateRandomID()
if err != nil {
return "", err
}
fs.mutex.Lock() fs.mutex.Lock()
defer fs.mutex.Unlock() defer fs.mutex.Unlock()
id, err := fs.generateRandomID() if fs.watchers[id] != nil {
if err == nil { return "", fmt.Errorf("failed to generate unique ID")
fs.watchers[id] = watcher
} }
fs.watchers[id] = watcher
return id, err return id, err
} }
func (fs *Filters) Uninstall(id string) { func (fs *Filters) Uninstall(id string) bool {
fs.mutex.Lock() fs.mutex.Lock()
defer fs.mutex.Unlock() defer fs.mutex.Unlock()
if fs.watchers[id] != nil {
delete(fs.watchers, id) delete(fs.watchers, id)
return true
}
return false
} }
func (fs *Filters) Get(id string) *Filter { 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) { func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
fs.mutex.RLock()
var msg *ReceivedMessage var msg *ReceivedMessage
for j, watcher := range fs.watchers {
if p2pMessage && !watcher.AcceptP2P { fs.mutex.RLock()
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), j)) defer fs.mutex.RUnlock()
j := -1
for _, watcher := range fs.watchers {
j++
if p2pMessage && !watcher.AllowP2P {
log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), j))
continue continue
} }
@ -118,22 +110,32 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
if match { if match {
msg = env.Open(watcher) msg = env.Open(watcher)
if msg == nil { if msg == nil {
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: failed to open", env.Hash(), j)) log.Trace(fmt.Sprintf("msg [%x], filter [%d]: failed to open", env.Hash(), j))
} }
} else { } else {
log.Trace(fmt.Sprintf("msg [%x], filter [%s]: does not match", env.Hash(), j)) log.Trace(fmt.Sprintf("msg [%x], filter [%d]: does not match", env.Hash(), j))
} }
} }
if match && msg != nil { if match && msg != nil {
log.Trace(fmt.Sprintf("message decrypted [%x]", env.Hash()))
watcher.Trigger(msg) 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(fmt.Sprintf("processing msg [%x]: failed to open", env.Hash()))
}
} else {
log.Trace(fmt.Sprintf("processing msg [%x]: does not match", env.Hash()))
}
return nil
} }
func (f *Filter) expectsAsymmetricEncryption() bool { func (f *Filter) expectsAsymmetricEncryption() bool {
@ -200,14 +202,27 @@ func (f *Filter) MatchTopic(topic TopicType) bool {
return true return true
} }
for _, t := range f.Topics { for _, bt := range f.Topics {
if t == topic { if MatchSingleTopic(topic, bt) {
return true return true
} }
} }
return false return false
} }
func MatchSingleTopic(topic TopicType, bt []byte) bool {
if len(bt) > 4 {
bt = bt[0:4]
}
for j, b := range bt {
if topic[j] != b {
return false
}
}
return true
}
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
if !ValidatePublicKey(a) { if !ValidatePublicKey(a) {
return false return false

View file

@ -53,8 +53,9 @@ func generateFilter(t *testing.T, symmetric bool) (*Filter, error) {
f.Messages = make(map[common.Hash]*ReceivedMessage) f.Messages = make(map[common.Hash]*ReceivedMessage)
const topicNum = 8 const topicNum = 8
f.Topics = make([]TopicType, topicNum) f.Topics = make([][]byte, topicNum)
for i := 0; i < topicNum; i++ { for i := 0; i < topicNum; i++ {
f.Topics[i] = make([]byte, 4)
randomize(f.Topics[i][:]) randomize(f.Topics[i][:])
f.Topics[i][0] = 0x01 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) t.Fatalf("seed %d: failed to install filter: %s", seed, err)
} }
tst[i].id = j 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)) t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j))
} }
} }
@ -194,8 +195,8 @@ func TestMatchEnvelope(t *testing.T) {
// encrypt symmetrically // encrypt symmetrically
i := rand.Int() % 4 i := rand.Int() % 4
fsym.Topics[i] = params.Topic fsym.Topics[i] = params.Topic[:]
fasym.Topics[i] = params.Topic fasym.Topics[i] = params.Topic[:]
msg = NewSentMessage(params) msg = NewSentMessage(params)
env, err = msg.Wrap(params) env, err = msg.Wrap(params)
if err != nil { if err != nil {
@ -320,7 +321,7 @@ func TestMatchMessageSym(t *testing.T) {
const index = 1 const index = 1
params.KeySym = f.KeySym params.KeySym = f.KeySym
params.Topic = f.Topics[index] params.Topic = BytesToTopic(f.Topics[index])
sentMessage := NewSentMessage(params) sentMessage := NewSentMessage(params)
env, err := sentMessage.Wrap(params) env, err := sentMessage.Wrap(params)
@ -413,7 +414,7 @@ func TestMatchMessageAsym(t *testing.T) {
} }
const index = 1 const index = 1
params.Topic = f.Topics[index] params.Topic = BytesToTopic(f.Topics[index])
params.Dst = &f.KeyAsym.PublicKey params.Dst = &f.KeyAsym.PublicKey
keySymOrig := params.KeySym keySymOrig := params.KeySym
params.KeySym = nil params.KeySym = nil
@ -491,7 +492,7 @@ func cloneFilter(orig *Filter) *Filter {
clone.KeySym = orig.KeySym clone.KeySym = orig.KeySym
clone.Topics = orig.Topics clone.Topics = orig.Topics
clone.PoW = orig.PoW clone.PoW = orig.PoW
clone.AcceptP2P = orig.AcceptP2P clone.AllowP2P = orig.AllowP2P
clone.SymKeyHash = orig.SymKeyHash clone.SymKeyHash = orig.SymKeyHash
return &clone return &clone
} }
@ -504,7 +505,7 @@ func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope {
} }
params.KeySym = f.KeySym params.KeySym = f.KeySym
params.Topic = f.Topics[2] params.Topic = BytesToTopic(f.Topics[2])
sentMessage := NewSentMessage(params) sentMessage := NewSentMessage(params)
env, err := sentMessage.Wrap(params) env, err := sentMessage.Wrap(params)
if err != nil { if err != nil {
@ -655,7 +656,7 @@ func TestWatchers(t *testing.T) {
if f == nil { if f == nil {
t.Fatalf("failed to get the filter with seed %d.", seed) t.Fatalf("failed to get the filter with seed %d.", seed)
} }
f.AcceptP2P = true f.AllowP2P = true
total = 0 total = 0
filters.NotifyWatchers(envelopes[0], true) 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) 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[0: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)
}
}
}

View file

@ -215,17 +215,6 @@ func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte,
} }
// Wrap bundles the message into an Envelope to transmit over the network. // 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) { func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) {
if options.TTL == 0 { if options.TTL == 0 {
options.TTL = DefaultTTL options.TTL = DefaultTTL
@ -236,10 +225,6 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
return nil, err 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 var salt, nonce []byte
if options.Dst != nil { if options.Dst != nil {
err = msg.encryptAsymmetric(options.Dst) err = msg.encryptAsymmetric(options.Dst)
@ -258,7 +243,6 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er
if err != nil { if err != nil {
return nil, err return nil, err
} }
return envelope, nil return envelope, nil
} }
@ -280,7 +264,7 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []by
} }
if len(nonce) != aesgcm.NonceSize() { if len(nonce) != aesgcm.NonceSize() {
info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize()) info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize())
log.Error(fmt.Sprintf(info)) log.Error(info)
return errors.New(info) return errors.New(info)
} }
decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil) decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil)

View file

@ -114,11 +114,13 @@ func initialize(t *testing.T) {
for i := 0; i < NumNodes; i++ { for i := 0; i < NumNodes; i++ {
var node TestNode var node TestNode
node.shh = New() node.shh = New()
node.shh.test = true node.shh.SetMinimumPoW(0.00000001)
node.shh.Start(nil) node.shh.Start(nil)
topics := make([]TopicType, 0) topics := make([]TopicType, 0)
topics = append(topics, sharedTopic) topics = append(topics, sharedTopic)
f := Filter{KeySym: sharedKey, Topics: topics} f := Filter{KeySym: sharedKey}
f.Topics = make([][]byte, 1)
f.Topics[0] = topics[0][:]
node.filerId, err = node.shh.Watch(&f) node.filerId, err = node.shh.Watch(&f)
if err != nil { if err != nil {
t.Fatalf("failed to install the filter: %s.", err) t.Fatalf("failed to install the filter: %s.", err)
@ -166,7 +168,7 @@ func stopServers() {
for i := 0; i < NumNodes; i++ { for i := 0; i < NumNodes; i++ {
n := nodes[i] n := nodes[i]
if n != nil { if n != nil {
n.shh.Unwatch(n.filerId) n.shh.Unsubscribe(n.filerId)
n.shh.Stop() n.shh.Stop()
n.server.Stop() n.server.Stop()
} }
@ -257,7 +259,7 @@ func sendMsg(t *testing.T, expected bool, id int) {
return 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 { if !expected {
opt.KeySym[0]++ opt.KeySym[0]++
opt.Topic[0]++ opt.Topic[0]++
@ -267,12 +269,12 @@ func sendMsg(t *testing.T, expected bool, id int) {
msg := NewSentMessage(&opt) msg := NewSentMessage(&opt)
envelope, err := msg.Wrap(&opt) envelope, err := msg.Wrap(&opt)
if err != nil { if err != nil {
t.Fatalf("failed to seal message.") t.Fatalf("failed to seal message: %s", err)
} }
err = nodes[id].shh.Send(envelope) err = nodes[id].shh.Send(envelope)
if err != nil { if err != nil {
t.Fatalf("failed to send message.") t.Fatalf("failed to send message: %s", err)
} }
} }

View file

@ -38,7 +38,9 @@ import (
type Statistics struct { type Statistics struct {
messagesCleared int messagesCleared int
memoryCleared int memoryCleared int
totalMemoryUsed int memoryUsed int
cycles int
totalMessagesCleared int
} }
// Whisper represents a dark communication interface through the Ethereum // Whisper represents a dark communication interface through the Ethereum
@ -52,7 +54,6 @@ type Whisper struct {
keyMu sync.RWMutex keyMu sync.RWMutex
envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node 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 expirations map[uint32]*set.SetNonTS // Message expiration pool
poolMu sync.RWMutex // Mutex to sync the message and expiration pools poolMu sync.RWMutex // Mutex to sync the message and expiration pools
@ -67,8 +68,9 @@ type Whisper struct {
stats Statistics stats Statistics
minPoW float64
maxMsgLength int
overflow bool overflow bool
test bool
} }
// New creates a Whisper client ready to communicate through the Ethereum P2P network. // New creates a Whisper client ready to communicate through the Ethereum P2P network.
@ -78,12 +80,13 @@ func New() *Whisper {
privateKeys: make(map[string]*ecdsa.PrivateKey), privateKeys: make(map[string]*ecdsa.PrivateKey),
symKeys: make(map[string][]byte), symKeys: make(map[string][]byte),
envelopes: make(map[common.Hash]*Envelope), envelopes: make(map[common.Hash]*Envelope),
messages: make(map[common.Hash]*ReceivedMessage),
expirations: make(map[uint32]*set.SetNonTS), expirations: make(map[uint32]*set.SetNonTS),
peers: make(map[*Peer]struct{}), peers: make(map[*Peer]struct{}),
messageQueue: make(chan *Envelope, messageQueueLimit), messageQueue: make(chan *Envelope, messageQueueLimit),
p2pMsgQueue: make(chan *Envelope, messageQueueLimit), p2pMsgQueue: make(chan *Envelope, messageQueueLimit),
quit: make(chan struct{}), quit: make(chan struct{}),
minPoW: DefaultMinimumPoW,
maxMsgLength: DefaultMaxMessageLength,
} }
whisper.filters = NewFilters(whisper) whisper.filters = NewFilters(whisper)
@ -124,6 +127,22 @@ func (w *Whisper) Version() uint {
return w.protocol.Version return w.protocol.Version
} }
func (w *Whisper) SetMaxMessageLength(val int) error {
if val <= 0 {
return fmt.Errorf("Invalid message length: %d", val)
}
w.maxMsgLength = val
return nil
}
func (w *Whisper) SetMinimumPoW(val float64) error {
if val <= 0.0 {
return fmt.Errorf("Invalid PoW: %f", val)
}
w.minPoW = val
return nil
}
func (w *Whisper) getPeer(peerID []byte) (*Peer, error) { func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
w.peerMu.Lock() w.peerMu.Lock()
defer w.peerMu.Unlock() defer w.peerMu.Unlock()
@ -138,7 +157,7 @@ func (w *Whisper) getPeer(peerID []byte) (*Peer, error) {
// MarkPeerTrusted marks specific peer trusted, which will allow it // MarkPeerTrusted marks specific peer trusted, which will allow it
// to send historic (expired) messages. // to send historic (expired) messages.
func (w *Whisper) MarkPeerTrusted(peerID []byte) error { func (w *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error {
p, err := w.getPeer(peerID) p, err := w.getPeer(peerID)
if err != nil { if err != nil {
return err return err
@ -169,112 +188,167 @@ func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error {
} }
// NewIdentity generates a new cryptographic identity for the client, and injects // NewIdentity generates a new cryptographic identity for the client, and injects
// it into the known identities for message decryption. // it into the known identities for message decryption. Returns ID of the new key pair.
func (w *Whisper) NewIdentity() *ecdsa.PrivateKey { func (w *Whisper) NewKeyPair() (string, error) {
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil || !validatePrivateKey(key) { if err != nil || !validatePrivateKey(key) {
key, err = crypto.GenerateKey() // retry once key, err = crypto.GenerateKey() // retry once
} }
if err != nil { if err != nil {
panic(err) return "", err
} }
if !validatePrivateKey(key) { 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() w.keyMu.Lock()
defer w.keyMu.Unlock() 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. // DeleteIdentity deletes the specified key if it exists.
func (w *Whisper) DeleteIdentity(key string) { func (w *Whisper) DeleteKeyPair(key string) bool {
w.keyMu.Lock() w.keyMu.Lock()
defer w.keyMu.Unlock() defer w.keyMu.Unlock()
if w.privateKeys[key] != nil {
delete(w.privateKeys, key) delete(w.privateKeys, key)
return true
}
return false
} }
// HasIdentity checks if the the whisper node is configured with the private key // HasIdentity checks if the the whisper node is configured with the private key
// of the specified public pair. // of the specified public pair.
func (w *Whisper) HasIdentity(pubKey string) bool { func (w *Whisper) HasKeyPair(id string) bool {
w.keyMu.RLock() w.keyMu.RLock()
defer w.keyMu.RUnlock() defer w.keyMu.RUnlock()
return w.privateKeys[pubKey] != nil return w.privateKeys[id] != nil
} }
// GetIdentity retrieves the private key of the specified public identity. // GetIdentity retrieves the private key of the specified identity.
func (w *Whisper) GetIdentity(pubKey string) *ecdsa.PrivateKey { func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
w.keyMu.RLock() w.keyMu.RLock()
defer w.keyMu.RUnlock() defer w.keyMu.RUnlock()
return w.privateKeys[pubKey] key := w.privateKeys[id]
if key == nil {
return nil, fmt.Errorf("GetPrivateKey: invalid id")
}
return key, nil
} }
func (w *Whisper) GenerateSymKey(name string) error { func (w *Whisper) GenerateSymKey() (string, error) {
const size = aesKeyLength * 2 const size = aesKeyLength * 2
buf := make([]byte, size) buf := make([]byte, size)
_, err := crand.Read(buf) _, err := crand.Read(buf)
if err != nil { if err != nil {
return err return "", err
} else if !validateSymmetricKey(buf) { } 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] key := buf[:aesKeyLength]
salt := buf[aesKeyLength:] salt := buf[aesKeyLength:]
derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion) derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion)
if err != nil { if err != nil {
return err return "", err
} else if !validateSymmetricKey(derived) { } else if !validateSymmetricKey(derived) {
return fmt.Errorf("failed to derive valid key") return "", fmt.Errorf("failed to derive valid key")
} }
w.keyMu.Lock() id, err := GenerateRandomID()
defer w.keyMu.Unlock()
if w.symKeys[name] != nil {
return fmt.Errorf("Key with name [%s] already exists", name)
}
w.symKeys[name] = derived
return nil
}
func (w *Whisper) AddSymKey(name string, key []byte) error {
if w.HasSymKey(name) {
return fmt.Errorf("Key with name [%s] already exists", name)
}
derived, err := deriveKeyMaterial(key, EnvelopeVersion)
if err != nil { if err != nil {
return err return "", fmt.Errorf("Failed to generate ID: %s", err)
} }
w.keyMu.Lock() w.keyMu.Lock()
defer w.keyMu.Unlock() defer w.keyMu.Unlock()
// double check is necessary, because deriveKeyMaterial() is slow if w.symKeys[id] != nil {
if w.symKeys[name] != nil { return "", fmt.Errorf("Failed to generate unique ID")
return fmt.Errorf("Key with name [%s] already exists", name)
} }
w.symKeys[name] = derived w.symKeys[id] = derived
return nil return id, nil
} }
func (w *Whisper) HasSymKey(name string) bool { func (w *Whisper) AddSymKeyDirect(key []byte) (string, error) {
w.keyMu.RLock() if len(key) != aesKeyLength {
defer w.keyMu.RUnlock() return "", fmt.Errorf("Wrong key size: %d", len(key))
return w.symKeys[name] != nil }
id, err := GenerateRandomID()
if err != nil {
return "", fmt.Errorf("Failed to generate ID: %s", err)
} }
func (w *Whisper) DeleteSymKey(name string) {
w.keyMu.Lock() w.keyMu.Lock()
defer w.keyMu.Unlock() defer w.keyMu.Unlock()
delete(w.symKeys, name)
if w.symKeys[id] != nil {
return "", fmt.Errorf("Failed to generate unique ID")
}
w.symKeys[id] = key
return id, nil
} }
func (w *Whisper) GetSymKey(name string) []byte { 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("Severe error: failed to generate unique ID")
}
w.symKeys[id] = derived
return id, nil
}
func (w *Whisper) HasSymKey(id string) bool {
w.keyMu.RLock() w.keyMu.RLock()
defer w.keyMu.RUnlock() defer w.keyMu.RUnlock()
return w.symKeys[name] return w.symKeys[id] != nil
}
func (w *Whisper) DeleteSymKey(id string) bool {
w.keyMu.Lock()
defer w.keyMu.Unlock()
if w.symKeys[id] != nil {
delete(w.symKeys, id)
return true
}
return false
}
func (w *Whisper) GetSymKey(id string) ([]byte, error) {
w.keyMu.RLock()
defer w.keyMu.RUnlock()
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 // Watch installs a new message handler to run in case a matching packet arrives
@ -287,22 +361,29 @@ func (w *Whisper) GetFilter(id string) *Filter {
return w.filters.Get(id) return w.filters.Get(id)
} }
// Unwatch removes an installed message handler. // Unsubscribe removes an installed message handler.
func (w *Whisper) Unwatch(id string) { func (w *Whisper) Unsubscribe(id string) error {
w.filters.Uninstall(id) 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 // Send injects a message into the whisper send queue, to be distributed in the
// network in the coming cycles. // network in the coming cycles.
func (w *Whisper) Send(envelope *Envelope) error { 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 return err
} }
// Start implements node.Service, starting the background data propagation thread // Start implements node.Service, starting the background data propagation thread
// of the Whisper protocol. // of the Whisper protocol.
func (w *Whisper) Start(*p2p.Server) error { func (w *Whisper) Start(*p2p.Server) error {
log.Info(fmt.Sprint("Whisper started")) log.Info(fmt.Sprintf("Whisper v%d started", ProtocolVersion))
go w.update() go w.update()
numCPU := runtime.NumCPU() numCPU := runtime.NumCPU()
@ -355,6 +436,9 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
if err != nil { if err != nil {
return err return err
} }
if packet.Size > uint32(wh.maxMsgLength) {
return fmt.Errorf("oversized message received")
}
switch packet.Code { switch packet.Code {
case statusCode: case statusCode:
@ -435,7 +519,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
} }
} }
if len(envelope.Data) > MaxMessageLength { if envelope.size() > wh.maxMsgLength {
return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash())
} }
@ -453,7 +537,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
return false, fmt.Errorf("oversized salt [%x]", envelope.Hash()) return false, fmt.Errorf("oversized salt [%x]", envelope.Hash())
} }
if envelope.PoW() < MinimumPoW && !wh.test { if envelope.PoW() < wh.minPoW {
log.Debug(fmt.Sprintf("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash())) log.Debug(fmt.Sprintf("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash()))
return false, nil // drop envelope without error return false, nil // drop envelope without error
} }
@ -477,7 +561,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) {
log.Trace(fmt.Sprintf("whisper envelope already cached [%x]\n", envelope.Hash())) log.Trace(fmt.Sprintf("whisper envelope already cached [%x]\n", envelope.Hash()))
} else { } else {
log.Trace(fmt.Sprintf("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope)) log.Trace(fmt.Sprintf("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope))
wh.stats.totalMemoryUsed += envelope.size() wh.stats.memoryUsed += envelope.size()
wh.postEvent(envelope, false) // notify the local node about the new message wh.postEvent(envelope, false) // notify the local node about the new message
if wh.mailServer != nil { if wh.mailServer != nil {
wh.mailServer.Archive(envelope) wh.mailServer.Archive(envelope)
@ -513,6 +597,7 @@ func (w *Whisper) checkOverflow() {
} else if queueSize <= messageQueueLimit/2 { } else if queueSize <= messageQueueLimit/2 {
if w.overflow { if w.overflow {
w.overflow = false w.overflow = false
log.Warn(fmt.Sprint("message queue overflow fixed (back to normal)"))
} }
} }
} }
@ -558,19 +643,17 @@ func (w *Whisper) expire() {
w.poolMu.Lock() w.poolMu.Lock()
defer w.poolMu.Unlock() defer w.poolMu.Unlock()
w.stats.clear() w.stats.reset()
now := uint32(time.Now().Unix()) now := uint32(time.Now().Unix())
for expiry, hashSet := range w.expirations { for expiry, hashSet := range w.expirations {
if expiry < now { if expiry < now {
w.stats.messagesCleared++
// Dump all expired messages and remove timestamp // Dump all expired messages and remove timestamp
hashSet.Each(func(v interface{}) bool { hashSet.Each(func(v interface{}) bool {
sz := w.envelopes[v.(common.Hash)].size() sz := w.envelopes[v.(common.Hash)].size()
w.stats.memoryCleared += sz
w.stats.totalMemoryUsed -= sz
delete(w.envelopes, v.(common.Hash)) 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 return true
}) })
w.expirations[expiry].Clear() w.expirations[expiry].Clear()
@ -580,8 +663,13 @@ func (w *Whisper) expire() {
} }
func (w *Whisper) Stats() string { func (w *Whisper) Stats() string {
return fmt.Sprintf("Latest expiry cycle cleared %d messages (%d bytes). Memory usage: %d bytes.", result := fmt.Sprintf("Memory usage: %d bytes. Average messages cleared per expiry cycle: %d. Total messages cleared: %d.",
w.stats.messagesCleared, w.stats.memoryCleared, w.stats.totalMemoryUsed) 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)
}
return result
} }
// envelopes retrieves all the messages currently pooled by the node. // envelopes retrieves all the messages currently pooled by the node.
@ -596,15 +684,17 @@ func (w *Whisper) Envelopes() []*Envelope {
return all 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 { func (w *Whisper) Messages(id string) []*ReceivedMessage {
result := make([]*ReceivedMessage, 0) result := make([]*ReceivedMessage, 0)
w.poolMu.RLock() w.poolMu.RLock()
defer w.poolMu.RUnlock() defer w.poolMu.RUnlock()
if filter := w.filters.Get(id); filter != nil { if filter := w.filters.Get(id); filter != nil {
for _, msg := range w.messages { for _, env := range w.envelopes {
if filter.MatchMessage(msg) { msg := filter.processEnvelope(env)
if msg != nil {
result = append(result, msg) result = append(result, msg)
} }
} }
@ -620,18 +710,22 @@ func (w *Whisper) isEnvelopeCached(hash common.Hash) bool {
return exist return exist
} }
func (w *Whisper) addDecryptedMessage(msg *ReceivedMessage) { func (s *Statistics) reset() {
w.poolMu.Lock() s.cycles++
defer w.poolMu.Unlock() s.totalMessagesCleared += s.messagesCleared
w.messages[msg.EnvelopeHash] = msg
}
func (s *Statistics) clear() {
s.memoryCleared = 0 s.memoryCleared = 0
s.messagesCleared = 0 s.messagesCleared = 0
} }
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
}
func ValidatePublicKey(k *ecdsa.PublicKey) bool { func ValidatePublicKey(k *ecdsa.PublicKey) bool {
return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
} }
@ -686,3 +780,16 @@ func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error
return nil, unknownVersionError(version) return nil, unknownVersionError(version)
} }
} }
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
}

View file

@ -20,9 +20,6 @@ import (
"bytes" "bytes"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
func TestWhisperBasic(t *testing.T) { func TestWhisperBasic(t *testing.T) {
@ -54,16 +51,19 @@ func TestWhisperBasic(t *testing.T) {
if peer != nil { if peer != nil {
t.Fatal("found peer for random key.") 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.") t.Fatalf("failed MarkPeerTrusted.")
} }
exist := w.HasSymKey("non-existing") exist := w.HasSymKey("non-existing")
if exist { if exist {
t.Fatalf("failed HasSymKey.") 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 { if key != nil {
t.Fatalf("failed GetSymKey.") t.Fatalf("failed GetSymKey: false positive.")
} }
mail := w.Envelopes() mail := w.Envelopes()
if len(mail) != 0 { if len(mail) != 0 {
@ -79,7 +79,7 @@ func TestWhisperBasic(t *testing.T) {
if _, err := deriveKeyMaterial(peerID, ver); err != unknownVersionError(ver) { if _, err := deriveKeyMaterial(peerID, ver); err != unknownVersionError(ver) {
t.Fatalf("failed deriveKeyMaterial with param = %v: %s.", peerID, err) t.Fatalf("failed deriveKeyMaterial with param = %v: %s.", peerID, err)
} }
derived, err := deriveKeyMaterial(peerID, 0) derived, err = deriveKeyMaterial(peerID, 0)
if err != nil { if err != nil {
t.Fatalf("failed second deriveKeyMaterial with param = %v: %s.", peerID, err) t.Fatalf("failed second deriveKeyMaterial with param = %v: %s.", peerID, err)
} }
@ -100,7 +100,14 @@ func TestWhisperBasic(t *testing.T) {
t.Fatalf("failed BytesToIntBigEndian: %d.", be) 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) { if !validatePrivateKey(pk) {
t.Fatalf("failed validatePrivateKey: %v.", pk) t.Fatalf("failed validatePrivateKey: %v.", pk)
} }
@ -111,67 +118,112 @@ func TestWhisperBasic(t *testing.T) {
func TestWhisperIdentityManagement(t *testing.T) { func TestWhisperIdentityManagement(t *testing.T) {
w := New() w := New()
id1 := w.NewIdentity() id1, err := w.NewKeyPair()
id2 := w.NewIdentity() if err != nil {
pub1 := common.ToHex(crypto.FromECDSAPub(&id1.PublicKey)) t.Fatalf("failed to generate new key pair: %s.", err)
pub2 := common.ToHex(crypto.FromECDSAPub(&id2.PublicKey))
pk1 := w.GetIdentity(pub1)
pk2 := w.GetIdentity(pub2)
if !w.HasIdentity(pub1) {
t.Fatalf("failed HasIdentity(pub1).")
} }
if !w.HasIdentity(pub2) { id2, err := w.NewKeyPair()
t.Fatalf("failed HasIdentity(pub2).") if err != nil {
t.Fatalf("failed to generate new key pair: %s.", err)
} }
if pk1 != id1 { pk1, err := w.GetPrivateKey(id1)
t.Fatalf("failed GetIdentity(pub1).") if err != nil {
t.Fatalf("failed to retrieve the key pair: %s.", err)
} }
if pk2 != id2 { pk2, err := w.GetPrivateKey(id2)
t.Fatalf("failed GetIdentity(pub2).") 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 !validatePrivateKey(pk2) {
t.Fatalf("pk2 is invalid.")
} }
// Delete one identity // Delete one identity
w.DeleteIdentity(pub1) done := w.DeleteKeyPair(id1)
pk1 = w.GetIdentity(pub1) if !done {
pk2 = w.GetIdentity(pub2) t.Fatalf("failed to delete id1.")
if w.HasIdentity(pub1) { }
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.") t.Fatalf("failed DeleteIdentity(pub1): still exist.")
} }
if !w.HasIdentity(pub2) { if !w.HasKeyPair(id2) {
t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.") t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.")
} }
if pk1 != nil { if pk1 != nil {
t.Fatalf("failed DeleteIdentity(pub1): first key still exist.") 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.") t.Fatalf("failed DeleteIdentity(pub1): second key does not exist.")
} }
// Delete again non-existing identity // Delete again non-existing identity
w.DeleteIdentity(pub1) done = w.DeleteKeyPair(id1)
pk1 = w.GetIdentity(pub1) if done {
pk2 = w.GetIdentity(pub2) t.Fatalf("delete id1: false positive.")
if w.HasIdentity(pub1) { }
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.") 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.") t.Fatalf("failed delete non-existing identity: pub2 does not exist.")
} }
if pk1 != nil { if pk1 != nil {
t.Fatalf("failed delete non-existing identity: first key exist.") 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.") t.Fatalf("failed delete non-existing identity: second key does not exist.")
} }
// Delete second identity // Delete second identity
w.DeleteIdentity(pub2) done = w.DeleteKeyPair(id2)
pk1 = w.GetIdentity(pub1) if !done {
pk2 = w.GetIdentity(pub2) t.Fatalf("failed to delete id2.")
if w.HasIdentity(pub1) { }
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.") 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.") t.Fatalf("failed delete second identity: still exist.")
} }
if pk1 != nil { if pk1 != nil {
@ -185,23 +237,30 @@ func TestWhisperIdentityManagement(t *testing.T) {
func TestWhisperSymKeyManagement(t *testing.T) { func TestWhisperSymKeyManagement(t *testing.T) {
InitSingleTest() InitSingleTest()
var err error
var k1, k2 []byte var k1, k2 []byte
w := New() w := New()
id1 := string("arbitrary-string-1") id1 := string("arbitrary-string-1")
id2 := string("arbitrary-string-2") id2 := string("arbitrary-string-2")
err := w.GenerateSymKey(id1) id1, err = w.GenerateSymKey()
if err != nil { if err != nil {
t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err)
} }
k1 = w.GetSymKey(id1) k1, err = w.GetSymKey(id1)
k2 = w.GetSymKey(id2) 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) { if !w.HasSymKey(id1) {
t.Fatalf("failed HasSymKey(id1).") t.Fatalf("failed HasSymKey(id1).")
} }
if w.HasSymKey(id2) { if w.HasSymKey(id2) {
t.Fatalf("failed HasSymKey(id2).") t.Fatalf("failed HasSymKey(id2): false positive.")
} }
if k1 == nil { if k1 == nil {
t.Fatalf("first key does not exist.") t.Fatalf("first key does not exist.")
@ -210,38 +269,49 @@ func TestWhisperSymKeyManagement(t *testing.T) {
t.Fatalf("second key still exist.") t.Fatalf("second key still exist.")
} }
// add existing id, nothing should change randomKey := make([]byte, aesKeyLength)
randomKey := make([]byte, 16)
randomize(randomKey) randomize(randomKey)
err = w.AddSymKey(id1, randomKey) id1, err = w.AddSymKeyDirect(randomKey)
if err == nil { if err != nil {
t.Fatalf("failed AddSymKey with seed %d.", seed) t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err)
} }
k1 = w.GetSymKey(id1) k1, err = w.GetSymKey(id1)
k2 = w.GetSymKey(id2) 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) { if !w.HasSymKey(id1) {
t.Fatalf("failed w.HasSymKey(id1).") t.Fatalf("failed w.HasSymKey(id1).")
} }
if w.HasSymKey(id2) { if w.HasSymKey(id2) {
t.Fatalf("failed w.HasSymKey(id2).") t.Fatalf("failed w.HasSymKey(id2): false positive.")
} }
if k1 == nil { if k1 == nil {
t.Fatalf("first key does not exist.") t.Fatalf("first key does not exist.")
} }
if bytes.Equal(k1, randomKey) { if !bytes.Equal(k1, randomKey) {
t.Fatalf("k1 == randomKey.") t.Fatalf("k1 != randomKey.")
} }
if k2 != nil { if k2 != nil {
t.Fatalf("second key already exist.") t.Fatalf("second key already exist.")
} }
err = w.AddSymKey(id2, randomKey) // add non-existing (yet) id2, err = w.AddSymKeyDirect(randomKey)
if err != nil { if err != nil {
t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err) t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err)
} }
k1 = w.GetSymKey(id1) k1, err = w.GetSymKey(id1)
k2 = w.GetSymKey(id2) 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) { if !w.HasSymKey(id1) {
t.Fatalf("HasSymKey(id1) failed.") t.Fatalf("HasSymKey(id1) failed.")
} }
@ -254,11 +324,11 @@ func TestWhisperSymKeyManagement(t *testing.T) {
if k2 == nil { if k2 == nil {
t.Fatalf("k2 does not exist.") t.Fatalf("k2 does not exist.")
} }
if bytes.Equal(k1, k2) { if !bytes.Equal(k1, k2) {
t.Fatalf("k1 == k2.") t.Fatalf("k1 != k2.")
} }
if bytes.Equal(k1, randomKey) { if !bytes.Equal(k1, randomKey) {
t.Fatalf("k1 == randomKey.") t.Fatalf("k1 != randomKey.")
} }
if len(k1) != aesKeyLength { if len(k1) != aesKeyLength {
t.Fatalf("wrong length of k1.") t.Fatalf("wrong length of k1.")
@ -268,8 +338,17 @@ func TestWhisperSymKeyManagement(t *testing.T) {
} }
w.DeleteSymKey(id1) w.DeleteSymKey(id1)
k1 = w.GetSymKey(id1) k1, err = w.GetSymKey(id1)
k2 = w.GetSymKey(id2) 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) { if w.HasSymKey(id1) {
t.Fatalf("failed to delete first key: still exist.") t.Fatalf("failed to delete first key: still exist.")
} }
@ -285,8 +364,17 @@ func TestWhisperSymKeyManagement(t *testing.T) {
w.DeleteSymKey(id1) w.DeleteSymKey(id1)
w.DeleteSymKey(id2) w.DeleteSymKey(id2)
k1 = w.GetSymKey(id1) k1, err = w.GetSymKey(id1)
k2 = w.GetSymKey(id2) 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) { if w.HasSymKey(id1) {
t.Fatalf("failed to delete second key: first key exist.") t.Fatalf("failed to delete second key: first key exist.")
} }
@ -299,13 +387,63 @@ func TestWhisperSymKeyManagement(t *testing.T) {
if k2 != nil { if k2 != nil {
t.Fatalf("failed to delete second key: second key is not nil.") t.Fatalf("failed to delete second key: second key is not nil.")
} }
randomKey = make([]byte, aesKeyLength+1)
randomize(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) { func TestExpiry(t *testing.T) {
InitSingleTest() InitSingleTest()
w := New() w := New()
w.test = true w.SetMinimumPoW(0.0000001)
defer w.SetMinimumPoW(DefaultMinimumPoW)
w.Start(nil) w.Start(nil)
defer w.Stop() defer w.Stop()
@ -353,3 +491,87 @@ func TestExpiry(t *testing.T) {
t.Fatalf("expire failed, seed: %d.", seed) 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.Watch(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")
}
}