From 9cd713551627a9b48e04a77f64a15ea6f829dcf4 Mon Sep 17 00:00:00 2001 From: gluk256 Date: Sun, 9 Apr 2017 23:49:22 +0200 Subject: whisper: big refactoring (#13852) * whisper: GetMessages fixed; size restriction updated * whisper: made PoW and MaxMsgSize customizable * whisper: test added * whisper: sym key management changed * whisper: identity management refactored * whisper: API refactoring (Post and Filter) * whisper: big refactoring complete * whisper: spelling fix * whisper: variable topic size allowed for a filter * whisper: final update * whisper: formatting * whisper: file exchange introduced in wnode * whisper: bugfix * whisper: API updated + new tests * whisper: statistics updated * whisper: wnode server updated * whisper: allowed filtering for variable topic size * whisper: tests added * whisper: resolving merge conflicts * whisper: refactoring (documenting mostly) * whsiper: tests fixed * whisper: down cased error messages * whisper: documenting the API functions * whisper: logging fixed * whisper: fixed wnode parameters * whisper: logs fixed (typos) --- whisper/mailserver/mailserver.go | 9 +- whisper/mailserver/server_test.go | 20 +- whisper/whisperv5/api.go | 490 ++++++++++++++++++++------------------ whisper/whisperv5/api_test.go | 405 +++++++++++++++++++------------ whisper/whisperv5/doc.go | 5 +- whisper/whisperv5/envelope.go | 8 +- whisper/whisperv5/filter.go | 99 ++++---- whisper/whisperv5/filter_test.go | 56 ++++- whisper/whisperv5/message.go | 47 ++-- whisper/whisperv5/peer.go | 16 +- whisper/whisperv5/peer_test.go | 15 +- whisper/whisperv5/whisper.go | 373 ++++++++++++++++++++--------- whisper/whisperv5/whisper_test.go | 355 ++++++++++++++++++++++----- 13 files changed, 1223 insertions(+), 675 deletions(-) (limited to 'whisper') diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go index 6533c56c2..d705c622f 100644 --- a/whisper/mailserver/mailserver.go +++ b/whisper/mailserver/mailserver.go @@ -31,8 +31,6 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) -const MailServerKeyName = "958e04ab302fb36ad2616a352cbac79d" - type WMailServer struct { db *leveldb.DB w *whisper.Whisper @@ -75,11 +73,14 @@ func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, p s.w = shh s.pow = pow - err = s.w.AddSymKey(MailServerKeyName, []byte(password)) + MailServerKeyID, err := s.w.AddSymKeyFromPassword(password) if err != nil { utils.Fatalf("Failed to create symmetric key for MailServer: %s", err) } - s.key = s.w.GetSymKey(MailServerKeyName) + s.key, err = s.w.GetSymKey(MailServerKeyID) + if err != nil { + utils.Fatalf("Failed to save symmetric key for MailServer") + } } func (s *WMailServer) Close() { diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index 8b58a826f..ffdff3191 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -30,8 +30,8 @@ import ( ) const powRequirement = 0.00001 -const keyName = "6d604bac5401ce9a6b995f1b45a4ab" +var keyID string var shh *whisper.Whisper var seed = time.Now().Unix() @@ -90,7 +90,7 @@ func TestMailServer(t *testing.T) { server.Init(shh, dir, password, powRequirement) defer server.Close() - err = shh.AddSymKey(keyName, []byte(password)) + keyID, err = shh.AddSymKeyFromPassword(password) if err != nil { t.Fatalf("Failed to create symmetric key for mail request: %s", err) } @@ -102,7 +102,14 @@ func TestMailServer(t *testing.T) { } func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) { - testPeerID := shh.NewIdentity() + id, err := shh.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err) + } + testPeerID, err := shh.GetPrivateKey(id) + if err != nil { + t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err) + } birth := env.Expiry - env.TTL p := &ServerTestParams{ topic: env.Topic, @@ -167,8 +174,13 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope { binary.BigEndian.PutUint32(data[4:], p.upp) copy(data[8:], p.topic[:]) + key, err := shh.GetSymKey(keyID) + if err != nil { + t.Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err) + } + params := &whisper.MessageParams{ - KeySym: shh.GetSymKey(keyName), + KeySym: key, Topic: p.topic, Payload: data, PoW: powRequirement * 2, diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 9b43f7b70..579efba9e 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -20,15 +20,14 @@ import ( "encoding/json" "errors" "fmt" - mathrand "math/rand" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover" ) -var whisperOffLineErr = errors.New("whisper is offline") +var whisperOfflineErr = errors.New("whisper is offline") // PublicWhisperAPI provides the whisper RPC service. type PublicWhisperAPI struct { @@ -43,7 +42,7 @@ func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI { // Start starts the Whisper worker threads. func (api *PublicWhisperAPI) Start() error { if api.whisper == nil { - return whisperOffLineErr + return whisperOfflineErr } return api.whisper.Start(nil) } @@ -51,7 +50,7 @@ func (api *PublicWhisperAPI) Start() error { // Stop stops the Whisper worker threads. func (api *PublicWhisperAPI) Stop() error { if api.whisper == nil { - return whisperOffLineErr + return whisperOfflineErr } return api.whisper.Stop() } @@ -59,179 +58,219 @@ func (api *PublicWhisperAPI) Stop() error { // Version returns the Whisper version this node offers. func (api *PublicWhisperAPI) Version() (hexutil.Uint, error) { if api.whisper == nil { - return 0, whisperOffLineErr + return 0, whisperOfflineErr } return hexutil.Uint(api.whisper.Version()), nil } -// Stats returns the Whisper statistics for diagnostics. -func (api *PublicWhisperAPI) Stats() (string, error) { +// Info returns the Whisper statistics for diagnostics. +func (api *PublicWhisperAPI) Info() (string, error) { if api.whisper == nil { - return "", whisperOffLineErr + return "", whisperOfflineErr } return api.whisper.Stats(), nil } -// MarkPeerTrusted marks specific peer trusted, which will allow it +// SetMaxMessageLength sets the maximal message length allowed by this node +func (api *PublicWhisperAPI) SetMaxMessageLength(val int) error { + if api.whisper == nil { + return whisperOfflineErr + } + return api.whisper.SetMaxMessageLength(val) +} + +// SetMinimumPoW sets the minimal PoW required by this node +func (api *PublicWhisperAPI) SetMinimumPoW(val float64) error { + if api.whisper == nil { + return whisperOfflineErr + } + return api.whisper.SetMinimumPoW(val) +} + +// AllowP2PMessagesFromPeer marks specific peer trusted, which will allow it // to send historic (expired) messages. -func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error { +func (api *PublicWhisperAPI) AllowP2PMessagesFromPeer(enode string) error { if api.whisper == nil { - return whisperOffLineErr + return whisperOfflineErr } - return api.whisper.MarkPeerTrusted(peerID) + n, err := discover.ParseNode(enode) + if err != nil { + return errors.New("failed to parse enode of trusted peer: " + err.Error()) + } + return api.whisper.AllowP2PMessagesFromPeer(n.ID[:]) } -// RequestHistoricMessages requests the peer to deliver the old (expired) messages. -// data contains parameters (time frame, payment details, etc.), required -// by the remote email-like server. Whisper is not aware about the data format, -// it will just forward the raw data to the server. -//func (api *PublicWhisperAPI) RequestHistoricMessages(peerID hexutil.Bytes, data hexutil.Bytes) error { -// if api.whisper == nil { -// return whisperOffLineErr -// } -// return api.whisper.RequestHistoricMessages(peerID, data) -//} - -// HasIdentity checks if the whisper node is configured with the private key +// HasKeyPair checks if the whisper node is configured with the private key // of the specified public pair. -func (api *PublicWhisperAPI) HasIdentity(identity string) (bool, error) { +func (api *PublicWhisperAPI) HasKeyPair(id string) (bool, error) { if api.whisper == nil { - return false, whisperOffLineErr + return false, whisperOfflineErr } - return api.whisper.HasIdentity(identity), nil + return api.whisper.HasKeyPair(id), nil } -// DeleteIdentity deletes the specifies key if it exists. -func (api *PublicWhisperAPI) DeleteIdentity(identity string) error { +// DeleteKeyPair deletes the specifies key if it exists. +func (api *PublicWhisperAPI) DeleteKeyPair(id string) (bool, error) { if api.whisper == nil { - return whisperOffLineErr + return false, whisperOfflineErr } - api.whisper.DeleteIdentity(identity) - return nil + return api.whisper.DeleteKeyPair(id), nil } -// NewIdentity generates a new cryptographic identity for the client, and injects +// NewKeyPair generates a new cryptographic identity for the client, and injects // it into the known identities for message decryption. -func (api *PublicWhisperAPI) NewIdentity() (string, error) { +func (api *PublicWhisperAPI) NewKeyPair() (string, error) { if api.whisper == nil { - return "", whisperOffLineErr + return "", whisperOfflineErr } - identity := api.whisper.NewIdentity() - return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil + return api.whisper.NewKeyPair() } -// GenerateSymKey generates a random symmetric key and stores it under -// the 'name' id. Will be used in the future for session key exchange. -func (api *PublicWhisperAPI) GenerateSymKey(name string) error { +// GetPublicKey returns the public key for identity id +func (api *PublicWhisperAPI) GetPublicKey(id string) (hexutil.Bytes, error) { if api.whisper == nil { - return whisperOffLineErr + return nil, whisperOfflineErr + } + key, err := api.whisper.GetPrivateKey(id) + if err != nil { + return nil, err } - return api.whisper.GenerateSymKey(name) + return crypto.FromECDSAPub(&key.PublicKey), nil } -// AddSymKey stores the key under the 'name' id. -func (api *PublicWhisperAPI) AddSymKey(name string, key hexutil.Bytes) error { +// GetPrivateKey returns the private key for identity id +func (api *PublicWhisperAPI) GetPrivateKey(id string) (string, error) { if api.whisper == nil { - return whisperOffLineErr + return "", whisperOfflineErr } - return api.whisper.AddSymKey(name, key) + key, err := api.whisper.GetPrivateKey(id) + if err != nil { + return "", err + } + return common.ToHex(crypto.FromECDSA(key)), nil } -// HasSymKey returns true if there is a key associated with the name string. -// Otherwise returns false. -func (api *PublicWhisperAPI) HasSymKey(name string) (bool, error) { +// GenerateSymmetricKey generates a random symmetric key and stores it under id, +// which is then returned. Will be used in the future for session key exchange. +func (api *PublicWhisperAPI) GenerateSymmetricKey() (string, error) { if api.whisper == nil { - return false, whisperOffLineErr + return "", whisperOfflineErr } - res := api.whisper.HasSymKey(name) - return res, nil + return api.whisper.GenerateSymKey() } -// DeleteSymKey deletes the key associated with the name string if it exists. -func (api *PublicWhisperAPI) DeleteSymKey(name string) error { +// AddSymmetricKeyDirect stores the key, and returns its id. +func (api *PublicWhisperAPI) AddSymmetricKeyDirect(key hexutil.Bytes) (string, error) { if api.whisper == nil { - return whisperOffLineErr + return "", whisperOfflineErr } - api.whisper.DeleteSymKey(name) - return nil + return api.whisper.AddSymKeyDirect(key) } -// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages. -// Returns the ID of the newly created Filter. -func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { +// AddSymmetricKeyFromPassword generates the key from password, stores it, and returns its id. +func (api *PublicWhisperAPI) AddSymmetricKeyFromPassword(password string) (string, error) { if api.whisper == nil { - return "", whisperOffLineErr + return "", whisperOfflineErr } + return api.whisper.AddSymKeyFromPassword(password) +} - filter := Filter{ - Src: crypto.ToECDSAPub(common.FromHex(args.From)), - KeySym: api.whisper.GetSymKey(args.KeyName), - PoW: args.PoW, - Messages: make(map[common.Hash]*ReceivedMessage), - AcceptP2P: args.AcceptP2P, - } - if len(filter.KeySym) > 0 { - filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) +// HasSymmetricKey returns true if there is a key associated with the given id. +// Otherwise returns false. +func (api *PublicWhisperAPI) HasSymmetricKey(id string) (bool, error) { + if api.whisper == nil { + return false, whisperOfflineErr } - filter.Topics = append(filter.Topics, args.Topics...) + res := api.whisper.HasSymKey(id) + return res, nil +} - if len(args.Topics) == 0 && len(args.KeyName) != 0 { - info := "NewFilter: at least one topic must be specified" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) +// GetSymmetricKey returns the symmetric key associated with the given id. +func (api *PublicWhisperAPI) GetSymmetricKey(name string) (hexutil.Bytes, error) { + if api.whisper == nil { + return nil, whisperOfflineErr + } + b, err := api.whisper.GetSymKey(name) + if err != nil { + return nil, err } + return b, nil +} - if len(args.KeyName) != 0 && len(filter.KeySym) == 0 { - info := "NewFilter: key was not found by name: " + args.KeyName - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) +// DeleteSymmetricKey deletes the key associated with the name string if it exists. +func (api *PublicWhisperAPI) DeleteSymmetricKey(name string) (bool, error) { + if api.whisper == nil { + return false, whisperOfflineErr } + res := api.whisper.DeleteSymKey(name) + return res, nil +} - if len(args.To) == 0 && len(filter.KeySym) == 0 { - info := "NewFilter: filter must contain either symmetric or asymmetric key" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) +// Subscribe creates and registers a new filter to watch for inbound whisper messages. +// Returns the ID of the newly created filter. +func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { + if api.whisper == nil { + return "", whisperOfflineErr } - if len(args.To) != 0 && len(filter.KeySym) != 0 { - info := "NewFilter: filter must not contain both symmetric and asymmetric key" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) + filter := Filter{ + Src: crypto.ToECDSAPub(common.FromHex(args.SignedWith)), + PoW: args.MinPoW, + Messages: make(map[common.Hash]*ReceivedMessage), + AllowP2P: args.AllowP2P, } - if len(args.To) > 0 { - dst := crypto.ToECDSAPub(common.FromHex(args.To)) - if !ValidatePublicKey(dst) { - info := "NewFilter: Invalid 'To' address" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) - } - filter.KeyAsym = api.whisper.GetIdentity(string(args.To)) - if filter.KeyAsym == nil { - info := "NewFilter: non-existent identity provided" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) + var err error + for i, bt := range args.Topics { + if len(bt) == 0 || len(bt) > 4 { + return "", errors.New(fmt.Sprintf("subscribe: topic %d has wrong size: %d", i, len(bt))) } + filter.Topics = append(filter.Topics, bt) } - if len(args.From) > 0 { + if err = ValidateKeyID(args.Key); err != nil { + return "", errors.New("subscribe: " + err.Error()) + } + + if len(args.SignedWith) > 0 { if !ValidatePublicKey(filter.Src) { - info := "NewFilter: Invalid 'From' address" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) + return "", errors.New("subscribe: invalid 'SignedWith' field") + } + } + + if args.Symmetric { + if len(args.Topics) == 0 { + return "", errors.New("subscribe: at least one topic must be specified with symmetric encryption") + } + symKey, err := api.whisper.GetSymKey(args.Key) + if err != nil { + return "", errors.New("subscribe: invalid key ID") + } + if !validateSymmetricKey(symKey) { + return "", errors.New("subscribe: retrieved key is invalid") + } + filter.KeySym = symKey + filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) + } else { + filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key) + if err != nil { + return "", errors.New("subscribe: invalid key ID") + } + if filter.KeyAsym == nil { + return "", errors.New("subscribe: non-existent identity provided") } } - return api.whisper.Watch(&filter) + return api.whisper.Subscribe(&filter) } -// UninstallFilter disables and removes an existing filter. -func (api *PublicWhisperAPI) UninstallFilter(filterId string) { - api.whisper.Unwatch(filterId) +// Unsubscribe disables and removes an existing filter. +func (api *PublicWhisperAPI) Unsubscribe(id string) { + api.whisper.Unsubscribe(id) } -// GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval. -func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage { +// GetSubscriptionMessages retrieves all the new messages matched by a filter since the last retrieval. +func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*WhisperMessage { f := api.whisper.GetFilter(filterId) if f != nil { newMail := f.Retrieve() @@ -240,7 +279,8 @@ func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage return toWhisperMessages(nil) } -// GetMessages retrieves all the known messages that match a specific filter. +// GetMessages retrieves all the floating messages that match a specific filter. +// It is likely to be called once per session, right after Subscribe call. func (api *PublicWhisperAPI) GetMessages(filterId string) []*WhisperMessage { all := api.whisper.Messages(filterId) return toWhisperMessages(all) @@ -258,139 +298,107 @@ func toWhisperMessages(messages []*ReceivedMessage) []*WhisperMessage { // Post creates a whisper message and injects it into the network for distribution. func (api *PublicWhisperAPI) Post(args PostArgs) error { if api.whisper == nil { - return whisperOffLineErr + return whisperOfflineErr } + var err error params := MessageParams{ TTL: args.TTL, - Dst: crypto.ToECDSAPub(common.FromHex(args.To)), - KeySym: api.whisper.GetSymKey(args.KeyName), - Topic: args.Topic, + WorkTime: args.PowTime, + PoW: args.PowTarget, Payload: args.Payload, Padding: args.Padding, - WorkTime: args.WorkTime, - PoW: args.PoW, } - if len(args.From) > 0 { - pub := crypto.ToECDSAPub(common.FromHex(args.From)) - if !ValidatePublicKey(pub) { - info := "Post: Invalid 'From' address" - log.Error(fmt.Sprintf(info)) - return errors.New(info) + if len(args.Key) == 0 { + return errors.New("post: key is missing") + } + + if len(args.SignWith) > 0 { + params.Src, err = api.whisper.GetPrivateKey(args.SignWith) + if err != nil { + return err } - params.Src = api.whisper.GetIdentity(string(args.From)) if params.Src == nil { - info := "Post: non-existent identity provided" - log.Error(fmt.Sprintf(info)) - return errors.New(info) + return errors.New("post: empty identity") } } - filter := api.whisper.GetFilter(args.FilterID) - if filter == nil && len(args.FilterID) > 0 { - info := fmt.Sprintf("Post: wrong filter id %s", args.FilterID) - log.Error(fmt.Sprintf(info)) - return errors.New(info) + if len(args.Topic) == TopicLength { + params.Topic = BytesToTopic(args.Topic) + } else if len(args.Topic) != 0 { + return errors.New(fmt.Sprintf("post: wrong topic size %d", len(args.Topic))) } - if filter != nil { - // get the missing fields from the filter - if params.KeySym == nil && filter.KeySym != nil { - params.KeySym = filter.KeySym + if args.Type == "sym" { + if err = ValidateKeyID(args.Key); err != nil { + return err } - if params.Src == nil && filter.Src != nil { - params.Src = filter.KeyAsym + params.KeySym, err = api.whisper.GetSymKey(args.Key) + if err != nil { + return err } - if (params.Topic == TopicType{}) { - sz := len(filter.Topics) - if sz < 1 { - info := fmt.Sprintf("Post: no topics in filter # %s", args.FilterID) - log.Error(fmt.Sprintf(info)) - return errors.New(info) - } else if sz == 1 { - params.Topic = filter.Topics[0] - } else { - // choose randomly - rnd := mathrand.Intn(sz) - params.Topic = filter.Topics[rnd] - } + if !validateSymmetricKey(params.KeySym) { + return errors.New("post: key for symmetric encryption is invalid") } - } - - // validate - if len(args.KeyName) != 0 && len(params.KeySym) == 0 { - info := "Post: key was not found by name: " + args.KeyName - log.Error(fmt.Sprintf(info)) - return errors.New(info) - } - - if len(args.To) == 0 && len(params.KeySym) == 0 { - info := "Post: message must be encrypted either symmetrically or asymmetrically" - log.Error(fmt.Sprintf(info)) - return errors.New(info) - } - - if len(args.To) != 0 && len(params.KeySym) != 0 { - info := "Post: ambigous encryption method requested" - log.Error(fmt.Sprintf(info)) - return errors.New(info) - } - - if len(args.To) > 0 { + if len(params.Topic) == 0 { + return errors.New("post: topic is missing for symmetric encryption") + } + } else if args.Type == "asym" { + params.Dst = crypto.ToECDSAPub(common.FromHex(args.Key)) if !ValidatePublicKey(params.Dst) { - info := "Post: Invalid 'To' address" - log.Error(fmt.Sprintf(info)) - return errors.New(info) + return errors.New("post: public key for asymmetric encryption is invalid") } + } else { + return errors.New("post: wrong type (sym/asym)") } // encrypt and send message := NewSentMessage(¶ms) + if message == nil { + return errors.New("post: failed create new message, probably due to failed rand function (OS level)") + } envelope, err := message.Wrap(¶ms) if err != nil { - log.Error(fmt.Sprintf(err.Error())) return err } - if len(envelope.Data) > MaxMessageLength { - info := "Post: message is too big" - log.Error(fmt.Sprintf(info)) - return errors.New(info) - } - if (envelope.Topic == TopicType{} && envelope.IsSymmetric()) { - info := "Post: topic is missing for symmetric encryption" - log.Error(fmt.Sprintf(info)) - return errors.New(info) + if envelope.size() > api.whisper.maxMsgLength { + return errors.New("post: message is too big") } - if args.PeerID != nil { - return api.whisper.SendP2PMessage(args.PeerID, envelope) + if len(args.TargetPeer) != 0 { + n, err := discover.ParseNode(args.TargetPeer) + if err != nil { + return errors.New("post: failed to parse enode of target peer: " + err.Error()) + } + return api.whisper.SendP2PMessage(n.ID[:], envelope) + } else if args.PowTarget < api.whisper.minPoW { + return errors.New("post: target PoW is less than minimum PoW, the message can not be sent") } return api.whisper.Send(envelope) } type PostArgs struct { - TTL uint32 `json:"ttl"` - From string `json:"from"` - To string `json:"to"` - KeyName string `json:"keyname"` - Topic TopicType `json:"topic"` - Padding hexutil.Bytes `json:"padding"` - Payload hexutil.Bytes `json:"payload"` - WorkTime uint32 `json:"worktime"` - PoW float64 `json:"pow"` - FilterID string `json:"filterID"` - PeerID hexutil.Bytes `json:"peerID"` + Type string `json:"type"` // "sym"/"asym" (symmetric or asymmetric) + TTL uint32 `json:"ttl"` // time-to-live in seconds + SignWith string `json:"signWith"` // id of the signing key + Key string `json:"key"` // id of encryption key + Topic hexutil.Bytes `json:"topic"` // topic (4 bytes) + Padding hexutil.Bytes `json:"padding"` // optional padding bytes + Payload hexutil.Bytes `json:"payload"` // payload to be encrypted + PowTime uint32 `json:"powTime"` // maximal time in seconds to be spent on PoW + PowTarget float64 `json:"powTarget"` // minimal PoW required for this message + TargetPeer string `json:"targetPeer"` // peer id (for p2p message only) } type WhisperFilterArgs struct { - To string `json:"to"` - From string `json:"from"` - KeyName string `json:"keyname"` - PoW float64 `json:"pow"` - Topics []TopicType `json:"topics"` - AcceptP2P bool `json:"p2p"` + Symmetric bool // encryption type + Key string // id of the key to be used for decryption + SignedWith string // public key of the sender to be verified + MinPoW float64 // minimal PoW requirement + Topics [][]byte // list of topics (up to 4 bytes each) to match + AllowP2P bool // indicates wheather direct p2p messages are allowed for this filter } // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a @@ -398,22 +406,30 @@ type WhisperFilterArgs struct { func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { // Unmarshal the JSON message and sanity check var obj struct { - To string `json:"to"` - From string `json:"from"` - KeyName string `json:"keyname"` - PoW float64 `json:"pow"` - Topics []interface{} `json:"topics"` - AcceptP2P bool `json:"p2p"` + Type string `json:"type"` + Key string `json:"key"` + SignedWith string `json:"signedWith"` + MinPoW float64 `json:"minPoW"` + Topics []interface{} `json:"topics"` + AllowP2P bool `json:"allowP2P"` } if err := json.Unmarshal(b, &obj); err != nil { return err } - args.To = obj.To - args.From = obj.From - args.KeyName = obj.KeyName - args.PoW = obj.PoW - args.AcceptP2P = obj.AcceptP2P + switch obj.Type { + case "sym": + args.Symmetric = true + case "asym": + args.Symmetric = false + default: + return errors.New("wrong type (sym/asym)") + } + + args.Key = obj.Key + args.SignedWith = obj.SignedWith + args.MinPoW = obj.MinPoW + args.AllowP2P = obj.AllowP2P // Construct the topic array if obj.Topics != nil { @@ -428,13 +444,13 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { return fmt.Errorf("topic[%d] is not a string", i) } } - topicsDecoded := make([]TopicType, len(topics)) + topicsDecoded := make([][]byte, len(topics)) for j, s := range topics { x := common.FromHex(s) - if x == nil || len(x) != TopicLength { + if x == nil || len(x) > TopicLength { return fmt.Errorf("topic[%d] is invalid", j) } - topicsDecoded[j] = BytesToTopic(x) + topicsDecoded[j] = x } args.Topics = topicsDecoded } @@ -444,34 +460,34 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { // WhisperMessage is the RPC representation of a whisper message. type WhisperMessage struct { - Topic string `json:"topic"` - Payload string `json:"payload"` - Padding string `json:"padding"` - From string `json:"from"` - To string `json:"to"` - Sent uint32 `json:"sent"` - TTL uint32 `json:"ttl"` - PoW float64 `json:"pow"` - Hash string `json:"hash"` + Topic string `json:"topic"` + Payload string `json:"payload"` + Padding string `json:"padding"` + Src string `json:"signedWith"` + Dst string `json:"recipientPublicKey"` + Timestamp uint32 `json:"timestamp"` + TTL uint32 `json:"ttl"` + PoW float64 `json:"pow"` + Hash string `json:"hash"` } // NewWhisperMessage converts an internal message into an API version. func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage { msg := WhisperMessage{ - Topic: common.ToHex(message.Topic[:]), - Payload: common.ToHex(message.Payload), - Padding: common.ToHex(message.Padding), - Sent: message.Sent, - TTL: message.TTL, - PoW: message.PoW, - Hash: common.ToHex(message.EnvelopeHash.Bytes()), + Topic: common.ToHex(message.Topic[:]), + Payload: common.ToHex(message.Payload), + Padding: common.ToHex(message.Padding), + Timestamp: message.Sent, + TTL: message.TTL, + PoW: message.PoW, + Hash: common.ToHex(message.EnvelopeHash.Bytes()), } if message.Dst != nil { - msg.To = common.ToHex(crypto.FromECDSAPub(message.Dst)) + msg.Dst = common.ToHex(crypto.FromECDSAPub(message.Dst)) } if isMessageSigned(message.Raw[0]) { - msg.From = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())) + msg.Src = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())) } return &msg } diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index ea0a2c40b..9207c6f10 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ) func TestBasic(t *testing.T) { @@ -42,12 +43,12 @@ func TestBasic(t *testing.T) { t.Fatalf("wrong version: %d.", ver) } - mail := api.GetFilterChanges("non-existent-id") + mail := api.GetSubscriptionMessages("non-existent-id") if len(mail) != 0 { t.Fatalf("failed GetFilterChanges: premature result") } - exist, err := api.HasIdentity(id) + exist, err := api.HasKeyPair(id) if err != nil { t.Fatalf("failed initial HasIdentity: %s.", err) } @@ -55,12 +56,15 @@ func TestBasic(t *testing.T) { t.Fatalf("failed initial HasIdentity: false positive.") } - err = api.DeleteIdentity(id) + success, err := api.DeleteKeyPair(id) if err != nil { t.Fatalf("failed DeleteIdentity: %s.", err) } + if success { + t.Fatalf("deleted non-existing identity: false positive.") + } - pub, err := api.NewIdentity() + pub, err := api.NewKeyPair() if err != nil { t.Fatalf("failed NewIdentity: %s.", err) } @@ -68,7 +72,7 @@ func TestBasic(t *testing.T) { t.Fatalf("failed NewIdentity: empty") } - exist, err = api.HasIdentity(pub) + exist, err = api.HasKeyPair(pub) if err != nil { t.Fatalf("failed HasIdentity: %s.", err) } @@ -76,12 +80,15 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasIdentity: false negative.") } - err = api.DeleteIdentity(pub) + success, err = api.DeleteKeyPair(pub) if err != nil { t.Fatalf("failed to delete second identity: %s.", err) } + if !success { + t.Fatalf("failed to delete second identity.") + } - exist, err = api.HasIdentity(pub) + exist, err = api.HasKeyPair(pub) if err != nil { t.Fatalf("failed HasIdentity(): %s.", err) } @@ -92,7 +99,7 @@ func TestBasic(t *testing.T) { id = "arbitrary text" id2 := "another arbitrary string" - exist, err = api.HasSymKey(id) + exist, err = api.HasSymmetricKey(id) if err != nil { t.Fatalf("failed HasSymKey: %s.", err) } @@ -100,12 +107,12 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey: false positive.") } - err = api.GenerateSymKey(id) + id, err = api.GenerateSymmetricKey() if err != nil { t.Fatalf("failed GenerateSymKey: %s.", err) } - exist, err = api.HasSymKey(id) + exist, err = api.HasSymmetricKey(id) if err != nil { t.Fatalf("failed HasSymKey(): %s.", err) } @@ -113,17 +120,18 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey(): false negative.") } - err = api.AddSymKey(id, []byte("some stuff here")) - if err == nil { + const password = "some stuff here" + id, err = api.AddSymmetricKeyFromPassword(password) + if err != nil { t.Fatalf("failed AddSymKey: %s.", err) } - err = api.AddSymKey(id2, []byte("some stuff here")) + id2, err = api.AddSymmetricKeyFromPassword(password) if err != nil { t.Fatalf("failed AddSymKey: %s.", err) } - exist, err = api.HasSymKey(id2) + exist, err = api.HasSymmetricKey(id2) if err != nil { t.Fatalf("failed HasSymKey(id2): %s.", err) } @@ -131,12 +139,28 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey(id2): false negative.") } - err = api.DeleteSymKey(id) + k1, err := api.GetSymmetricKey(id) + if err != nil { + t.Fatalf("failed GetSymKey(id): %s.", err) + } + k2, err := api.GetSymmetricKey(id2) + if err != nil { + t.Fatalf("failed GetSymKey(id2): %s.", err) + } + + if !bytes.Equal(k1, k2) { + t.Fatalf("installed keys are not equal") + } + + exist, err = api.DeleteSymmetricKey(id) if err != nil { t.Fatalf("failed DeleteSymKey(id): %s.", err) } + if !exist { + t.Fatalf("failed DeleteSymKey(id): false negative.") + } - exist, err = api.HasSymKey(id) + exist, err = api.HasSymmetricKey(id) if err != nil { t.Fatalf("failed HasSymKey(id): %s.", err) } @@ -147,12 +171,12 @@ func TestBasic(t *testing.T) { func TestUnmarshalFilterArgs(t *testing.T) { s := []byte(`{ - "to":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", - "from":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", - "keyname":"testname", - "pow":2.34, + "type":"sym", + "key":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", + "signedWith":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", + "minPoW":2.34, "topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"], - "p2p":true + "allowP2P":true }`) var f WhisperFilterArgs @@ -161,59 +185,58 @@ func TestUnmarshalFilterArgs(t *testing.T) { t.Fatalf("failed UnmarshalJSON: %s.", err) } - if f.To != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { - t.Fatalf("wrong To: %x.", f.To) + if !f.Symmetric { + t.Fatalf("wrong type.") } - if f.From != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { - t.Fatalf("wrong From: %x.", f.To) + if f.Key != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { + t.Fatalf("wrong key: %s.", f.Key) } - if f.KeyName != "testname" { - t.Fatalf("wrong KeyName: %s.", f.KeyName) + if f.SignedWith != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { + t.Fatalf("wrong SignedWith: %s.", f.SignedWith) } - if f.PoW != 2.34 { - t.Fatalf("wrong pow: %f.", f.PoW) + if f.MinPoW != 2.34 { + t.Fatalf("wrong MinPoW: %f.", f.MinPoW) } - if !f.AcceptP2P { - t.Fatalf("wrong AcceptP2P: %v.", f.AcceptP2P) + if !f.AllowP2P { + t.Fatalf("wrong AllowP2P.") } if len(f.Topics) != 4 { t.Fatalf("wrong topics number: %d.", len(f.Topics)) } i := 0 - if f.Topics[i] != (TopicType{0x00, 0x00, 0x00, 0x00}) { + if !bytes.Equal(f.Topics[i], []byte{0x00, 0x00, 0x00, 0x00}) { t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i]) } i++ - if f.Topics[i] != (TopicType{0x00, 0x7f, 0x80, 0xff}) { + if !bytes.Equal(f.Topics[i], []byte{0x00, 0x7f, 0x80, 0xff}) { t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i]) } i++ - if f.Topics[i] != (TopicType{0xff, 0x80, 0x7f, 0x00}) { + if !bytes.Equal(f.Topics[i], []byte{0xff, 0x80, 0x7f, 0x00}) { t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i]) } i++ - if f.Topics[i] != (TopicType{0xf2, 0x6e, 0x77, 0x79}) { + if !bytes.Equal(f.Topics[i], []byte{0xf2, 0x6e, 0x77, 0x79}) { t.Fatalf("wrong topic[%d]: %x.", i, f.Topics[i]) } } func TestUnmarshalPostArgs(t *testing.T) { s := []byte(`{ + "type":"sym", "ttl":12345, - "from":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", - "to":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", - "keyname":"shh_test", + "signWith":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", + "key":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "topic":"0xf26e7779", "padding":"0x74686973206973206D79207465737420737472696E67", "payload":"0x7061796C6F61642073686F756C642062652070736575646F72616E646F6D", - "worktime":777, - "pow":3.1416, - "filterid":"test-filter-id", - "peerid":"0xf26e7779" + "powTime":777, + "powTarget":3.1416, + "targetPeer":"enode://915533f667b1369793ebb9bda022416b1295235a1420799cd87a969467372546d808ebf59c5c9ce23f103d59b61b97df8af91f0908552485975397181b993461@127.0.0.1:12345" }`) var a PostArgs @@ -222,19 +245,20 @@ func TestUnmarshalPostArgs(t *testing.T) { t.Fatalf("failed UnmarshalJSON: %s.", err) } + if a.Type != "sym" { + t.Fatalf("wrong Type: %s.", a.Type) + } if a.TTL != 12345 { t.Fatalf("wrong ttl: %d.", a.TTL) } - if a.From != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { - t.Fatalf("wrong From: %x.", a.To) - } - if a.To != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { - t.Fatalf("wrong To: %x.", a.To) + if a.SignWith != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { + t.Fatalf("wrong From: %s.", a.SignWith) } - if a.KeyName != "shh_test" { - t.Fatalf("wrong KeyName: %s.", a.KeyName) + if a.Key != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { + t.Fatalf("wrong Key: %s.", a.Key) } - if a.Topic != (TopicType{0xf2, 0x6e, 0x77, 0x79}) { + + if BytesToTopic(a.Topic) != (TopicType{0xf2, 0x6e, 0x77, 0x79}) { t.Fatalf("wrong topic: %x.", a.Topic) } if string(a.Padding) != "this is my test string" { @@ -243,31 +267,34 @@ func TestUnmarshalPostArgs(t *testing.T) { if string(a.Payload) != "payload should be pseudorandom" { t.Fatalf("wrong Payload: %s.", string(a.Payload)) } - if a.WorkTime != 777 { - t.Fatalf("wrong WorkTime: %d.", a.WorkTime) - } - if a.PoW != 3.1416 { - t.Fatalf("wrong pow: %f.", a.PoW) + if a.PowTime != 777 { + t.Fatalf("wrong PowTime: %d.", a.PowTime) } - if a.FilterID != "test-filter-id" { - t.Fatalf("wrong FilterID: %s.", a.FilterID) + if a.PowTarget != 3.1416 { + t.Fatalf("wrong PowTarget: %f.", a.PowTarget) } - if !bytes.Equal(a.PeerID[:], a.Topic[:]) { - t.Fatalf("wrong PeerID: %x.", a.PeerID) + if a.TargetPeer != "enode://915533f667b1369793ebb9bda022416b1295235a1420799cd87a969467372546d808ebf59c5c9ce23f103d59b61b97df8af91f0908552485975397181b993461@127.0.0.1:12345" { + t.Fatalf("wrong PeerID: %s.", a.TargetPeer) } } -func waitForMessage(api *PublicWhisperAPI, id string, target int) bool { - for i := 0; i < 64; i++ { - all := api.GetMessages(id) - if len(all) >= target { - return true +func waitForMessages(api *PublicWhisperAPI, id string, target int) []*WhisperMessage { + // timeout: 2 seconds + result := make([]*WhisperMessage, 0, target) + for i := 0; i < 100; i++ { + mail := api.GetSubscriptionMessages(id) + if len(mail) > 0 { + for _, m := range mail { + result = append(result, m) + } + if len(result) >= target { + break + } } - time.Sleep(time.Millisecond * 16) + time.Sleep(time.Millisecond * 20) } - // timeout 1024 milliseconds - return false + return result } func TestIntegrationAsym(t *testing.T) { @@ -280,7 +307,7 @@ func TestIntegrationAsym(t *testing.T) { api.Start() defer api.Stop() - sig, err := api.NewIdentity() + sig, err := api.NewKeyPair() if err != nil { t.Fatalf("failed NewIdentity: %s.", err) } @@ -288,7 +315,7 @@ func TestIntegrationAsym(t *testing.T) { t.Fatalf("wrong signature") } - exist, err := api.HasIdentity(sig) + exist, err := api.HasKeyPair(sig) if err != nil { t.Fatalf("failed HasIdentity: %s.", err) } @@ -296,7 +323,12 @@ func TestIntegrationAsym(t *testing.T) { t.Fatalf("failed HasIdentity: false negative.") } - key, err := api.NewIdentity() + sigPubKey, err := api.GetPublicKey(sig) + if err != nil { + t.Fatalf("failed GetPublicKey: %s.", err) + } + + key, err := api.NewKeyPair() if err != nil { t.Fatalf("failed NewIdentity(): %s.", err) } @@ -304,42 +336,46 @@ func TestIntegrationAsym(t *testing.T) { t.Fatalf("wrong key") } + dstPubKey, err := api.GetPublicKey(key) + if err != nil { + t.Fatalf("failed GetPublicKey: %s.", err) + } + var topics [2]TopicType topics[0] = TopicType{0x00, 0x64, 0x00, 0xff} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} var f WhisperFilterArgs - f.To = key - f.From = sig - f.Topics = topics[:] - f.PoW = MinimumPoW / 2 - f.AcceptP2P = true + f.Symmetric = false + f.Key = key + f.SignedWith = sigPubKey.String() + f.Topics = make([][]byte, 2) + f.Topics[0] = topics[0][:] + f.Topics[1] = topics[1][:] + f.MinPoW = DefaultMinimumPoW / 2 + f.AllowP2P = true - id, err := api.NewFilter(f) + id, err := api.Subscribe(f) if err != nil { t.Fatalf("failed to create new filter: %s.", err) } var p PostArgs + p.Type = "asym" p.TTL = 2 - p.From = f.From - p.To = f.To + p.SignWith = sig + p.Key = dstPubKey.String() p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = MinimumPoW - p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} - p.WorkTime = 2 + p.PowTarget = DefaultMinimumPoW + p.PowTime = 2 + p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79} // topics[1] err = api.Post(p) if err != nil { t.Errorf("failed to post message: %s.", err) } - ok := waitForMessage(api, id, 1) - if !ok { - t.Fatalf("failed to receive first message: timeout.") - } - - mail := api.GetFilterChanges(id) + mail := waitForMessages(api, id, 1) if len(mail) != 1 { t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) } @@ -356,12 +392,7 @@ func TestIntegrationAsym(t *testing.T) { t.Fatalf("failed to post next message: %s.", err) } - ok = waitForMessage(api, id, 2) - if !ok { - t.Fatalf("failed to receive second message: timeout.") - } - - mail = api.GetFilterChanges(id) + mail = waitForMessages(api, id, 1) if len(mail) != 1 { t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) } @@ -382,21 +413,25 @@ func TestIntegrationSym(t *testing.T) { api.Start() defer api.Stop() - keyname := "schluessel" - err := api.GenerateSymKey(keyname) + symKeyID, err := api.GenerateSymmetricKey() if err != nil { t.Fatalf("failed GenerateSymKey: %s.", err) } - sig, err := api.NewIdentity() + sig, err := api.NewKeyPair() if err != nil { - t.Fatalf("failed NewIdentity: %s.", err) + t.Fatalf("failed NewKeyPair: %s.", err) } if len(sig) == 0 { t.Fatalf("wrong signature") } - exist, err := api.HasIdentity(sig) + sigPubKey, err := api.GetPublicKey(sig) + if err != nil { + t.Fatalf("failed GetPublicKey: %s.", err) + } + + exist, err := api.HasKeyPair(sig) if err != nil { t.Fatalf("failed HasIdentity: %s.", err) } @@ -408,38 +443,37 @@ func TestIntegrationSym(t *testing.T) { topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} var f WhisperFilterArgs - f.KeyName = keyname - f.Topics = topics[:] - f.PoW = 0.324 - f.From = sig - f.AcceptP2P = false + f.Symmetric = true + f.Key = symKeyID + f.Topics = make([][]byte, 2) + f.Topics[0] = topics[0][:] + f.Topics[1] = topics[1][:] + f.MinPoW = 0.324 + f.SignedWith = sigPubKey.String() + f.AllowP2P = false - id, err := api.NewFilter(f) + id, err := api.Subscribe(f) if err != nil { t.Fatalf("failed to create new filter: %s.", err) } var p PostArgs + p.Type = "sym" p.TTL = 1 - p.KeyName = keyname - p.From = f.From + p.Key = symKeyID + p.SignWith = sig p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = MinimumPoW - p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} - p.WorkTime = 2 + p.PowTarget = DefaultMinimumPoW + p.PowTime = 2 + p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79} err = api.Post(p) if err != nil { t.Fatalf("failed to post first message: %s.", err) } - ok := waitForMessage(api, id, 1) - if !ok { - t.Fatalf("failed to receive first message: timeout.") - } - - mail := api.GetFilterChanges(id) + mail := waitForMessages(api, id, 1) if len(mail) != 1 { t.Fatalf("failed GetFilterChanges: got %d messages.", len(mail)) } @@ -456,12 +490,7 @@ func TestIntegrationSym(t *testing.T) { t.Fatalf("failed to post second message: %s.", err) } - ok = waitForMessage(api, id, 2) - if !ok { - t.Fatalf("failed to receive second message: timeout.") - } - - mail = api.GetFilterChanges(id) + mail = waitForMessages(api, id, 1) if len(mail) != 1 { t.Fatalf("failed second GetFilterChanges: got %d messages.", len(mail)) } @@ -482,21 +511,20 @@ func TestIntegrationSymWithFilter(t *testing.T) { api.Start() defer api.Stop() - keyname := "schluessel" - err := api.GenerateSymKey(keyname) + symKeyID, err := api.GenerateSymmetricKey() if err != nil { t.Fatalf("failed to GenerateSymKey: %s.", err) } - sig, err := api.NewIdentity() + sigKeyID, err := api.NewKeyPair() if err != nil { t.Fatalf("failed NewIdentity: %s.", err) } - if len(sig) == 0 { + if len(sigKeyID) == 0 { t.Fatalf("wrong signature.") } - exist, err := api.HasIdentity(sig) + exist, err := api.HasKeyPair(sigKeyID) if err != nil { t.Fatalf("failed HasIdentity: %s.", err) } @@ -504,42 +532,46 @@ func TestIntegrationSymWithFilter(t *testing.T) { t.Fatalf("failed HasIdentity: does not exist.") } + sigPubKey, err := api.GetPublicKey(sigKeyID) + if err != nil { + t.Fatalf("failed GetPublicKey: %s.", err) + } + var topics [2]TopicType topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} var f WhisperFilterArgs - f.KeyName = keyname - f.Topics = topics[:] - f.PoW = 0.324 - f.From = sig - f.AcceptP2P = false + f.Symmetric = true + f.Key = symKeyID + f.Topics = make([][]byte, 2) + f.Topics[0] = topics[0][:] + f.Topics[1] = topics[1][:] + f.MinPoW = 0.324 + f.SignedWith = sigPubKey.String() + f.AllowP2P = false - id, err := api.NewFilter(f) + id, err := api.Subscribe(f) if err != nil { t.Fatalf("failed to create new filter: %s.", err) } var p PostArgs + p.Type = "sym" p.TTL = 1 - p.FilterID = id - p.From = sig + p.Key = symKeyID + p.SignWith = sigKeyID p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = MinimumPoW - p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} - p.WorkTime = 2 + p.PowTarget = DefaultMinimumPoW + p.PowTime = 2 + p.Topic = hexutil.Bytes{0xf2, 0x6e, 0x77, 0x79} err = api.Post(p) if err != nil { t.Fatalf("failed to post message: %s.", err) } - ok := waitForMessage(api, id, 1) - if !ok { - t.Fatalf("failed to receive first message: timeout.") - } - - mail := api.GetFilterChanges(id) + mail := waitForMessages(api, id, 1) if len(mail) != 1 { t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) } @@ -556,12 +588,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { t.Fatalf("failed to post next message: %s.", err) } - ok = waitForMessage(api, id, 2) - if !ok { - t.Fatalf("failed to receive second message: timeout.") - } - - mail = api.GetFilterChanges(id) + mail = waitForMessages(api, id, 1) if len(mail) != 1 { t.Fatalf("failed to GetFilterChanges: got %d messages.", len(mail)) } @@ -571,3 +598,83 @@ func TestIntegrationSymWithFilter(t *testing.T) { t.Fatalf("failed to decrypt second message: %s.", text) } } + +func TestKey(t *testing.T) { + w := New() + api := NewPublicWhisperAPI(w) + if api == nil { + t.Fatalf("failed to create API.") + } + + k, err := api.AddSymmetricKeyFromPassword("wwww") + if err != nil { + t.Fatalf("failed to create key: %s.", err) + } + + s, err := api.GetSymmetricKey(k) + if err != nil { + t.Fatalf("failed to get sym key: %s.", err) + } + + k2, err := api.AddSymmetricKeyDirect(s) + if err != nil { + t.Fatalf("failed to add sym key: %s.", err) + } + + s2, err := api.GetSymmetricKey(k2) + if err != nil { + t.Fatalf("failed to get sym key: %s.", err) + } + + if s.String() != "0x448652d595bd6ec00b2a9ea220ad6c26592d9bf4cf79023d3c1b30cb681e6e07" { + t.Fatalf("wrong key from password: %s", s.String()) + } + + if !bytes.Equal(s, s2) { + t.Fatalf("wrong key") + } +} + +func TestSubscribe(t *testing.T) { + var err error + var s string + + w := New() + api := NewPublicWhisperAPI(w) + if api == nil { + t.Fatalf("failed to create API.") + } + + symKeyID, err := api.GenerateSymmetricKey() + if err != nil { + t.Fatalf("failed to GenerateSymKey: %s.", err) + } + + var f WhisperFilterArgs + f.Symmetric = true + f.Key = symKeyID + f.Topics = make([][]byte, 5) + f.Topics[0] = []byte{0x21} + f.Topics[1] = []byte{0xd2, 0xe3} + f.Topics[2] = []byte{0x64, 0x75, 0x76} + f.Topics[3] = []byte{0xf8, 0xe9, 0xa0, 0xba} + f.Topics[4] = []byte{0xcb, 0x3c, 0xdd, 0xee, 0xff} + + s, err = api.Subscribe(f) + if err == nil { + t.Fatalf("Subscribe: false positive.") + } + + f.Topics[4] = []byte{} + if err == nil { + t.Fatalf("Subscribe: false positive again.") + } + + f.Topics[4] = []byte{0x00} + s, err = api.Subscribe(f) + if err != nil { + t.Fatalf("failed to subscribe: %s.", err) + } else { + api.Unsubscribe(s) + } +} diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 70c7008a7..d60868f67 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -54,9 +54,10 @@ const ( aesKeyLength = 32 saltLength = 12 AESNonceMaxLength = 12 + keyIdSize = 32 - MaxMessageLength = 0x0FFFFF // todo: remove this restriction after testing. this should be regulated by PoW. - MinimumPoW = 10.0 // todo: review after testing. + DefaultMaxMessageLength = 1024 * 1024 + DefaultMinimumPoW = 1.0 // todo: review after testing. padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go index 5d882d5dc..dffa7b286 100644 --- a/whisper/whisperv5/envelope.go +++ b/whisper/whisperv5/envelope.go @@ -21,7 +21,6 @@ package whisperv5 import ( "crypto/ecdsa" "encoding/binary" - "errors" "fmt" gmath "math" "math/big" @@ -83,7 +82,7 @@ func (e *Envelope) isAsymmetric() bool { } func (e *Envelope) Ver() uint64 { - return bytesToIntLittleEndian(e.Version) + return bytesToUintLittleEndian(e.Version) } // Seal closes the envelope by spending the requested amount of time as a proof @@ -95,6 +94,9 @@ func (e *Envelope) Seal(options *MessageParams) error { e.Expiry += options.WorkTime } else { target = e.powToFirstBit(options.PoW) + if target < 1 { + target = 1 + } } buf := make([]byte, 64) @@ -118,7 +120,7 @@ func (e *Envelope) Seal(options *MessageParams) error { } if target > 0 && bestBit < target { - return errors.New("Failed to reach the PoW target, insufficient work time") + return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime) } return nil diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index ffa5ae946..03101d4a4 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -18,7 +18,6 @@ package whisperv5 import ( "crypto/ecdsa" - crand "crypto/rand" "fmt" "sync" @@ -30,9 +29,9 @@ type Filter struct { Src *ecdsa.PublicKey // Sender of the message KeyAsym *ecdsa.PrivateKey // Private Key of recipient KeySym []byte // Key associated with the Topic - Topics []TopicType // Topics to filter messages with + Topics [][]byte // Topics to filter messages with PoW float64 // Proof of work as described in the Whisper spec - AcceptP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages + AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization Messages map[common.Hash]*ReceivedMessage @@ -52,47 +51,35 @@ func NewFilters(w *Whisper) *Filters { } } -func (fs *Filters) generateRandomID() (id string, err error) { - buf := make([]byte, 20) - for i := 0; i < 3; i++ { - _, err = crand.Read(buf) - if err != nil { - continue - } - if !validateSymmetricKey(buf) { - err = fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") - continue - } - id = common.Bytes2Hex(buf) - if fs.watchers[id] != nil { - err = fmt.Errorf("error in generateRandomID: generated same ID twice") - continue - } - return id, err - } - - return "", err -} - func (fs *Filters) Install(watcher *Filter) (string, error) { if watcher.Messages == nil { watcher.Messages = make(map[common.Hash]*ReceivedMessage) } + id, err := GenerateRandomID() + if err != nil { + return "", err + } + fs.mutex.Lock() defer fs.mutex.Unlock() - id, err := fs.generateRandomID() - if err == nil { - fs.watchers[id] = watcher + if fs.watchers[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") } + + fs.watchers[id] = watcher return id, err } -func (fs *Filters) Uninstall(id string) { +func (fs *Filters) Uninstall(id string) bool { fs.mutex.Lock() defer fs.mutex.Unlock() - delete(fs.watchers, id) + if fs.watchers[id] != nil { + delete(fs.watchers, id) + return true + } + return false } func (fs *Filters) Get(id string) *Filter { @@ -102,11 +89,16 @@ func (fs *Filters) Get(id string) *Filter { } func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { - fs.mutex.RLock() var msg *ReceivedMessage - for j, watcher := range fs.watchers { - if p2pMessage && !watcher.AcceptP2P { - log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), j)) + + fs.mutex.RLock() + defer fs.mutex.RUnlock() + + i := -1 // only used for logging info + for _, watcher := range fs.watchers { + i++ + if p2pMessage && !watcher.AllowP2P { + log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i)) continue } @@ -118,22 +110,32 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { if match { msg = env.Open(watcher) if msg == nil { - log.Trace(fmt.Sprintf("msg [%x], filter [%s]: failed to open", env.Hash(), j)) + log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", i) } } else { - log.Trace(fmt.Sprintf("msg [%x], filter [%s]: does not match", env.Hash(), j)) + log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i) } } if match && msg != nil { + log.Trace("processing message: decrypted", "hash", env.Hash().Hex()) watcher.Trigger(msg) } } - fs.mutex.RUnlock() // we need to unlock before calling addDecryptedMessage +} - if msg != nil { - fs.whisper.addDecryptedMessage(msg) +func (f *Filter) processEnvelope(env *Envelope) *ReceivedMessage { + if f.MatchEnvelope(env) { + msg := env.Open(f) + if msg != nil { + return msg + } else { + log.Trace("processing envelope: failed to open", "hash", env.Hash().Hex()) + } + } else { + log.Trace("processing envelope: does not match", "hash", env.Hash().Hex()) } + return nil } func (f *Filter) expectsAsymmetricEncryption() bool { @@ -200,20 +202,33 @@ func (f *Filter) MatchTopic(topic TopicType) bool { return true } - for _, t := range f.Topics { - if t == topic { + for _, bt := range f.Topics { + if matchSingleTopic(topic, bt) { return true } } return false } +func matchSingleTopic(topic TopicType, bt []byte) bool { + if len(bt) > 4 { + bt = bt[:4] + } + + for j, b := range bt { + if topic[j] != b { + return false + } + } + return true +} + func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { if !ValidatePublicKey(a) { return false } else if !ValidatePublicKey(b) { return false } - // the Curve is always the same, just compare the points + // the curve is always the same, just compare the points return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 } diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go index 1cf85b8d7..ae21d1739 100644 --- a/whisper/whisperv5/filter_test.go +++ b/whisper/whisperv5/filter_test.go @@ -53,8 +53,9 @@ func generateFilter(t *testing.T, symmetric bool) (*Filter, error) { f.Messages = make(map[common.Hash]*ReceivedMessage) const topicNum = 8 - f.Topics = make([]TopicType, topicNum) + f.Topics = make([][]byte, topicNum) for i := 0; i < topicNum; i++ { + f.Topics[i] = make([]byte, 4) mrand.Read(f.Topics[i][:]) f.Topics[i][0] = 0x01 } @@ -108,7 +109,7 @@ func TestInstallFilters(t *testing.T) { t.Fatalf("seed %d: failed to install filter: %s", seed, err) } tst[i].id = j - if len(j) != 40 { + if len(j) != keyIdSize*2 { t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j)) } } @@ -194,8 +195,8 @@ func TestMatchEnvelope(t *testing.T) { // encrypt symmetrically i := mrand.Int() % 4 - fsym.Topics[i] = params.Topic - fasym.Topics[i] = params.Topic + fsym.Topics[i] = params.Topic[:] + fasym.Topics[i] = params.Topic[:] msg = NewSentMessage(params) env, err = msg.Wrap(params) if err != nil { @@ -320,7 +321,7 @@ func TestMatchMessageSym(t *testing.T) { const index = 1 params.KeySym = f.KeySym - params.Topic = f.Topics[index] + params.Topic = BytesToTopic(f.Topics[index]) sentMessage := NewSentMessage(params) env, err := sentMessage.Wrap(params) @@ -413,7 +414,7 @@ func TestMatchMessageAsym(t *testing.T) { } const index = 1 - params.Topic = f.Topics[index] + params.Topic = BytesToTopic(f.Topics[index]) params.Dst = &f.KeyAsym.PublicKey keySymOrig := params.KeySym params.KeySym = nil @@ -491,7 +492,7 @@ func cloneFilter(orig *Filter) *Filter { clone.KeySym = orig.KeySym clone.Topics = orig.Topics clone.PoW = orig.PoW - clone.AcceptP2P = orig.AcceptP2P + clone.AllowP2P = orig.AllowP2P clone.SymKeyHash = orig.SymKeyHash return &clone } @@ -504,7 +505,7 @@ func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope { } params.KeySym = f.KeySym - params.Topic = f.Topics[2] + params.Topic = BytesToTopic(f.Topics[2]) sentMessage := NewSentMessage(params) env, err := sentMessage.Wrap(params) if err != nil { @@ -655,7 +656,7 @@ func TestWatchers(t *testing.T) { if f == nil { t.Fatalf("failed to get the filter with seed %d.", seed) } - f.AcceptP2P = true + f.AllowP2P = true total = 0 filters.NotifyWatchers(envelopes[0], true) @@ -668,3 +669,40 @@ func TestWatchers(t *testing.T) { t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total) } } + +func TestVariableTopics(t *testing.T) { + InitSingleTest() + + var match bool + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg := NewSentMessage(params) + env, err := msg.Wrap(params) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + f, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) + } + + for i := 0; i < 4; i++ { + arr := make([]byte, i+1, 4) + copy(arr, env.Topic[:i+1]) + + f.Topics[4] = arr + match = f.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i) + } + + f.Topics[4][i]++ + match = f.MatchEnvelope(env) + if match { + t.Fatalf("MatchEnvelope symmetric with seed %d, step %d: false positive.", seed, i) + } + } +} diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index 5f964b072..9b9c389a6 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -25,8 +25,6 @@ import ( crand "crypto/rand" "crypto/sha256" "errors" - "fmt" - mrand "math/rand" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -102,14 +100,18 @@ func NewSentMessage(params *MessageParams) *SentMessage { msg := SentMessage{} msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper) msg.Raw[0] = 0 // set all the flags to zero - msg.appendPadding(params) + err := msg.appendPadding(params) + if err != nil { + log.Error("failed to create NewSentMessage", "err", err) + return nil + } msg.Raw = append(msg.Raw, params.Payload...) return &msg } // appendPadding appends the pseudorandom padding bytes and sets the padding flag. // The last byte contains the size of padding (thus, its size must not exceed 256). -func (msg *SentMessage) appendPadding(params *MessageParams) { +func (msg *SentMessage) appendPadding(params *MessageParams) error { total := len(params.Payload) + 1 if params.Src != nil { total += signatureLength @@ -128,7 +130,10 @@ func (msg *SentMessage) appendPadding(params *MessageParams) { panic("please fix the padding algorithm before releasing new version") } buf := make([]byte, padSize) - mrand.Read(buf[1:]) + _, err := crand.Read(buf[1:]) + if err != nil { + return err + } buf[0] = byte(padSize) if params.Padding != nil { copy(buf[1:], params.Padding) @@ -136,6 +141,7 @@ func (msg *SentMessage) appendPadding(params *MessageParams) { msg.Raw = append(msg.Raw, buf...) msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size } + return nil } // sign calculates and sets the cryptographic signature for the message, @@ -143,7 +149,7 @@ func (msg *SentMessage) appendPadding(params *MessageParams) { func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error { if isMessageSigned(msg.Raw[0]) { // this should not happen, but no reason to panic - log.Error(fmt.Sprintf("Trying to sign a message which was already signed")) + log.Error("failed to sign the message: already signed") return nil } @@ -161,7 +167,7 @@ func (msg *SentMessage) sign(key *ecdsa.PrivateKey) error { // encryptAsymmetric encrypts a message with a public key. func (msg *SentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error { if !ValidatePublicKey(key) { - return fmt.Errorf("Invalid public key provided for asymmetric encryption") + return errors.New("invalid public key provided for asymmetric encryption") } encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil) if err == nil { @@ -215,17 +221,6 @@ func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte, } // Wrap bundles the message into an Envelope to transmit over the network. -// -// pow (Proof Of Work) controls how much time to spend on hashing the message, -// inherently controlling its priority through the network (smaller hash, bigger -// priority). -// -// The user can control the amount of identity, privacy and encryption through -// the options parameter as follows: -// - options.From == nil && options.To == nil: anonymous broadcast -// - options.From != nil && options.To == nil: signed broadcast (known sender) -// - options.From == nil && options.To != nil: encrypted anonymous message -// - options.From != nil && options.To != nil: encrypted signed message func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) { if options.TTL == 0 { options.TTL = DefaultTTL @@ -236,17 +231,13 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er return nil, err } } - if len(msg.Raw) > MaxMessageLength { - log.Error(fmt.Sprintf("Message size must not exceed %d bytes", MaxMessageLength)) - return nil, errors.New("Oversized message") - } var salt, nonce []byte if options.Dst != nil { err = msg.encryptAsymmetric(options.Dst) } else if options.KeySym != nil { salt, nonce, err = msg.encryptSymmetric(options.KeySym) } else { - err = errors.New("Unable to encrypt the message: neither Dst nor Key") + err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") } if err != nil { @@ -258,7 +249,6 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er if err != nil { return nil, err } - return envelope, nil } @@ -279,9 +269,8 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []by return err } if len(nonce) != aesgcm.NonceSize() { - info := fmt.Sprintf("Wrong AES nonce size - want: %d, got: %d", len(nonce), aesgcm.NonceSize()) - log.Error(fmt.Sprintf(info)) - return errors.New(info) + log.Error("decrypting the message", "AES nonce size", len(nonce)) + return errors.New("wrong AES nonce size") } decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil) if err != nil { @@ -336,7 +325,7 @@ func (msg *ReceivedMessage) extractPadding(end int) (int, bool) { paddingSize := 0 sz := int(msg.Raw[0] & paddingMask) // number of bytes containing the entire size of padding, could be zero if sz != 0 { - paddingSize = int(bytesToIntLittleEndian(msg.Raw[1 : 1+sz])) + paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz])) if paddingSize < sz || paddingSize+1 > end { return 0, false } @@ -351,7 +340,7 @@ func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { pub, err := crypto.SigToPub(msg.hash(), msg.Signature) if err != nil { - log.Error(fmt.Sprintf("Could not get public key from signature: %v", err)) + log.Error("failed to recover public key from signature", "err", err) return nil } return pub diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go index 315401aea..184c4ebf8 100644 --- a/whisper/whisperv5/peer.go +++ b/whisper/whisperv5/peer.go @@ -55,13 +55,13 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer { // into the network. func (p *Peer) start() { go p.update() - log.Debug(fmt.Sprintf("%v: whisper started", p.peer)) + log.Trace("start", "peer", p.ID()) } // stop terminates the peer updater, stopping message forwarding to it. func (p *Peer) stop() { close(p.quit) - log.Debug(fmt.Sprintf("%v: whisper stopped", p.peer)) + log.Trace("stop", "peer", p.ID()) } // handshake sends the protocol initiation status message to the remote peer and @@ -78,19 +78,19 @@ func (p *Peer) handshake() error { return err } if packet.Code != statusCode { - return fmt.Errorf("peer sent %x before status packet", packet.Code) + return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code) } s := rlp.NewStream(packet.Payload, uint64(packet.Size)) peerVersion, err := s.Uint() if err != nil { - return fmt.Errorf("bad status message: %v", err) + return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err) } if peerVersion != ProtocolVersion { - return fmt.Errorf("protocol version mismatch %d != %d", peerVersion, ProtocolVersion) + return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion) } // Wait until out own status is consumed too if err := <-errc; err != nil { - return fmt.Errorf("failed to send status packet: %v", err) + return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err) } return nil } @@ -110,7 +110,7 @@ func (p *Peer) update() { case <-transmit.C: if err := p.broadcast(); err != nil { - log.Info(fmt.Sprintf("%v: broadcast failed: %v", p.peer, err)) + log.Trace("broadcast failed", "reason", err, "peer", p.ID()) return } @@ -165,7 +165,7 @@ func (p *Peer) broadcast() error { if err := p2p.Send(p.ws, messagesCode, transmit); err != nil { return err } - log.Trace(fmt.Sprint(p.peer, "broadcasted", len(transmit), "message(s)")) + log.Trace("broadcast", "num. messages", len(transmit)) return nil } diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index e3073bc6c..a79b6ad14 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -114,12 +114,13 @@ func initialize(t *testing.T) { for i := 0; i < NumNodes; i++ { var node TestNode node.shh = New() - node.shh.test = true + node.shh.SetMinimumPoW(0.00000001) node.shh.Start(nil) topics := make([]TopicType, 0) topics = append(topics, sharedTopic) - f := Filter{KeySym: sharedKey, Topics: topics} - node.filerId, err = node.shh.Watch(&f) + f := Filter{KeySym: sharedKey} + f.Topics = [][]byte{topics[0][:]} + node.filerId, err = node.shh.Subscribe(&f) if err != nil { t.Fatalf("failed to install the filter: %s.", err) } @@ -166,7 +167,7 @@ func stopServers() { for i := 0; i < NumNodes; i++ { n := nodes[i] if n != nil { - n.shh.Unwatch(n.filerId) + n.shh.Unsubscribe(n.filerId) n.shh.Stop() n.server.Stop() } @@ -257,7 +258,7 @@ func sendMsg(t *testing.T, expected bool, id int) { return } - opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001} + opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1} if !expected { opt.KeySym[0]++ opt.Topic[0]++ @@ -267,12 +268,12 @@ func sendMsg(t *testing.T, expected bool, id int) { msg := NewSentMessage(&opt) envelope, err := msg.Wrap(&opt) if err != nil { - t.Fatalf("failed to seal message.") + t.Fatalf("failed to seal message: %s", err) } err = nodes[id].shh.Send(envelope) if err != nil { - t.Fatalf("failed to send message.") + t.Fatalf("failed to send message: %s", err) } } diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 5062f7b6b..c4d5d04a7 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -31,59 +31,62 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" + "github.com/syndtr/goleveldb/leveldb/errors" "golang.org/x/crypto/pbkdf2" set "gopkg.in/fatih/set.v0" ) type Statistics struct { - messagesCleared int - memoryCleared int - totalMemoryUsed int + messagesCleared int + memoryCleared int + memoryUsed int + cycles int + totalMessagesCleared int } // Whisper represents a dark communication interface through the Ethereum // network, using its very own P2P communication layer. type Whisper struct { - protocol p2p.Protocol - filters *Filters + protocol p2p.Protocol // Protocol description and parameters + filters *Filters // Message filters installed with Subscribe function - privateKeys map[string]*ecdsa.PrivateKey - symKeys map[string][]byte - keyMu sync.RWMutex + privateKeys map[string]*ecdsa.PrivateKey // Private key storage + symKeys map[string][]byte // Symmetric key storage + keyMu sync.RWMutex // Mutex associated with key storages - envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node - messages map[common.Hash]*ReceivedMessage // Pool of successfully decrypted messages, which are not expired yet - expirations map[uint32]*set.SetNonTS // Message expiration pool - poolMu sync.RWMutex // Mutex to sync the message and expiration pools + envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node + expirations map[uint32]*set.SetNonTS // Message expiration pool + poolMu sync.RWMutex // Mutex to sync the message and expiration pools peers map[*Peer]struct{} // Set of currently active peers peerMu sync.RWMutex // Mutex to sync the active peer set - mailServer MailServer + messageQueue chan *Envelope // Message queue for normal whisper messages + p2pMsgQueue chan *Envelope // Message queue for peer-to-peer messages (not to be forwarded any further) + quit chan struct{} // Channel used for graceful exit - messageQueue chan *Envelope - p2pMsgQueue chan *Envelope - quit chan struct{} + minPoW float64 // Minimal PoW required by the whisper node + maxMsgLength int // Maximal message length allowed by the whisper node + overflow bool // Indicator of message queue overflow - stats Statistics + stats Statistics // Statistics of whisper node - overflow bool - test bool + mailServer MailServer // MailServer interface } // New creates a Whisper client ready to communicate through the Ethereum P2P network. -// Param s should be passed if you want to implement mail server, otherwise nil. func New() *Whisper { whisper := &Whisper{ privateKeys: make(map[string]*ecdsa.PrivateKey), symKeys: make(map[string][]byte), envelopes: make(map[common.Hash]*Envelope), - messages: make(map[common.Hash]*ReceivedMessage), expirations: make(map[uint32]*set.SetNonTS), peers: make(map[*Peer]struct{}), messageQueue: make(chan *Envelope, messageQueueLimit), p2pMsgQueue: make(chan *Envelope, messageQueueLimit), quit: make(chan struct{}), + minPoW: DefaultMinimumPoW, + maxMsgLength: DefaultMaxMessageLength, } whisper.filters = NewFilters(whisper) @@ -110,6 +113,8 @@ func (w *Whisper) APIs() []rpc.API { } } +// RegisterServer registers MailServer interface. +// MailServer will process all the incoming messages with p2pRequestCode. func (w *Whisper) RegisterServer(server MailServer) { w.mailServer = server } @@ -124,6 +129,25 @@ func (w *Whisper) Version() uint { return w.protocol.Version } +// SetMaxMessageLength sets the maximal message length allowed by this node +func (w *Whisper) SetMaxMessageLength(val int) error { + if val <= 0 { + return fmt.Errorf("invalid message length: %d", val) + } + w.maxMsgLength = val + return nil +} + +// SetMinimumPoW sets the minimal PoW required by this node +func (w *Whisper) SetMinimumPoW(val float64) error { + if val <= 0.0 { + return fmt.Errorf("invalid PoW: %f", val) + } + w.minPoW = val + return nil +} + +// getPeer retrieves peer by ID func (w *Whisper) getPeer(peerID []byte) (*Peer, error) { w.peerMu.Lock() defer w.peerMu.Unlock() @@ -136,9 +160,9 @@ func (w *Whisper) getPeer(peerID []byte) (*Peer, error) { return nil, fmt.Errorf("Could not find peer with ID: %x", peerID) } -// MarkPeerTrusted marks specific peer trusted, which will allow it -// to send historic (expired) messages. -func (w *Whisper) MarkPeerTrusted(peerID []byte) error { +// AllowP2PMessagesFromPeer marks specific peer trusted, +// which will allow it to send historic (expired) messages. +func (w *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error { p, err := w.getPeer(peerID) if err != nil { return err @@ -147,6 +171,11 @@ func (w *Whisper) MarkPeerTrusted(peerID []byte) error { return nil } +// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer, +// which is known to implement MailServer interface, and is supposed to process this +// request and respond with a number of peer-to-peer messages (possibly expired), +// which are not supposed to be forwarded any further. +// The whisper protocol is agnostic of the format and contents of envelope. func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error { p, err := w.getPeer(peerID) if err != nil { @@ -156,153 +185,226 @@ func (w *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) err return p2p.Send(p.ws, p2pRequestCode, envelope) } +// SendP2PMessage sends a peer-to-peer message to a specific peer. func (w *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error { p, err := w.getPeer(peerID) if err != nil { return err } - return p2p.Send(p.ws, p2pCode, envelope) + return w.SendP2PDirect(p, envelope) } +// SendP2PDirect sends a peer-to-peer message to a specific peer. func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error { return p2p.Send(peer.ws, p2pCode, envelope) } -// NewIdentity generates a new cryptographic identity for the client, and injects -// it into the known identities for message decryption. -func (w *Whisper) NewIdentity() *ecdsa.PrivateKey { +// NewKeyPair generates a new cryptographic identity for the client, and injects +// it into the known identities for message decryption. Returns ID of the new key pair. +func (w *Whisper) NewKeyPair() (string, error) { key, err := crypto.GenerateKey() if err != nil || !validatePrivateKey(key) { key, err = crypto.GenerateKey() // retry once } if err != nil { - panic(err) + return "", err } if !validatePrivateKey(key) { - panic("Failed to generate valid key") + return "", fmt.Errorf("failed to generate valid key") } + + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("failed to generate ID: %s", err) + } + w.keyMu.Lock() defer w.keyMu.Unlock() - w.privateKeys[common.ToHex(crypto.FromECDSAPub(&key.PublicKey))] = key - return key + + if w.privateKeys[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") + } + w.privateKeys[id] = key + return id, nil } -// DeleteIdentity deletes the specified key if it exists. -func (w *Whisper) DeleteIdentity(key string) { +// DeleteKeyPair deletes the specified key if it exists. +func (w *Whisper) DeleteKeyPair(key string) bool { w.keyMu.Lock() defer w.keyMu.Unlock() - delete(w.privateKeys, key) + + if w.privateKeys[key] != nil { + delete(w.privateKeys, key) + return true + } + return false } -// HasIdentity checks if the the whisper node is configured with the private key +// HasKeyPair checks if the the whisper node is configured with the private key // of the specified public pair. -func (w *Whisper) HasIdentity(pubKey string) bool { +func (w *Whisper) HasKeyPair(id string) bool { w.keyMu.RLock() defer w.keyMu.RUnlock() - return w.privateKeys[pubKey] != nil + return w.privateKeys[id] != nil } -// GetIdentity retrieves the private key of the specified public identity. -func (w *Whisper) GetIdentity(pubKey string) *ecdsa.PrivateKey { +// GetPrivateKey retrieves the private key of the specified identity. +func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { w.keyMu.RLock() defer w.keyMu.RUnlock() - return w.privateKeys[pubKey] + key := w.privateKeys[id] + if key == nil { + return nil, fmt.Errorf("invalid id") + } + return key, nil } -func (w *Whisper) GenerateSymKey(name string) error { +// GenerateSymKey generates a random symmetric key and stores it under id, +// which is then returned. Will be used in the future for session key exchange. +func (w *Whisper) GenerateSymKey() (string, error) { const size = aesKeyLength * 2 buf := make([]byte, size) _, err := crand.Read(buf) if err != nil { - return err + return "", err } else if !validateSymmetricKey(buf) { - return fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data") + return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data") } key := buf[:aesKeyLength] salt := buf[aesKeyLength:] derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion) if err != nil { - return err + return "", err } else if !validateSymmetricKey(derived) { - return fmt.Errorf("failed to derive valid key") + return "", fmt.Errorf("failed to derive valid key") + } + + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("failed to generate ID: %s", err) } w.keyMu.Lock() defer w.keyMu.Unlock() - if w.symKeys[name] != nil { - return fmt.Errorf("Key with name [%s] already exists", name) + if w.symKeys[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") } - w.symKeys[name] = derived - return nil + w.symKeys[id] = derived + return id, nil } -func (w *Whisper) AddSymKey(name string, key []byte) error { - if w.HasSymKey(name) { - return fmt.Errorf("Key with name [%s] already exists", name) +// AddSymKeyDirect stores the key, and returns its id. +func (w *Whisper) AddSymKeyDirect(key []byte) (string, error) { + if len(key) != aesKeyLength { + return "", fmt.Errorf("wrong key size: %d", len(key)) } - derived, err := deriveKeyMaterial(key, EnvelopeVersion) + id, err := GenerateRandomID() if err != nil { - return err + return "", fmt.Errorf("failed to generate ID: %s", err) } w.keyMu.Lock() defer w.keyMu.Unlock() - // double check is necessary, because deriveKeyMaterial() is slow - if w.symKeys[name] != nil { - return fmt.Errorf("Key with name [%s] already exists", name) + if w.symKeys[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") } - w.symKeys[name] = derived - return nil + w.symKeys[id] = key + return id, nil } -func (w *Whisper) HasSymKey(name string) bool { +// AddSymKeyFromPassword generates the key from password, stores it, and returns its id. +func (w *Whisper) AddSymKeyFromPassword(password string) (string, error) { + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("failed to generate ID: %s", err) + } + if w.HasSymKey(id) { + return "", fmt.Errorf("failed to generate unique ID") + } + + derived, err := deriveKeyMaterial([]byte(password), EnvelopeVersion) + if err != nil { + return "", err + } + + w.keyMu.Lock() + defer w.keyMu.Unlock() + + // double check is necessary, because deriveKeyMaterial() is very slow + if w.symKeys[id] != nil { + return "", fmt.Errorf("critical error: failed to generate unique ID") + } + w.symKeys[id] = derived + return id, nil +} + +// HasSymKey returns true if there is a key associated with the given id. +// Otherwise returns false. +func (w *Whisper) HasSymKey(id string) bool { w.keyMu.RLock() defer w.keyMu.RUnlock() - return w.symKeys[name] != nil + return w.symKeys[id] != nil } -func (w *Whisper) DeleteSymKey(name string) { +// DeleteSymKey deletes the key associated with the name string if it exists. +func (w *Whisper) DeleteSymKey(id string) bool { w.keyMu.Lock() defer w.keyMu.Unlock() - delete(w.symKeys, name) + if w.symKeys[id] != nil { + delete(w.symKeys, id) + return true + } + return false } -func (w *Whisper) GetSymKey(name string) []byte { +// GetSymKey returns the symmetric key associated with the given id. +func (w *Whisper) GetSymKey(id string) ([]byte, error) { w.keyMu.RLock() defer w.keyMu.RUnlock() - return w.symKeys[name] + if w.symKeys[id] != nil { + return w.symKeys[id], nil + } + return nil, fmt.Errorf("non-existent key ID") } -// Watch installs a new message handler to run in case a matching packet arrives -// from the whisper network. -func (w *Whisper) Watch(f *Filter) (string, error) { +// Subscribe installs a new message handler used for filtering, decrypting +// and subsequent storing of incoming messages. +func (w *Whisper) Subscribe(f *Filter) (string, error) { return w.filters.Install(f) } +// GetFilter returns the filter by id. func (w *Whisper) GetFilter(id string) *Filter { return w.filters.Get(id) } -// Unwatch removes an installed message handler. -func (w *Whisper) Unwatch(id string) { - w.filters.Uninstall(id) +// Unsubscribe removes an installed message handler. +func (w *Whisper) Unsubscribe(id string) error { + ok := w.filters.Uninstall(id) + if !ok { + return fmt.Errorf("Unsubscribe: Invalid ID") + } + return nil } // Send injects a message into the whisper send queue, to be distributed in the // network in the coming cycles. func (w *Whisper) Send(envelope *Envelope) error { - _, err := w.add(envelope) + ok, err := w.add(envelope) + if !ok { + return fmt.Errorf("failed to add envelope") + } return err } // Start implements node.Service, starting the background data propagation thread // of the Whisper protocol. func (w *Whisper) Start(*p2p.Server) error { - log.Info(fmt.Sprint("Whisper started")) + log.Info("started whisper v." + ProtocolVersionStr) go w.update() numCPU := runtime.NumCPU() @@ -317,11 +419,11 @@ func (w *Whisper) Start(*p2p.Server) error { // of the Whisper protocol. func (w *Whisper) Stop() error { close(w.quit) - log.Info(fmt.Sprint("Whisper stopped")) + log.Info("whisper stopped") return nil } -// handlePeer is called by the underlying P2P layer when the whisper sub-protocol +// HandlePeer is called by the underlying P2P layer when the whisper sub-protocol // connection is negotiated. func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { // Create the new peer and start tracking it @@ -353,26 +455,31 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { // fetch the next packet packet, err := rw.ReadMsg() if err != nil { + log.Warn("message loop", "peer", p.peer.ID(), "err", err) return err } + if packet.Size > uint32(wh.maxMsgLength) { + log.Warn("oversized message received", "peer", p.peer.ID()) + return errors.New("oversized message received") + } switch packet.Code { case statusCode: // this should not happen, but no need to panic; just ignore this message. - log.Warn(fmt.Sprintf("%v: unxepected status message received", p.peer)) + log.Warn("unxepected status message received", "peer", p.peer.ID()) case messagesCode: // decode the contained envelopes var envelopes []*Envelope if err := packet.Decode(&envelopes); err != nil { - log.Warn(fmt.Sprintf("%v: failed to decode envelope: [%v], peer will be disconnected", p.peer, err)) - return fmt.Errorf("garbage received") + log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid envelope") } // inject all envelopes into the internal pool for _, envelope := range envelopes { cached, err := wh.add(envelope) if err != nil { - log.Warn(fmt.Sprintf("%v: bad envelope received: [%v], peer will be disconnected", p.peer, err)) - return fmt.Errorf("invalid envelope") + log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid envelope") } if cached { p.mark(envelope) @@ -386,8 +493,8 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { if p.trusted { var envelope Envelope if err := packet.Decode(&envelope); err != nil { - log.Warn(fmt.Sprintf("%v: failed to decode direct message: [%v], peer will be disconnected", p.peer, err)) - return fmt.Errorf("garbage received (directMessage)") + log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid direct message") } wh.postEvent(&envelope, true) } @@ -396,8 +503,8 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { if wh.mailServer != nil { var request Envelope if err := packet.Decode(&request); err != nil { - log.Warn(fmt.Sprintf("%v: failed to decode p2p request message: [%v], peer will be disconnected", p.peer, err)) - return fmt.Errorf("garbage received (p2p request)") + log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid p2p request") } wh.mailServer.DeliverMail(p, &request) } @@ -430,12 +537,12 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { if envelope.Expiry+SynchAllowance*2 < now { return false, fmt.Errorf("very old message") } else { - log.Debug(fmt.Sprintf("expired envelope dropped [%x]", envelope.Hash())) + log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) return false, nil // drop envelope without error } } - if len(envelope.Data) > MaxMessageLength { + if envelope.size() > wh.maxMsgLength { return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } @@ -453,8 +560,8 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("oversized salt [%x]", envelope.Hash()) } - if envelope.PoW() < MinimumPoW && !wh.test { - log.Debug(fmt.Sprintf("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash())) + if envelope.PoW() < wh.minPoW { + log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex()) return false, nil // drop envelope without error } @@ -474,10 +581,10 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { wh.poolMu.Unlock() if alreadyCached { - log.Trace(fmt.Sprintf("whisper envelope already cached [%x]\n", envelope.Hash())) + log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex()) } else { - log.Trace(fmt.Sprintf("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope)) - wh.stats.totalMemoryUsed += envelope.size() + log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex()) + wh.stats.memoryUsed += envelope.size() wh.postEvent(envelope, false) // notify the local node about the new message if wh.mailServer != nil { wh.mailServer.Archive(envelope) @@ -508,11 +615,12 @@ func (w *Whisper) checkOverflow() { if queueSize == messageQueueLimit { if !w.overflow { w.overflow = true - log.Warn(fmt.Sprint("message queue overflow")) + log.Warn("message queue overflow") } } else if queueSize <= messageQueueLimit/2 { if w.overflow { w.overflow = false + log.Warn("message queue overflow fixed (back to normal)") } } } @@ -558,19 +666,17 @@ func (w *Whisper) expire() { w.poolMu.Lock() defer w.poolMu.Unlock() - w.stats.clear() + w.stats.reset() now := uint32(time.Now().Unix()) for expiry, hashSet := range w.expirations { if expiry < now { - w.stats.messagesCleared++ - // Dump all expired messages and remove timestamp hashSet.Each(func(v interface{}) bool { sz := w.envelopes[v.(common.Hash)].size() - w.stats.memoryCleared += sz - w.stats.totalMemoryUsed -= sz delete(w.envelopes, v.(common.Hash)) - delete(w.messages, v.(common.Hash)) + w.stats.messagesCleared++ + w.stats.memoryCleared += sz + w.stats.memoryUsed -= sz return true }) w.expirations[expiry].Clear() @@ -579,12 +685,21 @@ func (w *Whisper) expire() { } } +// Stats returns the whisper node statistics. func (w *Whisper) Stats() string { - return fmt.Sprintf("Latest expiry cycle cleared %d messages (%d bytes). Memory usage: %d bytes.", - w.stats.messagesCleared, w.stats.memoryCleared, w.stats.totalMemoryUsed) + result := fmt.Sprintf("Memory usage: %d bytes. Average messages cleared per expiry cycle: %d. Total messages cleared: %d.", + w.stats.memoryUsed, w.stats.totalMessagesCleared/w.stats.cycles, w.stats.totalMessagesCleared) + if w.stats.messagesCleared > 0 { + result += fmt.Sprintf(" Latest expiry cycle cleared %d messages (%d bytes).", + w.stats.messagesCleared, w.stats.memoryCleared) + } + if w.overflow { + result += " Message queue state: overflow." + } + return result } -// envelopes retrieves all the messages currently pooled by the node. +// Envelopes retrieves all the messages currently pooled by the node. func (w *Whisper) Envelopes() []*Envelope { w.poolMu.RLock() defer w.poolMu.RUnlock() @@ -596,15 +711,17 @@ func (w *Whisper) Envelopes() []*Envelope { return all } -// Messages retrieves all the decrypted messages matching a filter id. +// Messages iterates through all currently floating envelopes +// and retrieves all the messages, that this filter could decrypt. func (w *Whisper) Messages(id string) []*ReceivedMessage { result := make([]*ReceivedMessage, 0) w.poolMu.RLock() defer w.poolMu.RUnlock() if filter := w.filters.Get(id); filter != nil { - for _, msg := range w.messages { - if filter.MatchMessage(msg) { + for _, env := range w.envelopes { + msg := filter.processEnvelope(env) + if msg != nil { result = append(result, msg) } } @@ -612,6 +729,7 @@ func (w *Whisper) Messages(id string) []*ReceivedMessage { return result } +// isEnvelopeCached checks if envelope with specific hash has already been received and cached. func (w *Whisper) isEnvelopeCached(hash common.Hash) bool { w.poolMu.Lock() defer w.poolMu.Unlock() @@ -620,22 +738,30 @@ func (w *Whisper) isEnvelopeCached(hash common.Hash) bool { return exist } -func (w *Whisper) addDecryptedMessage(msg *ReceivedMessage) { - w.poolMu.Lock() - defer w.poolMu.Unlock() - - w.messages[msg.EnvelopeHash] = msg -} +// reset resets the node's statistics after each expiry cycle. +func (s *Statistics) reset() { + s.cycles++ + s.totalMessagesCleared += s.messagesCleared -func (s *Statistics) clear() { s.memoryCleared = 0 s.messagesCleared = 0 } +// ValidateKeyID checks the format of key id. +func ValidateKeyID(id string) error { + const target = keyIdSize * 2 + if len(id) != target { + return fmt.Errorf("wrong size of key ID (expected %d bytes, got %d)", target, len(id)) + } + return nil +} + +// ValidatePublicKey checks the format of the given public key. func ValidatePublicKey(k *ecdsa.PublicKey) bool { return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 } +// validatePrivateKey checks the format of the given private key. func validatePrivateKey(k *ecdsa.PrivateKey) bool { if k == nil || k.D == nil || k.D.Sign() == 0 { return false @@ -648,6 +774,7 @@ func validateSymmetricKey(k []byte) bool { return len(k) > 0 && !containsOnlyZeros(k) } +// containsOnlyZeros checks if the data contain only zeros. func containsOnlyZeros(data []byte) bool { for _, b := range data { if b != 0 { @@ -657,7 +784,8 @@ func containsOnlyZeros(data []byte) bool { return true } -func bytesToIntLittleEndian(b []byte) (res uint64) { +// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer. +func bytesToUintLittleEndian(b []byte) (res uint64) { mul := uint64(1) for i := 0; i < len(b); i++ { res += uint64(b[i]) * mul @@ -666,7 +794,8 @@ func bytesToIntLittleEndian(b []byte) (res uint64) { return res } -func BytesToIntBigEndian(b []byte) (res uint64) { +// BytesToUintBigEndian converts the slice to 64-bit unsigned integer. +func BytesToUintBigEndian(b []byte) (res uint64) { for i := 0; i < len(b); i++ { res *= 256 res += uint64(b[i]) @@ -674,7 +803,7 @@ func BytesToIntBigEndian(b []byte) (res uint64) { return res } -// DeriveSymmetricKey derives symmetric key material from the key or password. +// deriveKeyMaterial derives symmetric key material from the key or password. // pbkdf2 is used for security, in case people use password instead of randomly generated keys. func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error) { if version == 0 { @@ -686,3 +815,17 @@ func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error return nil, unknownVersionError(version) } } + +// GenerateRandomID generates a random string, which is then returned to be used as a key id +func GenerateRandomID() (id string, err error) { + buf := make([]byte, keyIdSize) + _, err = crand.Read(buf) + if err != nil { + return "", err + } + if !validateSymmetricKey(buf) { + return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") + } + id = common.Bytes2Hex(buf) + return id, err +} diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index 8d63d443c..d5668259e 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -21,9 +21,6 @@ import ( mrand "math/rand" "testing" "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) func TestWhisperBasic(t *testing.T) { @@ -55,16 +52,19 @@ func TestWhisperBasic(t *testing.T) { if peer != nil { t.Fatal("found peer for random key.") } - if err := w.MarkPeerTrusted(peerID); err == nil { + if err := w.AllowP2PMessagesFromPeer(peerID); err == nil { t.Fatalf("failed MarkPeerTrusted.") } exist := w.HasSymKey("non-existing") if exist { t.Fatalf("failed HasSymKey.") } - key := w.GetSymKey("non-existing") + key, err := w.GetSymKey("non-existing") + if err == nil { + t.Fatalf("failed GetSymKey(non-existing): false positive.") + } if key != nil { - t.Fatalf("failed GetSymKey.") + t.Fatalf("failed GetSymKey: false positive.") } mail := w.Envelopes() if len(mail) != 0 { @@ -80,7 +80,7 @@ func TestWhisperBasic(t *testing.T) { if _, err := deriveKeyMaterial(peerID, ver); err != unknownVersionError(ver) { t.Fatalf("failed deriveKeyMaterial with param = %v: %s.", peerID, err) } - derived, err := deriveKeyMaterial(peerID, 0) + derived, err = deriveKeyMaterial(peerID, 0) if err != nil { t.Fatalf("failed second deriveKeyMaterial with param = %v: %s.", peerID, err) } @@ -92,8 +92,8 @@ func TestWhisperBasic(t *testing.T) { } buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0} - le := bytesToIntLittleEndian(buf) - be := BytesToIntBigEndian(buf) + le := bytesToUintLittleEndian(buf) + be := BytesToUintBigEndian(buf) if le != uint64(0x280e5ff) { t.Fatalf("failed bytesToIntLittleEndian: %d.", le) } @@ -101,7 +101,14 @@ func TestWhisperBasic(t *testing.T) { t.Fatalf("failed BytesToIntBigEndian: %d.", be) } - pk := w.NewIdentity() + id, err := w.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + pk, err := w.GetPrivateKey(id) + if err != nil { + t.Fatalf("failed to retrieve new key pair: %s.", err) + } if !validatePrivateKey(pk) { t.Fatalf("failed validatePrivateKey: %v.", pk) } @@ -112,67 +119,112 @@ func TestWhisperBasic(t *testing.T) { func TestWhisperIdentityManagement(t *testing.T) { w := New() - id1 := w.NewIdentity() - id2 := w.NewIdentity() - pub1 := common.ToHex(crypto.FromECDSAPub(&id1.PublicKey)) - pub2 := common.ToHex(crypto.FromECDSAPub(&id2.PublicKey)) - pk1 := w.GetIdentity(pub1) - pk2 := w.GetIdentity(pub2) - if !w.HasIdentity(pub1) { - t.Fatalf("failed HasIdentity(pub1).") + id1, err := w.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) } - if !w.HasIdentity(pub2) { - t.Fatalf("failed HasIdentity(pub2).") + id2, err := w.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) } - if pk1 != id1 { - t.Fatalf("failed GetIdentity(pub1).") + pk1, err := w.GetPrivateKey(id1) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + pk2, err := w.GetPrivateKey(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + + if !w.HasKeyPair(id1) { + t.Fatalf("failed HasIdentity(pk1).") + } + if !w.HasKeyPair(id2) { + t.Fatalf("failed HasIdentity(pk2).") + } + if pk1 == nil { + t.Fatalf("failed GetIdentity(pk1).") + } + if pk2 == nil { + t.Fatalf("failed GetIdentity(pk2).") + } + + if !validatePrivateKey(pk1) { + t.Fatalf("pk1 is invalid.") } - if pk2 != id2 { - t.Fatalf("failed GetIdentity(pub2).") + if !validatePrivateKey(pk2) { + t.Fatalf("pk2 is invalid.") } // Delete one identity - w.DeleteIdentity(pub1) - pk1 = w.GetIdentity(pub1) - pk2 = w.GetIdentity(pub2) - if w.HasIdentity(pub1) { + done := w.DeleteKeyPair(id1) + if !done { + t.Fatalf("failed to delete id1.") + } + pk1, err = w.GetPrivateKey(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetPrivateKey(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + if w.HasKeyPair(id1) { t.Fatalf("failed DeleteIdentity(pub1): still exist.") } - if !w.HasIdentity(pub2) { + if !w.HasKeyPair(id2) { t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.") } if pk1 != nil { t.Fatalf("failed DeleteIdentity(pub1): first key still exist.") } - if pk2 != id2 { + if pk2 == nil { t.Fatalf("failed DeleteIdentity(pub1): second key does not exist.") } // Delete again non-existing identity - w.DeleteIdentity(pub1) - pk1 = w.GetIdentity(pub1) - pk2 = w.GetIdentity(pub2) - if w.HasIdentity(pub1) { + done = w.DeleteKeyPair(id1) + if done { + t.Fatalf("delete id1: false positive.") + } + pk1, err = w.GetPrivateKey(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetPrivateKey(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + if w.HasKeyPair(id1) { t.Fatalf("failed delete non-existing identity: exist.") } - if !w.HasIdentity(pub2) { + if !w.HasKeyPair(id2) { t.Fatalf("failed delete non-existing identity: pub2 does not exist.") } if pk1 != nil { t.Fatalf("failed delete non-existing identity: first key exist.") } - if pk2 != id2 { + if pk2 == nil { t.Fatalf("failed delete non-existing identity: second key does not exist.") } // Delete second identity - w.DeleteIdentity(pub2) - pk1 = w.GetIdentity(pub1) - pk2 = w.GetIdentity(pub2) - if w.HasIdentity(pub1) { + done = w.DeleteKeyPair(id2) + if !done { + t.Fatalf("failed to delete id2.") + } + pk1, err = w.GetPrivateKey(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetPrivateKey(id2) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + if w.HasKeyPair(id1) { t.Fatalf("failed delete second identity: first identity exist.") } - if w.HasIdentity(pub2) { + if w.HasKeyPair(id2) { t.Fatalf("failed delete second identity: still exist.") } if pk1 != nil { @@ -186,23 +238,30 @@ func TestWhisperIdentityManagement(t *testing.T) { func TestWhisperSymKeyManagement(t *testing.T) { InitSingleTest() + var err error var k1, k2 []byte w := New() id1 := string("arbitrary-string-1") id2 := string("arbitrary-string-2") - err := w.GenerateSymKey(id1) + id1, err = w.GenerateSymKey() if err != nil { t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) } - k1 = w.GetSymKey(id1) - k2 = w.GetSymKey(id2) + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err == nil { + t.Fatalf("failed GetSymKey(id2): false positive.") + } if !w.HasSymKey(id1) { t.Fatalf("failed HasSymKey(id1).") } if w.HasSymKey(id2) { - t.Fatalf("failed HasSymKey(id2).") + t.Fatalf("failed HasSymKey(id2): false positive.") } if k1 == nil { t.Fatalf("first key does not exist.") @@ -212,37 +271,49 @@ func TestWhisperSymKeyManagement(t *testing.T) { } // add existing id, nothing should change - randomKey := make([]byte, 16) + randomKey := make([]byte, aesKeyLength) mrand.Read(randomKey) - err = w.AddSymKey(id1, randomKey) - if err == nil { - t.Fatalf("failed AddSymKey with seed %d.", seed) + id1, err = w.AddSymKeyDirect(randomKey) + if err != nil { + t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err) } - k1 = w.GetSymKey(id1) - k2 = w.GetSymKey(id2) + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err == nil { + t.Fatalf("failed w.GetSymKey(id2): false positive.") + } if !w.HasSymKey(id1) { t.Fatalf("failed w.HasSymKey(id1).") } if w.HasSymKey(id2) { - t.Fatalf("failed w.HasSymKey(id2).") + t.Fatalf("failed w.HasSymKey(id2): false positive.") } if k1 == nil { t.Fatalf("first key does not exist.") } - if bytes.Equal(k1, randomKey) { - t.Fatalf("k1 == randomKey.") + if !bytes.Equal(k1, randomKey) { + t.Fatalf("k1 != randomKey.") } if k2 != nil { t.Fatalf("second key already exist.") } - err = w.AddSymKey(id2, randomKey) // add non-existing (yet) + id2, err = w.AddSymKeyDirect(randomKey) if err != nil { t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err) } - k1 = w.GetSymKey(id1) - k2 = w.GetSymKey(id2) + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } if !w.HasSymKey(id1) { t.Fatalf("HasSymKey(id1) failed.") } @@ -255,11 +326,11 @@ func TestWhisperSymKeyManagement(t *testing.T) { if k2 == nil { t.Fatalf("k2 does not exist.") } - if bytes.Equal(k1, k2) { - t.Fatalf("k1 == k2.") + if !bytes.Equal(k1, k2) { + t.Fatalf("k1 != k2.") } - if bytes.Equal(k1, randomKey) { - t.Fatalf("k1 == randomKey.") + if !bytes.Equal(k1, randomKey) { + t.Fatalf("k1 != randomKey.") } if len(k1) != aesKeyLength { t.Fatalf("wrong length of k1.") @@ -269,8 +340,17 @@ func TestWhisperSymKeyManagement(t *testing.T) { } w.DeleteSymKey(id1) - k1 = w.GetSymKey(id1) - k2 = w.GetSymKey(id2) + k1, err = w.GetSymKey(id1) + if err == nil { + t.Fatalf("failed w.GetSymKey(id1): false positive.") + } + if k1 != nil { + t.Fatalf("failed GetSymKey(id1): false positive.") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } if w.HasSymKey(id1) { t.Fatalf("failed to delete first key: still exist.") } @@ -286,8 +366,17 @@ func TestWhisperSymKeyManagement(t *testing.T) { w.DeleteSymKey(id1) w.DeleteSymKey(id2) - k1 = w.GetSymKey(id1) - k2 = w.GetSymKey(id2) + k1, err = w.GetSymKey(id1) + if err == nil { + t.Fatalf("failed w.GetSymKey(id1): false positive.") + } + k2, err = w.GetSymKey(id2) + if err == nil { + t.Fatalf("failed w.GetSymKey(id2): false positive.") + } + if k1 != nil || k2 != nil { + t.Fatalf("k1 or k2 is not nil") + } if w.HasSymKey(id1) { t.Fatalf("failed to delete second key: first key exist.") } @@ -300,13 +389,63 @@ func TestWhisperSymKeyManagement(t *testing.T) { if k2 != nil { t.Fatalf("failed to delete second key: second key is not nil.") } + + randomKey = make([]byte, aesKeyLength+1) + mrand.Read(randomKey) + id1, err = w.AddSymKeyDirect(randomKey) + if err == nil { + t.Fatalf("added the key with wrong size, seed %d.", seed) + } + + const password = "arbitrary data here" + id1, err = w.AddSymKeyFromPassword(password) + if err != nil { + t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err) + } + id2, err = w.AddSymKeyFromPassword(password) + if err != nil { + t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err) + } + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } + if !w.HasSymKey(id1) { + t.Fatalf("HasSymKey(id1) failed.") + } + if !w.HasSymKey(id2) { + t.Fatalf("HasSymKey(id2) failed.") + } + if k1 == nil { + t.Fatalf("k1 does not exist.") + } + if k2 == nil { + t.Fatalf("k2 does not exist.") + } + if !bytes.Equal(k1, k2) { + t.Fatalf("k1 != k2.") + } + if len(k1) != aesKeyLength { + t.Fatalf("wrong length of k1.") + } + if len(k2) != aesKeyLength { + t.Fatalf("wrong length of k2.") + } + if !validateSymmetricKey(k2) { + t.Fatalf("key validation failed.") + } } func TestExpiry(t *testing.T) { InitSingleTest() w := New() - w.test = true + w.SetMinimumPoW(0.0000001) + defer w.SetMinimumPoW(DefaultMinimumPoW) w.Start(nil) defer w.Stop() @@ -354,3 +493,87 @@ func TestExpiry(t *testing.T) { t.Fatalf("expire failed, seed: %d.", seed) } } + +func TestCustomization(t *testing.T) { + InitSingleTest() + + w := New() + defer w.SetMinimumPoW(DefaultMinimumPoW) + defer w.SetMaxMessageLength(DefaultMaxMessageLength) + w.Start(nil) + defer w.Stop() + + const smallPoW = 0.00001 + + f, err := generateFilter(t, true) + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + params.KeySym = f.KeySym + params.Topic = BytesToTopic(f.Topics[2]) + params.PoW = smallPoW + params.TTL = 3600 * 24 // one day + msg := NewSentMessage(params) + env, err := msg.Wrap(params) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err == nil { + t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed) + } + + w.SetMinimumPoW(smallPoW / 2) + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + + params.TTL++ + msg = NewSentMessage(params) + env, err = msg.Wrap(params) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + w.SetMaxMessageLength(env.size() - 1) + err = w.Send(env) + if err == nil { + t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed) + } + + w.SetMaxMessageLength(DefaultMaxMessageLength) + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err) + } + + // wait till received or timeout + var received bool + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) > 1 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // check w.messages() + id, err := w.Subscribe(f) + time.Sleep(5 * time.Millisecond) + mail := f.Retrieve() + if len(mail) > 0 { + t.Fatalf("received premature mail") + } + + mail = w.Messages(id) + if len(mail) != 2 { + t.Fatalf("failed to get whisper messages") + } +} -- cgit v1.2.3