From a02cf9a0ad3c0daf78158cdff78ebb281c933ef4 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 28 Feb 2017 00:02:35 +0100 Subject: [PATCH 01/18] whisper: GetMessages fixed; size restriction updated --- whisper/whisperv5/api.go | 10 ++--- whisper/whisperv5/api_test.go | 74 +++++++++++++---------------------- whisper/whisperv5/doc.go | 2 +- whisper/whisperv5/filter.go | 30 ++++++++++---- whisper/whisperv5/message.go | 4 ++ whisper/whisperv5/whisper.go | 42 ++++++++++---------- 6 files changed, 81 insertions(+), 81 deletions(-) diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 9b43f7b706..2e69089502 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -102,12 +102,12 @@ func (api *PublicWhisperAPI) HasIdentity(identity string) (bool, error) { } // DeleteIdentity deletes the specifies key if it exists. -func (api *PublicWhisperAPI) DeleteIdentity(identity string) error { +func (api *PublicWhisperAPI) DeleteIdentity(identity string) (bool, error) { if api.whisper == nil { - return whisperOffLineErr + return false, whisperOffLineErr } - api.whisper.DeleteIdentity(identity) - return nil + success := api.whisper.DeleteIdentity(identity) + return success, nil } // NewIdentity generates a new cryptographic identity for the client, and injects @@ -352,7 +352,7 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { log.Error(fmt.Sprintf(err.Error())) return err } - if len(envelope.Data) > MaxMessageLength { + if envelope.size() > MaxMessageLength { info := "Post: message is too big" log.Error(fmt.Sprintf(info)) return errors.New(info) diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index ea0a2c40bc..cde84e991a 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -55,10 +55,13 @@ func TestBasic(t *testing.T) { t.Fatalf("failed initial HasIdentity: false positive.") } - err = api.DeleteIdentity(id) + success, err := api.DeleteIdentity(id) if err != nil { t.Fatalf("failed DeleteIdentity: %s.", err) } + if success { + t.Fatalf("deleted non-existing identity: false positive.") + } pub, err := api.NewIdentity() if err != nil { @@ -76,10 +79,13 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasIdentity: false negative.") } - err = api.DeleteIdentity(pub) + success, err = api.DeleteIdentity(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) if err != nil { @@ -257,17 +263,23 @@ func TestUnmarshalPostArgs(t *testing.T) { } } -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.GetFilterChanges(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) { @@ -334,12 +346,7 @@ func TestIntegrationAsym(t *testing.T) { 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 +363,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)) } @@ -434,12 +436,7 @@ func TestIntegrationSym(t *testing.T) { 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 +453,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)) } @@ -534,12 +526,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { 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 +543,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)) } diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 70c7008a73..9e533010b0 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -55,7 +55,7 @@ const ( saltLength = 12 AESNonceMaxLength = 12 - MaxMessageLength = 0x0FFFFF // todo: remove this restriction after testing. this should be regulated by PoW. + MaxMessageLength = 1024 * 1024 MinimumPoW = 10.0 // todo: review after testing. padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index ffa5ae9463..40c2ebbc1e 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -102,11 +102,16 @@ func (fs *Filters) Get(id string) *Filter { } func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { - fs.mutex.RLock() + var j int var msg *ReceivedMessage - for j, watcher := range fs.watchers { + + fs.mutex.RLock() + defer fs.mutex.RUnlock() + + for _, watcher := range fs.watchers { + j++ if p2pMessage && !watcher.AcceptP2P { - log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), j)) + log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), j)) continue } @@ -118,10 +123,10 @@ 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(fmt.Sprintf("msg [%x], filter [%d]: failed to open", env.Hash(), j)) } } else { - log.Trace(fmt.Sprintf("msg [%x], filter [%s]: does not match", env.Hash(), j)) + log.Trace(fmt.Sprintf("msg [%x], filter [%d]: does not match", env.Hash(), j)) } } @@ -129,11 +134,20 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { watcher.Trigger(msg) } } - fs.mutex.RUnlock() // we need to unlock before calling addDecryptedMessage +} - if msg != nil { - fs.whisper.addDecryptedMessage(msg) +func (f *Filter) processEnvelope(env *Envelope) *ReceivedMessage { + if f.MatchEnvelope(env) { + msg := env.Open(f) + if msg != nil { + return msg + } else { + log.Trace(fmt.Sprintf("processing msg [%x]: failed to open", env.Hash())) + } + } else { + log.Trace(fmt.Sprintf("processing msg [%x]: does not match", env.Hash())) } + return nil } func (f *Filter) expectsAsymmetricEncryption() bool { diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index 9677f278e5..995cfebbef 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -258,6 +258,10 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er if err != nil { return nil, err } + if envelope.size() > MaxMessageLength { + log.Error(fmt.Sprintf("Envelope size must not exceed %d bytes", MaxMessageLength)) + return nil, errors.New("Oversized message") + } return envelope, nil } diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 5062f7b6bc..c4f2b237d5 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -51,10 +51,9 @@ type Whisper struct { symKeys map[string][]byte keyMu sync.RWMutex - 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 @@ -78,7 +77,6 @@ func New() *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), @@ -188,10 +186,15 @@ func (w *Whisper) NewIdentity() *ecdsa.PrivateKey { } // DeleteIdentity deletes the specified key if it exists. -func (w *Whisper) DeleteIdentity(key string) { +func (w *Whisper) DeleteIdentity(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 @@ -355,6 +358,9 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { if err != nil { return err } + if packet.Size > MaxMessageLength { + return fmt.Errorf("oversized message received") + } switch packet.Code { case statusCode: @@ -435,7 +441,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { } } - if len(envelope.Data) > MaxMessageLength { + if envelope.size() > MaxMessageLength { return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } @@ -558,7 +564,7 @@ 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 { @@ -570,7 +576,6 @@ func (w *Whisper) expire() { w.stats.memoryCleared += sz w.stats.totalMemoryUsed -= sz delete(w.envelopes, v.(common.Hash)) - delete(w.messages, v.(common.Hash)) return true }) w.expirations[expiry].Clear() @@ -596,15 +601,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) } } @@ -620,14 +627,7 @@ 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 -} - -func (s *Statistics) clear() { +func (s *Statistics) reset() { s.memoryCleared = 0 s.messagesCleared = 0 } From 709d6e47f8e8020c7cf4e7302533d45fbcb90acd Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 1 Mar 2017 13:22:55 +0100 Subject: [PATCH 02/18] whisper: made PoW and MaxMsgSize customizable --- cmd/wnode/main.go | 4 ++-- whisper/whisperv5/api.go | 16 +++++++++++++++- whisper/whisperv5/api_test.go | 8 ++++---- whisper/whisperv5/doc.go | 4 ++-- whisper/whisperv5/message.go | 20 -------------------- whisper/whisperv5/peer_test.go | 2 +- whisper/whisperv5/whisper.go | 30 +++++++++++++++++++++++++----- whisper/whisperv5/whisper_test.go | 3 ++- 8 files changed, 51 insertions(+), 36 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 8191f9292e..f631c03bad 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -84,8 +84,8 @@ var ( argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level") argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") argWorkTime = flag.Uint("work", 5, "work time in seconds") - argPoW = flag.Float64("pow", whisper.MinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") - argServerPoW = flag.Float64("mspow", whisper.MinimumPoW, "PoW requirement for Mail Server request") + argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") + argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request") argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") argPub = flag.String("pub", "", "public key for asymmetric encryption") diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 2e69089502..ba99350189 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -72,6 +72,20 @@ func (api *PublicWhisperAPI) Stats() (string, error) { return api.whisper.Stats(), nil } +func (api *PublicWhisperAPI) SetMaxMessageLength(val int) error { + if api.whisper == nil { + return whisperOffLineErr + } + return api.whisper.SetMaxMessageLength(val) +} + +func (api *PublicWhisperAPI) SetMinimumPoW(val float64) error { + if api.whisper == nil { + return whisperOffLineErr + } + return api.whisper.SetMinimumPoW(val) +} + // MarkPeerTrusted marks specific peer trusted, which will allow it // to send historic (expired) messages. func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error { @@ -352,7 +366,7 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { log.Error(fmt.Sprintf(err.Error())) return err } - if envelope.size() > MaxMessageLength { + if envelope.size() > api.whisper.maxMsgLength { info := "Post: message is too big" log.Error(fmt.Sprintf(info)) return errors.New(info) diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index cde84e991a..9c8fb2841b 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -323,7 +323,7 @@ func TestIntegrationAsym(t *testing.T) { f.To = key f.From = sig f.Topics = topics[:] - f.PoW = MinimumPoW / 2 + f.PoW = DefaultMinimumPoW / 2 f.AcceptP2P = true id, err := api.NewFilter(f) @@ -337,7 +337,7 @@ func TestIntegrationAsym(t *testing.T) { p.To = f.To p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = MinimumPoW + p.PoW = DefaultMinimumPoW p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} p.WorkTime = 2 @@ -427,7 +427,7 @@ func TestIntegrationSym(t *testing.T) { p.From = f.From p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = MinimumPoW + p.PoW = DefaultMinimumPoW p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} p.WorkTime = 2 @@ -517,7 +517,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { p.From = sig p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = MinimumPoW + p.PoW = DefaultMinimumPoW p.Topic = TopicType{0xf2, 0x6e, 0x77, 0x79} p.WorkTime = 2 diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 9e533010b0..69756eacee 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -55,8 +55,8 @@ const ( saltLength = 12 AESNonceMaxLength = 12 - MaxMessageLength = 1024 * 1024 - MinimumPoW = 10.0 // todo: review after testing. + DefaultMaxMessageLength = 1024 * 1024 + DefaultMinimumPoW = 10.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/message.go b/whisper/whisperv5/message.go index 995cfebbef..c5103578c8 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -215,17 +215,6 @@ func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte, } // Wrap bundles the message into an Envelope to transmit over the network. -// -// 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,10 +225,6 @@ 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) @@ -258,11 +243,6 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er if err != nil { return nil, err } - if envelope.size() > MaxMessageLength { - log.Error(fmt.Sprintf("Envelope size must not exceed %d bytes", MaxMessageLength)) - return nil, errors.New("Oversized message") - } - return envelope, nil } diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index e3073bc6c8..110c3af531 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -114,7 +114,7 @@ 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) diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index c4f2b237d5..1b46421ea0 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -66,8 +66,9 @@ type Whisper struct { stats Statistics - overflow bool - test bool + minPoW float64 + maxMsgLength int + overflow bool } // New creates a Whisper client ready to communicate through the Ethereum P2P network. @@ -82,6 +83,8 @@ func New() *Whisper { messageQueue: make(chan *Envelope, messageQueueLimit), p2pMsgQueue: make(chan *Envelope, messageQueueLimit), quit: make(chan struct{}), + minPoW: DefaultMinimumPoW, + maxMsgLength: DefaultMaxMessageLength, } whisper.filters = NewFilters(whisper) @@ -122,6 +125,22 @@ func (w *Whisper) Version() uint { return w.protocol.Version } +func (w *Whisper) SetMaxMessageLength(val int) error { + if val <= 0 { + return fmt.Errorf("Invalid message length: %d", val) + } + w.maxMsgLength = val + return nil +} + +func (w *Whisper) SetMinimumPoW(val float64) error { + if val <= 0.0 { + return fmt.Errorf("Invalid PoW: %f", val) + } + w.minPoW = val + return nil +} + func (w *Whisper) getPeer(peerID []byte) (*Peer, error) { w.peerMu.Lock() defer w.peerMu.Unlock() @@ -358,7 +377,7 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { if err != nil { return err } - if packet.Size > MaxMessageLength { + if packet.Size > uint32(wh.maxMsgLength) { return fmt.Errorf("oversized message received") } @@ -441,7 +460,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { } } - if envelope.size() > MaxMessageLength { + if envelope.size() > wh.maxMsgLength { return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } @@ -459,7 +478,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("oversized salt [%x]", envelope.Hash()) } - if envelope.PoW() < MinimumPoW && !wh.test { + if envelope.PoW() < wh.minPoW { log.Debug(fmt.Sprintf("envelope with low PoW dropped: %f [%x]", envelope.PoW(), envelope.Hash())) return false, nil // drop envelope without error } @@ -519,6 +538,7 @@ func (w *Whisper) checkOverflow() { } else if queueSize <= messageQueueLimit/2 { if w.overflow { w.overflow = false + log.Warn(fmt.Sprint("message queue overflow fixed (back to normal)")) } } } diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index 312dacfc4b..664530215a 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -305,7 +305,8 @@ 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() From 581841f74df963aa37b3a3dc69fc4934dcd95aa3 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 1 Mar 2017 18:54:51 +0100 Subject: [PATCH 03/18] whisper: test added --- whisper/whisperv5/whisper.go | 5 +- whisper/whisperv5/whisper_test.go | 84 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 1b46421ea0..2f416cae19 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -317,7 +317,10 @@ func (w *Whisper) Unwatch(id string) { // 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 } diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index 664530215a..fbaebe8306 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -354,3 +354,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 = f.Topics[2] + params.PoW = smallPoW + params.TTL = 3600 * 24 // one day + msg := NewSentMessage(params) + env, err := msg.Wrap(params) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err == nil { + t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed) + } + + w.SetMinimumPoW(smallPoW / 2) + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + + params.TTL++ + msg = NewSentMessage(params) + env, err = msg.Wrap(params) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + w.SetMaxMessageLength(env.size() - 1) + err = w.Send(env) + if err == nil { + t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed) + } + + w.SetMaxMessageLength(DefaultMaxMessageLength) + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err) + } + + // wait till received or timeout + var received bool + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) > 1 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // check w.messages() + id, err := w.Watch(f) + time.Sleep(5 * time.Millisecond) + mail := f.Retrieve() + if len(mail) > 0 { + t.Fatalf("received premature mail") + } + + mail = w.Messages(id) + if len(mail) != 2 { + t.Fatalf("failed to get whisper messages") + } +} From 8ac71045670dbd15de9d9af8b0d0b8db641f1e49 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 2 Mar 2017 13:38:38 +0100 Subject: [PATCH 04/18] whisper: sym key management changed --- cmd/wnode/main.go | 18 ++-- whisper/mailserver/mailserver.go | 9 +- whisper/mailserver/server_test.go | 11 ++- whisper/whisperv5/api.go | 97 +++++++++++++------- whisper/whisperv5/api_test.go | 39 +++++--- whisper/whisperv5/doc.go | 1 + whisper/whisperv5/filter.go | 35 ++------ whisper/whisperv5/filter_test.go | 2 +- whisper/whisperv5/whisper.go | 129 ++++++++++++++++++-------- whisper/whisperv5/whisper_test.go | 144 ++++++++++++++++++++++++------ 10 files changed, 336 insertions(+), 149 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index f631c03bad..1746247f01 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -46,7 +46,6 @@ import ( ) const quitCommand = "~Q" -const symKeyName = "da919ea33001b04dfc630522e33078ec0df11" // singletons var ( @@ -288,8 +287,14 @@ func configureNode() { } } - shh.AddSymKey(symKeyName, []byte(symPass)) - symKey = shh.GetSymKey(symKeyName) + symKeyID, err := shh.AddSymKeyFromPassword(symPass) + if err != nil { + utils.Fatalf("Failed to create symmetric key: %s", err) + } + symKey, err = shh.GetSymKey(symKeyID) + if err != nil { + utils.Fatalf("Failed to save symmetric key: %s", err) + } if len(*argTopic) == 0 { generateTopic([]byte(symPass)) } @@ -470,11 +475,14 @@ func requestExpiredMessagesLoop() { var t string var xt, empty whisper.TopicType - err := shh.AddSymKey(mailserver.MailServerKeyName, []byte(msPassword)) + keyID, err := shh.AddSymKeyFromPassword(msPassword) if err != nil { utils.Fatalf("Failed to create symmetric key for mail request: %s", err) } - key = shh.GetSymKey(mailserver.MailServerKeyName) + key, err = shh.GetSymKey(keyID) + if err != nil { + utils.Fatalf("Failed to save symmetric key for mail request: %s", err) + } peerID = extractIdFromEnode(*argEnode) shh.MarkPeerTrusted(peerID) diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go index 6533c56c2f..d705c622f4 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 8b58a826fd..9304d81774 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) } @@ -167,8 +167,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 ba99350189..456e16fa21 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -134,40 +134,63 @@ func (api *PublicWhisperAPI) NewIdentity() (string, error) { return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil } -// GenerateSymKey generates a random symmetric key and stores it under -// the 'name' id. Will be used in the future for session key exchange. -func (api *PublicWhisperAPI) GenerateSymKey(name string) error { +// todo: implement +//func (api *PublicWhisperAPI) GetPublicKey(id string) (bool, error) { +// if api.whisper == nil { +// return false, whisperOffLineErr +// } +// return api.whisper.HasIdentity(identity), nil +//} + +// GenerateSymKey generates a random symmetric key and stores it under id, +// which is then returned. Will be used in the future for session key exchange. +func (api *PublicWhisperAPI) GenerateSymKey() (string, error) { if api.whisper == nil { - return whisperOffLineErr + return "", whisperOffLineErr } - return api.whisper.GenerateSymKey(name) + return api.whisper.GenerateSymKey() } -// AddSymKey stores the key under the 'name' id. -func (api *PublicWhisperAPI) AddSymKey(name string, key hexutil.Bytes) error { +// AddSymKeyDirect stores the key, and returns its id. +func (api *PublicWhisperAPI) AddSymKeyDirect(key hexutil.Bytes) (string, error) { if api.whisper == nil { - return whisperOffLineErr + return "", whisperOffLineErr } - return api.whisper.AddSymKey(name, key) + return api.whisper.AddSymKeyDirect(key) } -// HasSymKey returns true if there is a key associated with the name string. +// AddSymKeyFromPassword generates the key from password, stores it, and returns its id. +func (api *PublicWhisperAPI) AddSymKeyFromPassword(password string) (string, error) { + if api.whisper == nil { + return "", whisperOffLineErr + } + return api.whisper.AddSymKeyFromPassword(password) +} + +// HasSymKey returns true if there is a key associated with the given id. // Otherwise returns false. -func (api *PublicWhisperAPI) HasSymKey(name string) (bool, error) { +func (api *PublicWhisperAPI) HasSymKey(id string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } - res := api.whisper.HasSymKey(name) + res := api.whisper.HasSymKey(id) return res, nil } -// DeleteSymKey deletes the key associated with the name string if it exists. -func (api *PublicWhisperAPI) DeleteSymKey(name string) error { +func (api *PublicWhisperAPI) GetSymKey(name string) ([]byte, error) { if api.whisper == nil { - return whisperOffLineErr + return nil, whisperOffLineErr } - api.whisper.DeleteSymKey(name) - return nil + return api.whisper.GetSymKey(name) +} + +// DeleteSymKey deletes the key associated with the name string if it exists. +func (api *PublicWhisperAPI) DeleteSymKey(name string) (bool, error) { + if api.whisper == nil { + return false, whisperOffLineErr + } + res := api.whisper.DeleteSymKey(name) + return res, nil } // NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages. @@ -177,9 +200,21 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { return "", whisperOffLineErr } + var err error + var symKey []byte + + if len(args.KeyName) > 0 { + symKey, err = api.whisper.GetSymKey(args.KeyName) + if err != nil { + info := "NewFilter: symmetric key ID does not exist: " + args.KeyName + log.Error(fmt.Sprintf(info)) + return "", errors.New(info) + } + } + filter := Filter{ Src: crypto.ToECDSAPub(common.FromHex(args.From)), - KeySym: api.whisper.GetSymKey(args.KeyName), + KeySym: symKey, PoW: args.PoW, Messages: make(map[common.Hash]*ReceivedMessage), AcceptP2P: args.AcceptP2P, @@ -195,12 +230,6 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { return "", errors.New(info) } - 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) - } - if len(args.To) == 0 && len(filter.KeySym) == 0 { info := "NewFilter: filter must contain either symmetric or asymmetric key" log.Error(fmt.Sprintf(info)) @@ -275,10 +304,22 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { return whisperOffLineErr } + var err error + var symKey []byte + + if len(args.KeyName) > 0 { + symKey, err = api.whisper.GetSymKey(args.KeyName) + if err != nil { + info := "NewFilter: symmetric key ID does not exist: " + args.KeyName + log.Error(fmt.Sprintf(info)) + return errors.New(info) + } + } + params := MessageParams{ TTL: args.TTL, Dst: crypto.ToECDSAPub(common.FromHex(args.To)), - KeySym: api.whisper.GetSymKey(args.KeyName), + KeySym: symKey, Topic: args.Topic, Payload: args.Payload, Padding: args.Padding, @@ -333,12 +374,6 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { } // 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)) diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index 9c8fb2841b..a94909ecca 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -106,7 +106,7 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey: false positive.") } - err = api.GenerateSymKey(id) + id, err = api.GenerateSymKey() if err != nil { t.Fatalf("failed GenerateSymKey: %s.", err) } @@ -119,12 +119,13 @@ 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.AddSymKeyFromPassword(password) + if err != nil { t.Fatalf("failed AddSymKey: %s.", err) } - err = api.AddSymKey(id2, []byte("some stuff here")) + id2, err = api.AddSymKeyFromPassword(password) if err != nil { t.Fatalf("failed AddSymKey: %s.", err) } @@ -137,10 +138,26 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey(id2): false negative.") } - err = api.DeleteSymKey(id) + k1, err := api.GetSymKey(id) + if err != nil { + t.Fatalf("failed GetSymKey(id): %s.", err) + } + k2, err := api.GetSymKey(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.DeleteSymKey(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) if err != nil { @@ -384,8 +401,7 @@ func TestIntegrationSym(t *testing.T) { api.Start() defer api.Stop() - keyname := "schluessel" - err := api.GenerateSymKey(keyname) + keyID, err := api.GenerateSymKey() if err != nil { t.Fatalf("failed GenerateSymKey: %s.", err) } @@ -410,7 +426,7 @@ 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.KeyName = keyID f.Topics = topics[:] f.PoW = 0.324 f.From = sig @@ -423,7 +439,7 @@ func TestIntegrationSym(t *testing.T) { var p PostArgs p.TTL = 1 - p.KeyName = keyname + p.KeyName = keyID p.From = f.From p.Padding = []byte("test string") p.Payload = []byte("extended test string") @@ -474,8 +490,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { api.Start() defer api.Stop() - keyname := "schluessel" - err := api.GenerateSymKey(keyname) + keyID, err := api.GenerateSymKey() if err != nil { t.Fatalf("failed to GenerateSymKey: %s.", err) } @@ -500,7 +515,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} var f WhisperFilterArgs - f.KeyName = keyname + f.KeyName = keyID f.Topics = topics[:] f.PoW = 0.324 f.From = sig diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 69756eacee..4b735cbc6c 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -54,6 +54,7 @@ const ( aesKeyLength = 32 saltLength = 12 AESNonceMaxLength = 12 + keyIdSize = 32 DefaultMaxMessageLength = 1024 * 1024 DefaultMinimumPoW = 10.0 // todo: review after testing. diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index 40c2ebbc1e..cfb8f0f4b0 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" @@ -52,40 +51,24 @@ 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 } diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go index d69fb40dbf..b4f52a8185 100644 --- a/whisper/whisperv5/filter_test.go +++ b/whisper/whisperv5/filter_test.go @@ -108,7 +108,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)) } } diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 2f416cae19..fd9f544817 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -48,8 +48,9 @@ type Whisper struct { filters *Filters privateKeys map[string]*ecdsa.PrivateKey - symKeys map[string][]byte - keyMu sync.RWMutex + //identities map[string]*ecdsa.PrivateKey + symKeys map[string][]byte + keyMu sync.RWMutex envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node expirations map[uint32]*set.SetNonTS // Message expiration pool @@ -75,7 +76,8 @@ type Whisper struct { // 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), + privateKeys: make(map[string]*ecdsa.PrivateKey), + //identities: make(map[string]*ecdsa.PrivateKey), symKeys: make(map[string][]byte), envelopes: make(map[common.Hash]*Envelope), expirations: make(map[uint32]*set.SetNonTS), @@ -231,72 +233,108 @@ func (w *Whisper) GetIdentity(pubKey string) *ecdsa.PrivateKey { return w.privateKeys[pubKey] } -func (w *Whisper) GenerateSymKey(name string) error { +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") } - w.keyMu.Lock() - defer w.keyMu.Unlock() - - if w.symKeys[name] != nil { - return fmt.Errorf("Key with name [%s] already exists", name) - } - w.symKeys[name] = derived - return nil -} - -func (w *Whisper) AddSymKey(name string, key []byte) error { - if w.HasSymKey(name) { - return fmt.Errorf("Key with name [%s] already exists", name) - } - - derived, err := deriveKeyMaterial(key, EnvelopeVersion) + 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] = derived + return id, nil } -func (w *Whisper) HasSymKey(name string) bool { - w.keyMu.RLock() - defer w.keyMu.RUnlock() - return w.symKeys[name] != nil -} +func (w *Whisper) AddSymKeyDirect(key []byte) (string, error) { + if len(key) != aesKeyLength { + return "", fmt.Errorf("Wrong key size: %d", len(key)) + } + + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("Failed to generate ID: %s", err) + } -func (w *Whisper) DeleteSymKey(name string) { w.keyMu.Lock() defer w.keyMu.Unlock() - delete(w.symKeys, name) + + if w.symKeys[id] != nil { + return "", fmt.Errorf("Failed to generate unique ID") + } + w.symKeys[id] = key + return id, nil } -func (w *Whisper) GetSymKey(name string) []byte { +func (w *Whisper) AddSymKeyFromPassword(password string) (string, error) { + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("Failed to generate ID: %s", err) + } + if w.HasSymKey(id) { + return "", fmt.Errorf("Failed to generate unique ID") + } + + derived, err := deriveKeyMaterial([]byte(password), EnvelopeVersion) + if err != nil { + return "", err + } + + w.keyMu.Lock() + defer w.keyMu.Unlock() + + // double check is necessary, because deriveKeyMaterial() is very slow + if w.symKeys[id] != nil { + return "", fmt.Errorf("Severe error: failed to generate unique ID") + } + w.symKeys[id] = derived + return id, nil +} + +func (w *Whisper) HasSymKey(id string) bool { w.keyMu.RLock() defer w.keyMu.RUnlock() - return w.symKeys[name] + return w.symKeys[id] != nil +} + +func (w *Whisper) DeleteSymKey(id string) bool { + w.keyMu.Lock() + defer w.keyMu.Unlock() + if w.symKeys[id] != nil { + delete(w.symKeys, id) + return true + } + return false +} + +func (w *Whisper) GetSymKey(id string) ([]byte, error) { + w.keyMu.RLock() + defer w.keyMu.RUnlock() + if w.symKeys[id] != nil { + return w.symKeys[id], nil + } + return nil, fmt.Errorf("non-existent ID") } // Watch installs a new message handler to run in case a matching packet arrives @@ -709,3 +747,16 @@ func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error return nil, unknownVersionError(version) } } + +func GenerateRandomID() (id string, err error) { + buf := make([]byte, keyIdSize) + _, err = crand.Read(buf) + if err != nil { + return "", err + } + if !validateSymmetricKey(buf) { + return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") + } + id = common.Bytes2Hex(buf) + return id, err +} diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index fbaebe8306..9a9142c0ca 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -61,9 +61,12 @@ func TestWhisperBasic(t *testing.T) { 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 { @@ -79,7 +82,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) } @@ -185,23 +188,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.") @@ -210,38 +220,49 @@ func TestWhisperSymKeyManagement(t *testing.T) { t.Fatalf("second key still exist.") } - // add existing id, nothing should change - randomKey := make([]byte, 16) + randomKey := make([]byte, aesKeyLength) randomize(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.") } @@ -254,11 +275,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.") @@ -268,8 +289,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.") } @@ -285,8 +315,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.") } @@ -299,6 +338,55 @@ 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) + randomize(randomKey) + id1, err = w.AddSymKeyDirect(randomKey) + if err == nil { + t.Fatalf("added the key with wrong size, seed %d.", seed) + } + + const password = "arbitrary data here" + id1, err = w.AddSymKeyFromPassword(password) + if err != nil { + t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err) + } + id2, err = w.AddSymKeyFromPassword(password) + if err != nil { + t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err) + } + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } + if !w.HasSymKey(id1) { + t.Fatalf("HasSymKey(id1) failed.") + } + if !w.HasSymKey(id2) { + t.Fatalf("HasSymKey(id2) failed.") + } + if k1 == nil { + t.Fatalf("k1 does not exist.") + } + if k2 == nil { + t.Fatalf("k2 does not exist.") + } + if !bytes.Equal(k1, k2) { + t.Fatalf("k1 != k2.") + } + if len(k1) != aesKeyLength { + t.Fatalf("wrong length of k1.") + } + if len(k2) != aesKeyLength { + t.Fatalf("wrong length of k2.") + } + if !validateSymmetricKey(k2) { + t.Fatalf("key validation failed.") + } } func TestExpiry(t *testing.T) { From 490d4701e10a18d7aa990cd21703b12a151b1245 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 2 Mar 2017 17:22:30 +0100 Subject: [PATCH 05/18] whisper: identity management refactored --- cmd/wnode/main.go | 22 +++++- whisper/mailserver/server_test.go | 9 ++- whisper/whisperv5/api.go | 51 ++++++++++---- whisper/whisperv5/whisper.go | 34 +++++++--- whisper/whisperv5/whisper_test.go | 109 +++++++++++++++++++++--------- 5 files changed, 166 insertions(+), 59 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 1746247f01..3e7219e15a 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -64,6 +64,7 @@ var ( asymKey *ecdsa.PrivateKey nodeid *ecdsa.PrivateKey topic whisper.TopicType + asymKeyID string filterID string symPass string msPassword string @@ -198,9 +199,26 @@ func initialize() { shh = whisper.New() } - asymKey = shh.NewIdentity() + asymKeyID, err = shh.NewIdentity() + if err != nil { + utils.Fatalf("Failed to generate a new key pair: %s", err) + } + + asymKey, err = shh.GetIdentity(asymKeyID) + if err != nil { + utils.Fatalf("Failed to retrieve a new key pair: %s", err) + } + if nodeid == nil { - nodeid = shh.NewIdentity() + tmpID, err := shh.NewIdentity() + if err != nil { + utils.Fatalf("Failed to generate a new key pair: %s", err) + } + + nodeid, err = shh.GetIdentity(tmpID) + if err != nil { + utils.Fatalf("Failed to retrieve a new key pair: %s", err) + } } maxPeers := 80 diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index 9304d81774..e9ae6877a6 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -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.NewIdentity() + if err != nil { + t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err) + } + testPeerID, err := shh.GetIdentity(id) + if err != nil { + t.Fatalf("failed to retireve new key pair with seed %d: %s.", seed, err) + } birth := env.Expiry - env.TTL p := &ServerTestParams{ topic: env.Topic, diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 456e16fa21..0c31eb3835 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -108,19 +108,19 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error { // HasIdentity checks if the whisper node is configured with the private key // of the specified public pair. -func (api *PublicWhisperAPI) HasIdentity(identity string) (bool, error) { +func (api *PublicWhisperAPI) HasIdentity(id string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } - return api.whisper.HasIdentity(identity), nil + return api.whisper.HasIdentity(id), nil } // DeleteIdentity deletes the specifies key if it exists. -func (api *PublicWhisperAPI) DeleteIdentity(identity string) (bool, error) { +func (api *PublicWhisperAPI) DeleteIdentity(id string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } - success := api.whisper.DeleteIdentity(identity) + success := api.whisper.DeleteIdentity(id) return success, nil } @@ -130,17 +130,32 @@ func (api *PublicWhisperAPI) NewIdentity() (string, error) { if api.whisper == nil { return "", whisperOffLineErr } - identity := api.whisper.NewIdentity() - return common.ToHex(crypto.FromECDSAPub(&identity.PublicKey)), nil + return api.whisper.NewIdentity() } -// todo: implement -//func (api *PublicWhisperAPI) GetPublicKey(id string) (bool, error) { -// if api.whisper == nil { -// return false, whisperOffLineErr -// } -// return api.whisper.HasIdentity(identity), nil -//} +// GetPublicKey returns the public key for identity id +func (api *PublicWhisperAPI) GetPublicKey(id string) (string, error) { + if api.whisper == nil { + return "", whisperOffLineErr + } + key, err := api.whisper.GetIdentity(id) + if err != nil { + return "", err + } + return common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), nil +} + +// GetPrivateKey returns the private key for identity id +func (api *PublicWhisperAPI) GetPrivateKey(id string) (string, error) { + if api.whisper == nil { + return "", whisperOffLineErr + } + key, err := api.whisper.GetIdentity(id) + if err != nil { + return "", err + } + return common.ToHex(crypto.FromECDSA(key)), nil +} // GenerateSymKey generates a random symmetric key and stores it under id, // which is then returned. Will be used in the future for session key exchange. @@ -249,7 +264,10 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { log.Error(fmt.Sprintf(info)) return "", errors.New(info) } - filter.KeyAsym = api.whisper.GetIdentity(string(args.To)) + filter.KeyAsym, err = api.whisper.GetIdentity(string(args.To)) + if err != nil { + return "", err + } if filter.KeyAsym == nil { info := "NewFilter: non-existent identity provided" log.Error(fmt.Sprintf(info)) @@ -334,7 +352,10 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { log.Error(fmt.Sprintf(info)) return errors.New(info) } - params.Src = api.whisper.GetIdentity(string(args.From)) + params.Src, err = api.whisper.GetIdentity(string(args.From)) + if err != nil { + return err + } if params.Src == nil { info := "Post: non-existent identity provided" log.Error(fmt.Sprintf(info)) diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index fd9f544817..cad7f2e425 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -188,22 +188,32 @@ func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error { } // 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 { +// it into the known identities for message decryption. Returns ID of the new key pair. +func (w *Whisper) NewIdentity() (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. @@ -220,17 +230,21 @@ func (w *Whisper) DeleteIdentity(key string) bool { // HasIdentity checks if the the whisper node is configured with the private key // of the specified public pair. -func (w *Whisper) HasIdentity(pubKey string) bool { +func (w *Whisper) HasIdentity(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 { +func (w *Whisper) GetIdentity(pubKey string) (*ecdsa.PrivateKey, error) { w.keyMu.RLock() defer w.keyMu.RUnlock() - return w.privateKeys[pubKey] + key := w.privateKeys[pubKey] + if key == nil { + return nil, fmt.Errorf("invalid id") + } + return key, nil } func (w *Whisper) GenerateSymKey() (string, error) { diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index 9a9142c0ca..c712fa6eb1 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -20,9 +20,6 @@ import ( "bytes" "testing" "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" ) func TestWhisperBasic(t *testing.T) { @@ -103,7 +100,14 @@ func TestWhisperBasic(t *testing.T) { t.Fatalf("failed BytesToIntBigEndian: %d.", be) } - pk := w.NewIdentity() + id, err := w.NewIdentity() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + pk, err := w.GetIdentity(id) + if err != nil { + t.Fatalf("failed to retrieve new key pair: %s.", err) + } if !validatePrivateKey(pk) { t.Fatalf("failed validatePrivateKey: %v.", pk) } @@ -114,67 +118,110 @@ 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) { + id1, err := w.NewIdentity() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + id2, err := w.NewIdentity() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + pk1, err := w.GetIdentity(id1) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + pk2, err := w.GetIdentity(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + // todo: delete + //pub1 := common.ToHex(crypto.FromECDSAPub(&id1.PublicKey)) + //pub2 := common.ToHex(crypto.FromECDSAPub(&id2.PublicKey)) + //pub1 := pk1.PublicKey + //pub2 := pk2.PublicKey + + if !w.HasIdentity(id1) { t.Fatalf("failed HasIdentity(pub1).") } - if !w.HasIdentity(pub2) { + if !w.HasIdentity(id2) { t.Fatalf("failed HasIdentity(pub2).") } - if pk1 != id1 { + if pk1 == nil { t.Fatalf("failed GetIdentity(pub1).") } - if pk2 != id2 { + if pk2 == nil { t.Fatalf("failed GetIdentity(pub2).") } // Delete one identity - w.DeleteIdentity(pub1) - pk1 = w.GetIdentity(pub1) - pk2 = w.GetIdentity(pub2) - if w.HasIdentity(pub1) { + done := w.DeleteIdentity(id1) + if !done { + t.Fatalf("failed to delete id1.") + } + pk1, err = w.GetIdentity(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetIdentity(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + if w.HasIdentity(id1) { t.Fatalf("failed DeleteIdentity(pub1): still exist.") } - if !w.HasIdentity(pub2) { + if !w.HasIdentity(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.DeleteIdentity(id1) + if done { + t.Fatalf("delete id1: false positive.") + } + pk1, err = w.GetIdentity(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetIdentity(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + if w.HasIdentity(id1) { t.Fatalf("failed delete non-existing identity: exist.") } - if !w.HasIdentity(pub2) { + if !w.HasIdentity(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.DeleteIdentity(id2) + if !done { + t.Fatalf("failed to delete id2.") + } + pk1, err = w.GetIdentity(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetIdentity(id2) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + if w.HasIdentity(id1) { t.Fatalf("failed delete second identity: first identity exist.") } - if w.HasIdentity(pub2) { + if w.HasIdentity(id2) { t.Fatalf("failed delete second identity: still exist.") } if pk1 != nil { From a25228a27c8517957adc8cdb415201a33d495be4 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 4 Mar 2017 13:29:15 +0100 Subject: [PATCH 06/18] whisper: API refactoring (Post and Filter) --- cmd/wnode/main.go | 16 +- whisper/mailserver/server_test.go | 4 +- whisper/whisperv5/api.go | 366 ++++++++++++++---------------- whisper/whisperv5/api_test.go | 197 ++++++++-------- whisper/whisperv5/filter.go | 16 +- whisper/whisperv5/filter_test.go | 4 +- whisper/whisperv5/message.go | 2 +- whisper/whisperv5/peer_test.go | 2 +- whisper/whisperv5/whisper.go | 49 ++-- whisper/whisperv5/whisper_test.go | 66 +++--- 10 files changed, 362 insertions(+), 360 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 3e7219e15a..f9a1296879 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -199,23 +199,23 @@ func initialize() { shh = whisper.New() } - asymKeyID, err = shh.NewIdentity() + asymKeyID, err = shh.NewKeyPair() if err != nil { utils.Fatalf("Failed to generate a new key pair: %s", err) } - asymKey, err = shh.GetIdentity(asymKeyID) + asymKey, err = shh.GetPrivateKey(asymKeyID) if err != nil { utils.Fatalf("Failed to retrieve a new key pair: %s", err) } if nodeid == nil { - tmpID, err := shh.NewIdentity() + tmpID, err := shh.NewKeyPair() if err != nil { utils.Fatalf("Failed to generate a new key pair: %s", err) } - nodeid, err = shh.GetIdentity(tmpID) + nodeid, err = shh.GetPrivateKey(tmpID) if err != nil { utils.Fatalf("Failed to retrieve a new key pair: %s", err) } @@ -325,10 +325,10 @@ func configureNode() { } filter := whisper.Filter{ - KeySym: symKey, - KeyAsym: asymKey, - Topics: []whisper.TopicType{topic}, - AcceptP2P: p2pAccept, + KeySym: symKey, + KeyAsym: asymKey, + Topics: []whisper.TopicType{topic}, + AllowP2P: p2pAccept, } filterID, err = shh.Watch(&filter) if err != nil { diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index e9ae6877a6..b0d3e06e49 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -102,11 +102,11 @@ func TestMailServer(t *testing.T) { } func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) { - id, err := 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.GetIdentity(id) + testPeerID, err := shh.GetPrivateKey(id) if err != nil { t.Fatalf("failed to retireve new key pair with seed %d: %s.", seed, err) } diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 0c31eb3835..9de29a6fee 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -20,12 +20,12 @@ 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") @@ -65,7 +65,7 @@ func (api *PublicWhisperAPI) Version() (hexutil.Uint, error) { } // Stats returns the Whisper statistics for diagnostics. -func (api *PublicWhisperAPI) Stats() (string, error) { +func (api *PublicWhisperAPI) Info() (string, error) { if api.whisper == nil { return "", whisperOffLineErr } @@ -108,29 +108,29 @@ func (api *PublicWhisperAPI) MarkPeerTrusted(peerID hexutil.Bytes) error { // HasIdentity checks if the whisper node is configured with the private key // of the specified public pair. -func (api *PublicWhisperAPI) HasIdentity(id string) (bool, error) { +func (api *PublicWhisperAPI) HasKeyPair(id string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } - return api.whisper.HasIdentity(id), nil + return api.whisper.HasKeyPair(id), nil } // DeleteIdentity deletes the specifies key if it exists. -func (api *PublicWhisperAPI) DeleteIdentity(id string) (bool, error) { +func (api *PublicWhisperAPI) DeleteKeyPair(id string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } - success := api.whisper.DeleteIdentity(id) + success := api.whisper.DeleteKeyPair(id) return success, 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 api.whisper.NewIdentity() + return api.whisper.NewKeyPair() } // GetPublicKey returns the public key for identity id @@ -138,7 +138,7 @@ func (api *PublicWhisperAPI) GetPublicKey(id string) (string, error) { if api.whisper == nil { return "", whisperOffLineErr } - key, err := api.whisper.GetIdentity(id) + key, err := api.whisper.GetPrivateKey(id) if err != nil { return "", err } @@ -150,7 +150,7 @@ func (api *PublicWhisperAPI) GetPrivateKey(id string) (string, error) { if api.whisper == nil { return "", whisperOffLineErr } - key, err := api.whisper.GetIdentity(id) + key, err := api.whisper.GetPrivateKey(id) if err != nil { return "", err } @@ -159,7 +159,7 @@ func (api *PublicWhisperAPI) GetPrivateKey(id string) (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 (api *PublicWhisperAPI) GenerateSymKey() (string, error) { +func (api *PublicWhisperAPI) GenerateSymmetricKey() (string, error) { if api.whisper == nil { return "", whisperOffLineErr } @@ -167,7 +167,7 @@ func (api *PublicWhisperAPI) GenerateSymKey() (string, error) { } // AddSymKeyDirect stores the key, and returns its id. -func (api *PublicWhisperAPI) AddSymKeyDirect(key hexutil.Bytes) (string, error) { +func (api *PublicWhisperAPI) AddSymmetricKeyDirect(key hexutil.Bytes) (string, error) { if api.whisper == nil { return "", whisperOffLineErr } @@ -175,7 +175,7 @@ func (api *PublicWhisperAPI) AddSymKeyDirect(key hexutil.Bytes) (string, error) } // AddSymKeyFromPassword generates the key from password, stores it, and returns its id. -func (api *PublicWhisperAPI) AddSymKeyFromPassword(password string) (string, error) { +func (api *PublicWhisperAPI) AddSymmetricKeyFromPassword(password string) (string, error) { if api.whisper == nil { return "", whisperOffLineErr } @@ -184,7 +184,7 @@ func (api *PublicWhisperAPI) AddSymKeyFromPassword(password string) (string, err // HasSymKey returns true if there is a key associated with the given id. // Otherwise returns false. -func (api *PublicWhisperAPI) HasSymKey(id string) (bool, error) { +func (api *PublicWhisperAPI) HasSymmetricKey(id string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } @@ -192,7 +192,7 @@ func (api *PublicWhisperAPI) HasSymKey(id string) (bool, error) { return res, nil } -func (api *PublicWhisperAPI) GetSymKey(name string) ([]byte, error) { +func (api *PublicWhisperAPI) GetSymmetricKey(name string) ([]byte, error) { if api.whisper == nil { return nil, whisperOffLineErr } @@ -200,7 +200,7 @@ func (api *PublicWhisperAPI) GetSymKey(name string) ([]byte, error) { } // DeleteSymKey deletes the key associated with the name string if it exists. -func (api *PublicWhisperAPI) DeleteSymKey(name string) (bool, error) { +func (api *PublicWhisperAPI) DeleteSymmetricKey(name string) (bool, error) { if api.whisper == nil { return false, whisperOffLineErr } @@ -208,77 +208,72 @@ func (api *PublicWhisperAPI) DeleteSymKey(name string) (bool, error) { return res, nil } -// NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages. -// Returns the ID of the newly created Filter. -func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { +// 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 } - var err error - var symKey []byte - - if len(args.KeyName) > 0 { - symKey, err = api.whisper.GetSymKey(args.KeyName) - if err != nil { - info := "NewFilter: symmetric key ID does not exist: " + args.KeyName - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) - } - } - filter := Filter{ - Src: crypto.ToECDSAPub(common.FromHex(args.From)), - KeySym: symKey, - PoW: args.PoW, - Messages: make(map[common.Hash]*ReceivedMessage), - AcceptP2P: args.AcceptP2P, - } - if len(filter.KeySym) > 0 { - filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) + Src: crypto.ToECDSAPub(common.FromHex(args.SignedWith)), + PoW: args.MinPoW, + Messages: make(map[common.Hash]*ReceivedMessage), + AllowP2P: args.AllowP2P, } + filter.Topics = append(filter.Topics, args.Topics...) - 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) - } - - if len(args.To) == 0 && len(filter.KeySym) == 0 { - info := "NewFilter: filter must contain either symmetric or asymmetric key" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) - } - - if len(args.To) != 0 && len(filter.KeySym) != 0 { - info := "NewFilter: filter must not contain both symmetric and asymmetric key" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) - } - - if len(args.To) > 0 { - dst := crypto.ToECDSAPub(common.FromHex(args.To)) - if !ValidatePublicKey(dst) { - info := "NewFilter: Invalid 'To' address" - log.Error(fmt.Sprintf(info)) + if len(args.SignedWith) > 0 { + if !ValidatePublicKey(filter.Src) { + info := "NewFilter: Invalid 'From' address" + log.Error(info) return "", errors.New(info) } - filter.KeyAsym, err = api.whisper.GetIdentity(string(args.To)) + } + + err := ValidateKeyID(args.Key) + if err != nil { + return "", err + } + + if !ValidatePublicKey(filter.Src) { + info := "NewFilter: 'SignedWith' public key is invalid" + log.Error(info) + return "", errors.New(info) + } + + if args.Symmetric { + if len(args.Topics) == 0 { + info := "NewFilter: at least one topic must be specified with symmetric encryption" + log.Error(info) + return "", errors.New(info) + } + + symKey, err := api.whisper.GetSymKey(args.Key) if err != nil { - return "", err + info := "NewFilter: invalid key ID: " + args.Key + log.Error(info) + return "", errors.New(info) + } + if !validateSymmetricKey(symKey) { + info := "NewFilter: retrieved key is invalid" + log.Error(info) + return "", errors.New(info) + } + + filter.KeySym = symKey + filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) + } else { + filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key) + if err != nil { + info := "NewFilter: invalid key ID: " + args.Key + log.Error(info) + return "", errors.New(info) } if filter.KeyAsym == nil { info := "NewFilter: non-existent identity provided" - log.Error(fmt.Sprintf(info)) - return "", errors.New(info) - } - } - - if len(args.From) > 0 { - if !ValidatePublicKey(filter.Src) { - info := "NewFilter: Invalid 'From' address" - log.Error(fmt.Sprintf(info)) + log.Error(info) return "", errors.New(info) } } @@ -286,9 +281,9 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { return api.whisper.Watch(&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. @@ -323,144 +318,110 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { } var err error - var symKey []byte - - if len(args.KeyName) > 0 { - symKey, err = api.whisper.GetSymKey(args.KeyName) - if err != nil { - info := "NewFilter: symmetric key ID does not exist: " + args.KeyName - log.Error(fmt.Sprintf(info)) - return errors.New(info) - } - } - params := MessageParams{ TTL: args.TTL, - Dst: crypto.ToECDSAPub(common.FromHex(args.To)), - KeySym: symKey, - 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) - } - params.Src, err = api.whisper.GetIdentity(string(args.From)) + if len(args.SignWith) > 0 { + params.Src, err = api.whisper.GetPrivateKey(args.SignWith) if err != nil { + log.Error(err.Error()) return err } if params.Src == nil { - info := "Post: non-existent identity provided" - log.Error(fmt.Sprintf(info)) + info := "Post: empty identity" + log.Error(info) return errors.New(info) } } - 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)) + if len(args.Topic) == TopicLength { + params.Topic = BytesToTopic(args.Topic) + } else if len(args.Topic) != 0 { + info := fmt.Sprintf("Post: wrong topic size %d", len(args.Topic)) + log.Error(info) return errors.New(info) } - if filter != nil { - // get the missing fields from the filter - if params.KeySym == nil && filter.KeySym != nil { - params.KeySym = filter.KeySym + if args.Type == "sym" { + err = ValidateKeyID(args.Key) + if err != nil { + log.Error(err.Error()) + return err } - if params.Src == nil && filter.Src != nil { - params.Src = filter.KeyAsym + params.KeySym, err = api.whisper.GetSymKey(args.Key) + if err != nil { + log.Error(err.Error()) + 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 len(params.Topic) == 0 { + info := "Post: topic is missing for symmetric encryption" + log.Error(info) + return errors.New(info) } - } - - // validate - 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 { + } 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)) + info := "NewFilter: 'SignWith' public key is invalid" + log.Error(info) return errors.New(info) } + } else { + info := "Post: wrong type (sym/asym)" + log.Error(info) + return errors.New(info) } // encrypt and send message := NewSentMessage(¶ms) envelope, err := message.Wrap(¶ms) if err != nil { - log.Error(fmt.Sprintf(err.Error())) + log.Error(err.Error()) return err } if envelope.size() > api.whisper.maxMsgLength { 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)) + log.Error(info) return errors.New(info) } - 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 { + info := "Post: failed to parse enode of target peer: " + err.Error() + log.Error(info) + return errors.New(info) + } + return api.whisper.SendP2PMessage(n.ID[:], envelope) } 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"` + TTL uint32 `json:"ttl"` + SignWith string `json:"signWith"` + Key string `json:"key"` + Topic hexutil.Bytes `json:"topic"` + Padding hexutil.Bytes `json:"padding"` + Payload hexutil.Bytes `json:"payload"` + PowTime uint32 `json:"powTime"` + PowTarget float64 `json:"powTarget"` + TargetPeer string `json:"targetPeer"` } 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 + Key string + SignedWith string + MinPoW float64 + Topics []TopicType + AllowP2P bool } // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a @@ -468,22 +429,29 @@ 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 + if obj.Type == "sym" { + args.Symmetric = true + } else if obj.Type == "asym" { + args.Symmetric = false + } else { + return fmt.Errorf("Wrong type (sym/asym") + } + + args.Key = obj.Key + args.SignedWith = obj.SignedWith + args.MinPoW = obj.MinPoW + args.AllowP2P = obj.AllowP2P // Construct the topic array if obj.Topics != nil { @@ -514,34 +482,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:"receipientPublicKey"` + 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 a94909ecca..558f125446 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) { @@ -47,7 +48,7 @@ func TestBasic(t *testing.T) { 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,7 +56,7 @@ func TestBasic(t *testing.T) { t.Fatalf("failed initial HasIdentity: false positive.") } - success, err := api.DeleteIdentity(id) + success, err := api.DeleteKeyPair(id) if err != nil { t.Fatalf("failed DeleteIdentity: %s.", err) } @@ -63,7 +64,7 @@ func TestBasic(t *testing.T) { 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) } @@ -71,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) } @@ -79,7 +80,7 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasIdentity: false negative.") } - success, err = api.DeleteIdentity(pub) + success, err = api.DeleteKeyPair(pub) if err != nil { t.Fatalf("failed to delete second identity: %s.", err) } @@ -87,7 +88,7 @@ func TestBasic(t *testing.T) { 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) } @@ -98,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) } @@ -106,12 +107,12 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey: false positive.") } - id, err = api.GenerateSymKey() + 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) } @@ -120,17 +121,17 @@ func TestBasic(t *testing.T) { } const password = "some stuff here" - id, err = api.AddSymKeyFromPassword(password) + id, err = api.AddSymmetricKeyFromPassword(password) if err != nil { t.Fatalf("failed AddSymKey: %s.", err) } - id2, err = api.AddSymKeyFromPassword(password) + 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) } @@ -138,11 +139,11 @@ func TestBasic(t *testing.T) { t.Fatalf("failed HasSymKey(id2): false negative.") } - k1, err := api.GetSymKey(id) + k1, err := api.GetSymmetricKey(id) if err != nil { t.Fatalf("failed GetSymKey(id): %s.", err) } - k2, err := api.GetSymKey(id2) + k2, err := api.GetSymmetricKey(id2) if err != nil { t.Fatalf("failed GetSymKey(id2): %s.", err) } @@ -151,7 +152,7 @@ func TestBasic(t *testing.T) { t.Fatalf("installed keys are not equal") } - exist, err = api.DeleteSymKey(id) + exist, err = api.DeleteSymmetricKey(id) if err != nil { t.Fatalf("failed DeleteSymKey(id): %s.", err) } @@ -159,7 +160,7 @@ func TestBasic(t *testing.T) { 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) } @@ -170,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":"asym", + "key":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", + "signedWith":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", + "minPoW":2.34, "topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"], - "p2p":true + "allowP2P":true }`) var f WhisperFilterArgs @@ -184,20 +185,20 @@ 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)) @@ -226,17 +227,16 @@ func TestUnmarshalFilterArgs(t *testing.T) { 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 @@ -245,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.SignWith != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { + t.Fatalf("wrong From: %s.", a.SignWith) } - if a.To != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { - t.Fatalf("wrong To: %x.", a.To) + if a.Key != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { + t.Fatalf("wrong Key: %s.", a.Key) } - if a.KeyName != "shh_test" { - t.Fatalf("wrong KeyName: %s.", a.KeyName) - } - 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" { @@ -266,17 +267,14 @@ 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.PowTime != 777 { + t.Fatalf("wrong PowTime: %d.", a.PowTime) } - if a.PoW != 3.1416 { - t.Fatalf("wrong pow: %f.", a.PoW) + if a.PowTarget != 3.1416 { + t.Fatalf("wrong PowTarget: %f.", a.PowTarget) } - if a.FilterID != "test-filter-id" { - t.Fatalf("wrong FilterID: %s.", a.FilterID) - } - 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) } } @@ -309,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) } @@ -317,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) } @@ -325,7 +323,7 @@ func TestIntegrationAsym(t *testing.T) { t.Fatalf("failed HasIdentity: false negative.") } - key, err := api.NewIdentity() + key, err := api.NewKeyPair() if err != nil { t.Fatalf("failed NewIdentity(): %s.", err) } @@ -337,26 +335,27 @@ func TestIntegrationAsym(t *testing.T) { topics[0] = TopicType{0x00, 0x64, 0x00, 0xff} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} var f WhisperFilterArgs - f.To = key - f.From = sig + f.Symmetric = false + f.Key = key + f.SignedWith = sig f.Topics = topics[:] - f.PoW = DefaultMinimumPoW / 2 - f.AcceptP2P = true + 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.TTL = 2 - p.From = f.From - p.To = f.To + p.SignWith = f.SignedWith + p.Key = f.Key p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = DefaultMinimumPoW - 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 { @@ -401,12 +400,12 @@ func TestIntegrationSym(t *testing.T) { api.Start() defer api.Stop() - keyID, err := api.GenerateSymKey() + 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) } @@ -414,7 +413,7 @@ func TestIntegrationSym(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) } @@ -426,26 +425,28 @@ func TestIntegrationSym(t *testing.T) { topics[0] = TopicType{0x00, 0x7f, 0x80, 0xff} topics[1] = TopicType{0xf2, 0x6e, 0x77, 0x79} var f WhisperFilterArgs - f.KeyName = keyID + f.Symmetric = true + f.Key = symKeyID f.Topics = topics[:] - f.PoW = 0.324 - f.From = sig - f.AcceptP2P = false + f.MinPoW = 0.324 + f.SignedWith = sig + 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 = keyID - p.From = f.From + p.Key = symKeyID + p.SignWith = f.SignedWith p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = DefaultMinimumPoW - 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 { @@ -490,20 +491,20 @@ func TestIntegrationSymWithFilter(t *testing.T) { api.Start() defer api.Stop() - keyID, err := api.GenerateSymKey() + 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) } @@ -511,30 +512,36 @@ 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 = keyID + f.Symmetric = true + f.Key = symKeyID f.Topics = topics[:] - f.PoW = 0.324 - f.From = sig - f.AcceptP2P = false + f.MinPoW = 0.324 + f.SignedWith = sigPubKey + 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.SignWith = sigKeyID p.Padding = []byte("test string") p.Payload = []byte("extended test string") - p.PoW = DefaultMinimumPoW - 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 { diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index cfb8f0f4b0..f18b4ee292 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -31,11 +31,11 @@ type Filter struct { KeySym []byte // Key associated with the Topic Topics []TopicType // Topics to filter messages with PoW float64 // Proof of work as described in the Whisper spec - AcceptP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages + 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 - mutex sync.RWMutex + Messages map[common.Hash]*ReceivedMessage + mutex sync.RWMutex } type Filters struct { @@ -72,10 +72,14 @@ func (fs *Filters) Install(watcher *Filter) (string, error) { 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 { @@ -93,7 +97,7 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { for _, watcher := range fs.watchers { j++ - if p2pMessage && !watcher.AcceptP2P { + if p2pMessage && !watcher.AllowP2P { log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), j)) continue } diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go index b4f52a8185..bdd2e6d6c6 100644 --- a/whisper/whisperv5/filter_test.go +++ b/whisper/whisperv5/filter_test.go @@ -491,7 +491,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 } @@ -655,7 +655,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) diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index c5103578c8..afbce7d890 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -264,7 +264,7 @@ func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []by } 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)) + log.Error(info) return errors.New(info) } decrypted, err := aesgcm.Open(nil, nonce, msg.Raw, nil) diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index 110c3af531..9df2a060e8 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -166,7 +166,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() } diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index cad7f2e425..42cca2ee12 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -36,9 +36,11 @@ import ( ) 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 @@ -189,7 +191,7 @@ func (w *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error { // NewIdentity 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) NewIdentity() (string, error) { +func (w *Whisper) NewKeyPair() (string, error) { key, err := crypto.GenerateKey() if err != nil || !validatePrivateKey(key) { key, err = crypto.GenerateKey() // retry once @@ -217,7 +219,7 @@ func (w *Whisper) NewIdentity() (string, error) { } // DeleteIdentity deletes the specified key if it exists. -func (w *Whisper) DeleteIdentity(key string) bool { +func (w *Whisper) DeleteKeyPair(key string) bool { w.keyMu.Lock() defer w.keyMu.Unlock() @@ -230,14 +232,14 @@ func (w *Whisper) DeleteIdentity(key string) bool { // HasIdentity checks if the the whisper node is configured with the private key // of the specified public pair. -func (w *Whisper) HasIdentity(id string) bool { +func (w *Whisper) HasKeyPair(id string) bool { w.keyMu.RLock() defer w.keyMu.RUnlock() return w.privateKeys[id] != nil } // GetIdentity retrieves the private key of the specified public identity. -func (w *Whisper) GetIdentity(pubKey string) (*ecdsa.PrivateKey, error) { +func (w *Whisper) GetPrivateKey(pubKey string) (*ecdsa.PrivateKey, error) { w.keyMu.RLock() defer w.keyMu.RUnlock() key := w.privateKeys[pubKey] @@ -361,9 +363,13 @@ 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("Invalid ID") + } + return nil } // Send injects a message into the whisper send queue, to be distributed in the @@ -557,7 +563,7 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { log.Trace(fmt.Sprintf("whisper envelope already cached [%x]\n", envelope.Hash())) } else { log.Trace(fmt.Sprintf("cached whisper envelope [%x]: %v\n", envelope.Hash(), envelope)) - wh.stats.totalMemoryUsed += envelope.size() + wh.stats.memoryUsed += envelope.size() wh.postEvent(envelope, false) // notify the local node about the new message if wh.mailServer != nil { wh.mailServer.Archive(envelope) @@ -649,7 +655,7 @@ func (w *Whisper) expire() { hashSet.Each(func(v interface{}) bool { sz := w.envelopes[v.(common.Hash)].size() w.stats.memoryCleared += sz - w.stats.totalMemoryUsed -= sz + w.stats.memoryUsed -= sz delete(w.envelopes, v.(common.Hash)) return true }) @@ -660,8 +666,13 @@ func (w *Whisper) expire() { } 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.\nAverage messages cleared per expiry cycle: %d.", + w.stats.memoryUsed, w.stats.totalMessagesCleared/w.stats.cycles) + if w.stats.messagesCleared > 0 { + result += fmt.Sprintf("\nLatest expiry cycle cleared %d messages (%d bytes).", + w.stats.messagesCleared, w.stats.memoryCleared) + } + return result } // envelopes retrieves all the messages currently pooled by the node. @@ -703,10 +714,20 @@ func (w *Whisper) isEnvelopeCached(hash common.Hash) bool { } func (s *Statistics) reset() { + s.cycles++ + s.totalMessagesCleared += s.messagesCleared + s.memoryCleared = 0 s.messagesCleared = 0 } +func ValidateKeyID(id string) error { + if len(id) != keyIdSize*2 { + return fmt.Errorf("Wrong size of key ID") + } + return nil +} + func ValidatePublicKey(k *ecdsa.PublicKey) bool { return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 } diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index c712fa6eb1..de86ff4ad7 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -100,11 +100,11 @@ func TestWhisperBasic(t *testing.T) { t.Fatalf("failed BytesToIntBigEndian: %d.", be) } - id, err := w.NewIdentity() + id, err := w.NewKeyPair() if err != nil { t.Fatalf("failed to generate new key pair: %s.", err) } - pk, err := w.GetIdentity(id) + pk, err := w.GetPrivateKey(id) if err != nil { t.Fatalf("failed to retrieve new key pair: %s.", err) } @@ -118,58 +118,60 @@ func TestWhisperBasic(t *testing.T) { func TestWhisperIdentityManagement(t *testing.T) { w := New() - id1, err := w.NewIdentity() + id1, err := w.NewKeyPair() if err != nil { t.Fatalf("failed to generate new key pair: %s.", err) } - id2, err := w.NewIdentity() + id2, err := w.NewKeyPair() if err != nil { t.Fatalf("failed to generate new key pair: %s.", err) } - pk1, err := w.GetIdentity(id1) + pk1, err := w.GetPrivateKey(id1) if err != nil { t.Fatalf("failed to retrieve the key pair: %s.", err) } - pk2, err := w.GetIdentity(id2) + pk2, err := w.GetPrivateKey(id2) if err != nil { t.Fatalf("failed to retrieve the key pair: %s.", err) } - // todo: delete - //pub1 := common.ToHex(crypto.FromECDSAPub(&id1.PublicKey)) - //pub2 := common.ToHex(crypto.FromECDSAPub(&id2.PublicKey)) - //pub1 := pk1.PublicKey - //pub2 := pk2.PublicKey - if !w.HasIdentity(id1) { - t.Fatalf("failed HasIdentity(pub1).") + if !w.HasKeyPair(id1) { + t.Fatalf("failed HasIdentity(pk1).") } - if !w.HasIdentity(id2) { - t.Fatalf("failed HasIdentity(pub2).") + if !w.HasKeyPair(id2) { + t.Fatalf("failed HasIdentity(pk2).") } if pk1 == nil { - t.Fatalf("failed GetIdentity(pub1).") + t.Fatalf("failed GetIdentity(pk1).") } if pk2 == nil { - t.Fatalf("failed GetIdentity(pub2).") + t.Fatalf("failed GetIdentity(pk2).") + } + + if !validatePrivateKey(pk1) { + t.Fatalf("pk1 is invalid.") + } + if !validatePrivateKey(pk2) { + t.Fatalf("pk2 is invalid.") } // Delete one identity - done := w.DeleteIdentity(id1) + done := w.DeleteKeyPair(id1) if !done { t.Fatalf("failed to delete id1.") } - pk1, err = w.GetIdentity(id1) + pk1, err = w.GetPrivateKey(id1) if err == nil { t.Fatalf("retrieve the key pair: false positive.") } - pk2, err = w.GetIdentity(id2) + pk2, err = w.GetPrivateKey(id2) if err != nil { t.Fatalf("failed to retrieve the key pair: %s.", err) } - if w.HasIdentity(id1) { + if w.HasKeyPair(id1) { t.Fatalf("failed DeleteIdentity(pub1): still exist.") } - if !w.HasIdentity(id2) { + if !w.HasKeyPair(id2) { t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.") } if pk1 != nil { @@ -180,22 +182,22 @@ func TestWhisperIdentityManagement(t *testing.T) { } // Delete again non-existing identity - done = w.DeleteIdentity(id1) + done = w.DeleteKeyPair(id1) if done { t.Fatalf("delete id1: false positive.") } - pk1, err = w.GetIdentity(id1) + pk1, err = w.GetPrivateKey(id1) if err == nil { t.Fatalf("retrieve the key pair: false positive.") } - pk2, err = w.GetIdentity(id2) + pk2, err = w.GetPrivateKey(id2) if err != nil { t.Fatalf("failed to retrieve the key pair: %s.", err) } - if w.HasIdentity(id1) { + if w.HasKeyPair(id1) { t.Fatalf("failed delete non-existing identity: exist.") } - if !w.HasIdentity(id2) { + if !w.HasKeyPair(id2) { t.Fatalf("failed delete non-existing identity: pub2 does not exist.") } if pk1 != nil { @@ -206,22 +208,22 @@ func TestWhisperIdentityManagement(t *testing.T) { } // Delete second identity - done = w.DeleteIdentity(id2) + done = w.DeleteKeyPair(id2) if !done { t.Fatalf("failed to delete id2.") } - pk1, err = w.GetIdentity(id1) + pk1, err = w.GetPrivateKey(id1) if err == nil { t.Fatalf("retrieve the key pair: false positive.") } - pk2, err = w.GetIdentity(id2) + pk2, err = w.GetPrivateKey(id2) if err == nil { t.Fatalf("retrieve the key pair: false positive.") } - if w.HasIdentity(id1) { + if w.HasKeyPair(id1) { t.Fatalf("failed delete second identity: first identity exist.") } - if w.HasIdentity(id2) { + if w.HasKeyPair(id2) { t.Fatalf("failed delete second identity: still exist.") } if pk1 != nil { From cd1c987c92e9085af96af3a5308e11e333380840 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sun, 5 Mar 2017 12:07:54 +0100 Subject: [PATCH 07/18] whisper: big refactoring complete --- whisper/whisperv5/api.go | 38 ++++++++++++++++++++--------------- whisper/whisperv5/api_test.go | 29 ++++++++++++++++++++------ whisper/whisperv5/whisper.go | 20 +++++++++--------- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 9de29a6fee..1093906a39 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -224,35 +224,30 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { filter.Topics = append(filter.Topics, args.Topics...) + err := ValidateKeyID(args.Key) + if err != nil { + info := "NewFilter: " + err.Error() + log.Error(info) + return "", errors.New(info) + } + if len(args.SignedWith) > 0 { if !ValidatePublicKey(filter.Src) { - info := "NewFilter: Invalid 'From' address" + info := "NewFilter: Invalid 'SignedWith' field" log.Error(info) return "", errors.New(info) } } - err := ValidateKeyID(args.Key) - if err != nil { - return "", err - } - - if !ValidatePublicKey(filter.Src) { - info := "NewFilter: 'SignedWith' public key is invalid" - log.Error(info) - return "", errors.New(info) - } - if args.Symmetric { if len(args.Topics) == 0 { info := "NewFilter: at least one topic must be specified with symmetric encryption" log.Error(info) return "", errors.New(info) } - symKey, err := api.whisper.GetSymKey(args.Key) if err != nil { - info := "NewFilter: invalid key ID: " + args.Key + info := "NewFilter: invalid key ID" log.Error(info) return "", errors.New(info) } @@ -267,7 +262,7 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { } else { filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key) if err != nil { - info := "NewFilter: invalid key ID: " + args.Key + info := "NewFilter: invalid key ID" log.Error(info) return "", errors.New(info) } @@ -326,6 +321,12 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { Padding: args.Padding, } + if len(args.Key) == 0 { + info := "Post: key is missing" + log.Error(info) + return errors.New(info) + } + if len(args.SignWith) > 0 { params.Src, err = api.whisper.GetPrivateKey(args.SignWith) if err != nil { @@ -358,6 +359,11 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { log.Error(err.Error()) return err } + if !validateSymmetricKey(params.KeySym) { + info := "Post: key for symmetric encryption is invalid" + log.Error(info) + return errors.New(info) + } if len(params.Topic) == 0 { info := "Post: topic is missing for symmetric encryption" log.Error(info) @@ -366,7 +372,7 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { } else if args.Type == "asym" { params.Dst = crypto.ToECDSAPub(common.FromHex(args.Key)) if !ValidatePublicKey(params.Dst) { - info := "NewFilter: 'SignWith' public key is invalid" + info := "Post: public key for asymmetric encryption is invalid" log.Error(info) return errors.New(info) } diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index 558f125446..e9df3f9a53 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -323,6 +323,11 @@ func TestIntegrationAsym(t *testing.T) { t.Fatalf("failed HasIdentity: false negative.") } + 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) @@ -331,13 +336,18 @@ 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.Symmetric = false f.Key = key - f.SignedWith = sig + f.SignedWith = sigPubKey f.Topics = topics[:] f.MinPoW = DefaultMinimumPoW / 2 f.AllowP2P = true @@ -348,9 +358,10 @@ func TestIntegrationAsym(t *testing.T) { } var p PostArgs + p.Type = "asym" p.TTL = 2 - p.SignWith = f.SignedWith - p.Key = f.Key + p.SignWith = sig + p.Key = dstPubKey p.Padding = []byte("test string") p.Payload = []byte("extended test string") p.PowTarget = DefaultMinimumPoW @@ -407,12 +418,17 @@ func TestIntegrationSym(t *testing.T) { 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") } + 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) @@ -429,7 +445,7 @@ func TestIntegrationSym(t *testing.T) { f.Key = symKeyID f.Topics = topics[:] f.MinPoW = 0.324 - f.SignedWith = sig + f.SignedWith = sigPubKey f.AllowP2P = false id, err := api.Subscribe(f) @@ -441,7 +457,7 @@ func TestIntegrationSym(t *testing.T) { p.Type = "sym" p.TTL = 1 p.Key = symKeyID - p.SignWith = f.SignedWith + p.SignWith = sig p.Padding = []byte("test string") p.Payload = []byte("extended test string") p.PowTarget = DefaultMinimumPoW @@ -536,6 +552,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { var p PostArgs p.Type = "sym" p.TTL = 1 + p.Key = symKeyID p.SignWith = sigKeyID p.Padding = []byte("test string") p.Payload = []byte("extended test string") diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 42cca2ee12..e4637dc6ad 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -50,9 +50,8 @@ type Whisper struct { filters *Filters privateKeys map[string]*ecdsa.PrivateKey - //identities map[string]*ecdsa.PrivateKey - symKeys map[string][]byte - keyMu sync.RWMutex + symKeys map[string][]byte + keyMu sync.RWMutex envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node expirations map[uint32]*set.SetNonTS // Message expiration pool @@ -238,13 +237,13 @@ func (w *Whisper) HasKeyPair(id string) bool { return w.privateKeys[id] != nil } -// GetIdentity retrieves the private key of the specified public identity. -func (w *Whisper) GetPrivateKey(pubKey string) (*ecdsa.PrivateKey, error) { +// GetIdentity retrieves the private key of the specified identity. +func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { w.keyMu.RLock() defer w.keyMu.RUnlock() - key := w.privateKeys[pubKey] + key := w.privateKeys[id] if key == nil { - return nil, fmt.Errorf("invalid id") + return nil, fmt.Errorf("GetPrivateKey: invalid id") } return key, nil } @@ -367,7 +366,7 @@ func (w *Whisper) GetFilter(id string) *Filter { func (w *Whisper) Unsubscribe(id string) error { ok := w.filters.Uninstall(id) if !ok { - return fmt.Errorf("Invalid ID") + return fmt.Errorf("Unsubscribe: Invalid ID") } return nil } @@ -722,8 +721,9 @@ func (s *Statistics) reset() { } func ValidateKeyID(id string) error { - if len(id) != keyIdSize*2 { - return fmt.Errorf("Wrong size of key ID") + 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 } From 539e3f35865c72834a6585bced7e14ced5c624df Mon Sep 17 00:00:00 2001 From: Vlad Date: Sun, 5 Mar 2017 12:40:04 +0100 Subject: [PATCH 08/18] whisper: spelling fix --- whisper/mailserver/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index b0d3e06e49..ffdff31915 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -108,7 +108,7 @@ func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) { } testPeerID, err := shh.GetPrivateKey(id) if err != nil { - t.Fatalf("failed to retireve new key pair with seed %d: %s.", seed, err) + t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err) } birth := env.Expiry - env.TTL p := &ServerTestParams{ From ffd1bf83edd51492fbc151ecd1ce11f73a95c1d8 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 14 Mar 2017 23:59:54 +0100 Subject: [PATCH 09/18] whisper: final update --- cmd/wnode/main.go | 91 +++++++++++++++++++++++++++---- whisper/whisperv5/api.go | 12 +++- whisper/whisperv5/doc.go | 2 +- whisper/whisperv5/whisper.go | 5 +- whisper/whisperv5/whisper_test.go | 2 +- 5 files changed, 94 insertions(+), 18 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index f9a1296879..5fb10d52a8 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -32,6 +32,10 @@ import ( "strings" "time" + "io/ioutil" + + "path/filepath" + "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console" @@ -80,6 +84,7 @@ var ( asymmetricMode = flag.Bool("a", false, "use asymmetric encryption") testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics") generateKey = flag.Bool("k", false, "generate and show the private key") + fileExMode = flag.Bool("x", false, "file exchange mode") argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level") argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") @@ -87,12 +92,13 @@ var ( argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request") - argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") - argPub = flag.String("pub", "", "public key for asymmetric encryption") - argDBPath = flag.String("dbpath", "", "path to the server's DB directory") - argIDFile = flag.String("idfile", "", "file name with node id (private key)") - argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)") - argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)") + argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") + argPub = flag.String("pub", "", "public key for asymmetric encryption") + argDBPath = flag.String("dbpath", "", "path to the server's DB directory") + argIDFile = flag.String("idfile", "", "file name with node id (private key)") + argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)") + argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)") + argSaveDir = flag.String("savedir", "", "directory where incoming messages will be saved as files") ) func main() { @@ -134,6 +140,14 @@ func processArgs() { } } + if len(*argSaveDir) > 0 { + if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) { + utils.Fatalf("Download directory '%s' does not exist", *argSaveDir) + } + } else if *fileExMode { + utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode") + } + if *echoMode { echo() } @@ -374,6 +388,8 @@ func run() { if *requestMail { requestExpiredMessagesLoop() + } else if *fileExMode { + sendFilesLoop() } else { sendLoop() } @@ -399,6 +415,31 @@ func sendLoop() { } } +func sendFilesLoop() { + for { + s := scanLine("") + if s == quitCommand { + fmt.Println("Quit command received") + close(done) + break + } + b, err := ioutil.ReadFile(s) + if err != nil { + fmt.Printf(">>> Error: %s \n", err) + continue + } else { + h := sendMsg(b) + if (h == common.Hash{}) { + fmt.Printf(">>> Error: message was not sent \n") + } else { + timestamp := time.Now().Unix() + from := crypto.PubkeyToAddress(asymKey.PublicKey) + fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h) + } + } + } +} + func scanLine(prompt string) string { if len(prompt) > 0 { fmt.Print(prompt) @@ -425,7 +466,7 @@ func scanUint(prompt string) uint32 { return uint32(i) } -func sendMsg(payload []byte) { +func sendMsg(payload []byte) common.Hash { params := whisper.MessageParams{ Src: asymKey, Dst: pub, @@ -441,13 +482,16 @@ func sendMsg(payload []byte) { envelope, err := msg.Wrap(¶ms) if err != nil { fmt.Printf("failed to seal message: %v \n", err) - return + return common.Hash{} } err = shh.Send(envelope) if err != nil { fmt.Printf("failed to send message: %v \n", err) + return common.Hash{} } + + return envelope.Hash() } func messageLoop() { @@ -463,7 +507,11 @@ func messageLoop() { case <-ticker.C: messages := f.Retrieve() for _, msg := range messages { - printMessageInfo(msg) + if *fileExMode { + saveMessageInFile(msg) + } else { + printMessageInfo(msg) + } } case <-done: return @@ -487,6 +535,29 @@ func printMessageInfo(msg *whisper.ReceivedMessage) { } } +func saveMessageInFile(msg *whisper.ReceivedMessage) { + timestamp := fmt.Sprintf("%d", msg.Sent) + name := fmt.Sprintf("%x", msg.EnvelopeHash) + + var address common.Address + if msg.Src != nil { + address = crypto.PubkeyToAddress(*msg.Src) + } + + if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { + // message from myself: don't save, only report + fmt.Printf("\n%s <%x>: message received: '%s'\n", timestamp, address, name) + } else { + fullpath := filepath.Join(*argSaveDir, name) + err := ioutil.WriteFile(fullpath, msg.Payload, 0644) + if err != nil { + fmt.Printf("\n%s [%x]: message received but not saved: %s\n", timestamp, address, err) + } else { + fmt.Printf("\n%s [%x]: message received and saved as '%s'\n", timestamp, address, name) + } + } +} + func requestExpiredMessagesLoop() { var key, peerID []byte var timeLow, timeUpp uint32 @@ -502,7 +573,7 @@ func requestExpiredMessagesLoop() { utils.Fatalf("Failed to save symmetric key for mail request: %s", err) } peerID = extractIdFromEnode(*argEnode) - shh.MarkPeerTrusted(peerID) + shh.AllowP2PMessagesFromPeer(peerID) for { timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 1093906a39..f965508df3 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -86,13 +86,19 @@ func (api *PublicWhisperAPI) SetMinimumPoW(val float64) error { return api.whisper.SetMinimumPoW(val) } -// MarkPeerTrusted marks specific peer trusted, which will allow it +// 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 api.whisper.MarkPeerTrusted(peerID) + n, err := discover.ParseNode(enode) + if err != nil { + info := "Failed to parse enode of trusted peer: " + err.Error() + log.Error(info) + return errors.New(info) + } + return api.whisper.AllowP2PMessagesFromPeer(n.ID[:]) } // RequestHistoricMessages requests the peer to deliver the old (expired) messages. diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 4b735cbc6c..58f2bd9852 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -57,7 +57,7 @@ const ( keyIdSize = 32 DefaultMaxMessageLength = 1024 * 1024 - DefaultMinimumPoW = 10.0 // todo: review after testing. + DefaultMinimumPoW = 2.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/whisper.go b/whisper/whisperv5/whisper.go index e4637dc6ad..8771f37780 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -77,8 +77,7 @@ type Whisper struct { // 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), - //identities: make(map[string]*ecdsa.PrivateKey), + privateKeys: make(map[string]*ecdsa.PrivateKey), symKeys: make(map[string][]byte), envelopes: make(map[common.Hash]*Envelope), expirations: make(map[uint32]*set.SetNonTS), @@ -158,7 +157,7 @@ func (w *Whisper) getPeer(peerID []byte) (*Peer, error) { // MarkPeerTrusted marks specific peer trusted, which will allow it // to send historic (expired) messages. -func (w *Whisper) MarkPeerTrusted(peerID []byte) error { +func (w *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error { p, err := w.getPeer(peerID) if err != nil { return err diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index de86ff4ad7..7a7db360e8 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -51,7 +51,7 @@ 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") From 25970afaac7737369d337601e15175f0cbab4585 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 15 Mar 2017 00:08:00 +0100 Subject: [PATCH 10/18] whisper: formatting --- cmd/wnode/main.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 5fb10d52a8..ff388f60f4 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -27,15 +27,13 @@ import ( "encoding/hex" "flag" "fmt" + "io/ioutil" "os" + "path/filepath" "strconv" "strings" "time" - "io/ioutil" - - "path/filepath" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console" From f8815e0f92cb7a6699b037c66f2abe41eed78ece Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 15 Mar 2017 12:24:39 +0100 Subject: [PATCH 11/18] whisper: file exchange introduced in wnode --- cmd/wnode/main.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index ff388f60f4..0d4da7e95f 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -87,6 +87,7 @@ var ( argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level") argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") argWorkTime = flag.Uint("work", 5, "work time in seconds") + argMaxSize = flag.Int("maxsize", whisper.DefaultMaxMessageLength, "max size of message") argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request") @@ -211,6 +212,20 @@ func initialize() { shh = whisper.New() } + if *argPoW != whisper.DefaultMinimumPoW { + err := shh.SetMinimumPoW(*argPoW) + if err != nil { + utils.Fatalf("Failed to set PoW: %s", err) + } + } + + if *argMaxSize != whisper.DefaultMaxMessageLength { + err := shh.SetMaxMessageLength(*argMaxSize) + if err != nil { + utils.Fatalf("Failed to set max message size: %s", err) + } + } + asymKeyID, err = shh.NewKeyPair() if err != nil { utils.Fatalf("Failed to generate a new key pair: %s", err) @@ -505,7 +520,7 @@ func messageLoop() { case <-ticker.C: messages := f.Retrieve() for _, msg := range messages { - if *fileExMode { + if *fileExMode || len(msg.Payload) > 2048 { saveMessageInFile(msg) } else { printMessageInfo(msg) @@ -545,14 +560,16 @@ func saveMessageInFile(msg *whisper.ReceivedMessage) { if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { // message from myself: don't save, only report fmt.Printf("\n%s <%x>: message received: '%s'\n", timestamp, address, name) - } else { + } else if len(*argSaveDir) > 0 { fullpath := filepath.Join(*argSaveDir, name) err := ioutil.WriteFile(fullpath, msg.Payload, 0644) if err != nil { - fmt.Printf("\n%s [%x]: message received but not saved: %s\n", timestamp, address, err) + fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err) } else { - fmt.Printf("\n%s [%x]: message received and saved as '%s'\n", timestamp, address, name) + fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(msg.Payload)) } + } else { + fmt.Printf("\n%s {%x}: big message received (%d bytes), but not saved: %s\n", timestamp, address, len(msg.Payload), name) } } From 6eb6853d9e68a175349a02ce62226d45e874ac8b Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 15 Mar 2017 22:54:02 +0100 Subject: [PATCH 12/18] whisper: bugfix --- whisper/whisperv5/doc.go | 2 +- whisper/whisperv5/envelope.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 58f2bd9852..d60868f670 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -57,7 +57,7 @@ const ( keyIdSize = 32 DefaultMaxMessageLength = 1024 * 1024 - DefaultMinimumPoW = 2.0 // todo: review after testing. + DefaultMinimumPoW = 1.0 // todo: review after testing. padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature 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 5d882d5dc2..94a091b200 100644 --- a/whisper/whisperv5/envelope.go +++ b/whisper/whisperv5/envelope.go @@ -95,6 +95,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) From 74678aacc9a5cf496cbf4f2e8d0537470a85c7a2 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 16 Mar 2017 18:24:49 +0100 Subject: [PATCH 13/18] whisper: API updated + new tests --- whisper/whisperv5/api.go | 31 ++++++++++++++-------- whisper/whisperv5/api_test.go | 48 +++++++++++++++++++++++++++++----- whisper/whisperv5/envelope.go | 3 +-- whisper/whisperv5/peer_test.go | 6 ++--- whisper/whisperv5/whisper.go | 4 +-- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index f965508df3..fb247d29f3 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -198,11 +198,16 @@ func (api *PublicWhisperAPI) HasSymmetricKey(id string) (bool, error) { return res, nil } -func (api *PublicWhisperAPI) GetSymmetricKey(name string) ([]byte, error) { +func (api *PublicWhisperAPI) GetSymmetricKey(name string) (string, error) { if api.whisper == nil { - return nil, whisperOffLineErr + return "", whisperOffLineErr } - return api.whisper.GetSymKey(name) + + b, err := api.whisper.GetSymKey(name) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", b), nil } // DeleteSymKey deletes the key associated with the name string if it exists. @@ -232,14 +237,14 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { err := ValidateKeyID(args.Key) if err != nil { - info := "NewFilter: " + err.Error() + info := "Subscribe: " + err.Error() log.Error(info) return "", errors.New(info) } if len(args.SignedWith) > 0 { if !ValidatePublicKey(filter.Src) { - info := "NewFilter: Invalid 'SignedWith' field" + info := "Subscribe: Invalid 'SignedWith' field" log.Error(info) return "", errors.New(info) } @@ -247,18 +252,18 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { if args.Symmetric { if len(args.Topics) == 0 { - info := "NewFilter: at least one topic must be specified with symmetric encryption" + info := "Subscribe: at least one topic must be specified with symmetric encryption" log.Error(info) return "", errors.New(info) } symKey, err := api.whisper.GetSymKey(args.Key) if err != nil { - info := "NewFilter: invalid key ID" + info := "Subscribe: invalid key ID" log.Error(info) return "", errors.New(info) } if !validateSymmetricKey(symKey) { - info := "NewFilter: retrieved key is invalid" + info := "Subscribe: retrieved key is invalid" log.Error(info) return "", errors.New(info) } @@ -268,12 +273,12 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { } else { filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key) if err != nil { - info := "NewFilter: invalid key ID" + info := "Subscribe: invalid key ID" log.Error(info) return "", errors.New(info) } if filter.KeyAsym == nil { - info := "NewFilter: non-existent identity provided" + info := "Subscribe: non-existent identity provided" log.Error(info) return "", errors.New(info) } @@ -288,7 +293,7 @@ func (api *PublicWhisperAPI) Unsubscribe(id string) { } // GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval. -func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage { +func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*WhisperMessage { f := api.whisper.GetFilter(filterId) if f != nil { newMail := f.Retrieve() @@ -409,6 +414,10 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { return errors.New(info) } return api.whisper.SendP2PMessage(n.ID[:], envelope) + } else if args.PowTarget < api.whisper.minPoW { + info := "Post: target PoW is less than minimum PoW, the message can not be sent" + log.Error(info) + return errors.New(info) } return api.whisper.Send(envelope) diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index e9df3f9a53..5c15b7ce11 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -17,7 +17,6 @@ package whisperv5 import ( - "bytes" "encoding/json" "testing" "time" @@ -43,7 +42,7 @@ 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") } @@ -148,7 +147,7 @@ func TestBasic(t *testing.T) { t.Fatalf("failed GetSymKey(id2): %s.", err) } - if !bytes.Equal(k1, k2) { + if k1 != k2 { t.Fatalf("installed keys are not equal") } @@ -171,7 +170,7 @@ func TestBasic(t *testing.T) { func TestUnmarshalFilterArgs(t *testing.T) { s := []byte(`{ - "type":"asym", + "type":"sym", "key":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", "signedWith":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "minPoW":2.34, @@ -185,7 +184,7 @@ func TestUnmarshalFilterArgs(t *testing.T) { t.Fatalf("failed UnmarshalJSON: %s.", err) } - if f.Symmetric { + if !f.Symmetric { t.Fatalf("wrong type.") } if f.Key != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { @@ -282,7 +281,7 @@ func waitForMessages(api *PublicWhisperAPI, id string, target int) []*WhisperMes // timeout: 2 seconds result := make([]*WhisperMessage, 0, target) for i := 0; i < 100; i++ { - mail := api.GetFilterChanges(id) + mail := api.GetSubscriptionMessages(id) if len(mail) > 0 { for _, m := range mail { result = append(result, m) @@ -592,3 +591,40 @@ 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) + } + + b := common.FromHex(s) + k2, err := api.AddSymmetricKeyDirect(b) + if err != nil { + t.Fatalf("failed to add sym key: %s.", err) + } + + s2, err := api.GetSymmetricKey(k2) + if err != nil { + t.Fatalf("failed to get sym key: %s.", err) + } + + if s != "448652d595bd6ec00b2a9ea220ad6c26592d9bf4cf79023d3c1b30cb681e6e07" { + t.Fatalf("wrong key from password") + } + + if s != s2 { + t.Fatalf("wrong key") + } +} diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go index 94a091b200..ef2c54a04d 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" @@ -121,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/peer_test.go b/whisper/whisperv5/peer_test.go index 9df2a060e8..7d8fc1b696 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -257,7 +257,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 +267,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 8771f37780..4e4edab0c9 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -348,7 +348,7 @@ func (w *Whisper) GetSymKey(id string) ([]byte, error) { if w.symKeys[id] != nil { return w.symKeys[id], nil } - return nil, fmt.Errorf("non-existent ID") + return nil, fmt.Errorf("non-existent key ID") } // Watch installs a new message handler to run in case a matching packet arrives @@ -383,7 +383,7 @@ func (w *Whisper) Send(envelope *Envelope) error { // 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(fmt.Sprintf("Whisper v%d started", ProtocolVersion)) go w.update() numCPU := runtime.NumCPU() From 8a1f1c557ed36f89c0e620683a9632f497138ead Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 16 Mar 2017 20:55:53 +0100 Subject: [PATCH 14/18] whisper: statistics updated --- whisper/whisperv5/whisper.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 4e4edab0c9..c5f06f16eb 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -647,14 +647,13 @@ func (w *Whisper) expire() { 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() + delete(w.envelopes, v.(common.Hash)) + w.stats.messagesCleared++ w.stats.memoryCleared += sz w.stats.memoryUsed -= sz - delete(w.envelopes, v.(common.Hash)) return true }) w.expirations[expiry].Clear() @@ -664,10 +663,10 @@ func (w *Whisper) expire() { } func (w *Whisper) Stats() string { - result := fmt.Sprintf("Memory usage: %d bytes.\nAverage messages cleared per expiry cycle: %d.", - w.stats.memoryUsed, w.stats.totalMessagesCleared/w.stats.cycles) + 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("\nLatest expiry cycle cleared %d messages (%d bytes).", + result += fmt.Sprintf(" Latest expiry cycle cleared %d messages (%d bytes).", w.stats.messagesCleared, w.stats.memoryCleared) } return result From 812a2d5a05e5ff28a8b9fa916b1104e07549c72b Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 18 Mar 2017 14:56:15 +0100 Subject: [PATCH 15/18] whisper: wnode server updated --- cmd/wnode/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 0d4da7e95f..163df52d1c 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -257,7 +257,8 @@ func initialize() { Config: p2p.Config{ PrivateKey: nodeid, MaxPeers: maxPeers, - Name: common.MakeName("whisper-go", "5.0"), + Discovery: true, + Name: common.MakeName("wnode", "5.0"), Protocols: shh.Protocols(), ListenAddr: *argIP, NAT: nat.Any(), From 58c8121a227d4f4a0cb148b589dcadcc6beaf807 Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 13 Mar 2017 20:56:22 +0100 Subject: [PATCH 16/18] whisper: variable topic size allowed for a filter --- cmd/wnode/main.go | 8 ++++---- whisper/whisperv5/api.go | 12 +++++++----- whisper/whisperv5/api_test.go | 20 +++++++++++++------- whisper/whisperv5/filter.go | 16 +++++++++------- whisper/whisperv5/filter_test.go | 13 +++++++------ whisper/whisperv5/peer_test.go | 4 +++- whisper/whisperv5/whisper_test.go | 2 +- 7 files changed, 44 insertions(+), 31 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 163df52d1c..0358ccc057 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -65,7 +65,7 @@ var ( pub *ecdsa.PublicKey asymKey *ecdsa.PrivateKey nodeid *ecdsa.PrivateKey - topic whisper.TopicType + topic []byte asymKeyID string filterID string symPass string @@ -129,7 +129,7 @@ func processArgs() { if err != nil { utils.Fatalf("Failed to parse the topic: %s", err) } - topic = whisper.BytesToTopic(x) + topic = x } if *asymmetricMode && len(*argPub) > 0 { @@ -355,7 +355,7 @@ func configureNode() { filter := whisper.Filter{ KeySym: symKey, KeyAsym: asymKey, - Topics: []whisper.TopicType{topic}, + Topics: [][]byte{topic}, AllowP2P: p2pAccept, } filterID, err = shh.Watch(&filter) @@ -486,7 +486,7 @@ func sendMsg(payload []byte) common.Hash { Dst: pub, KeySym: symKey, Payload: payload, - Topic: topic, + Topic: whisper.BytesToTopic(topic), TTL: uint32(*argTTL), PoW: *argPoW, WorkTime: uint32(*argWorkTime), diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index fb247d29f3..349ef9b6ac 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -233,7 +233,9 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { AllowP2P: args.AllowP2P, } - filter.Topics = append(filter.Topics, args.Topics...) + for _, bt := range args.Topics { + filter.Topics = append(filter.Topics, bt) + } err := ValidateKeyID(args.Key) if err != nil { @@ -441,7 +443,7 @@ type WhisperFilterArgs struct { Key string SignedWith string MinPoW float64 - Topics []TopicType + Topics [][]byte AllowP2P bool } @@ -487,13 +489,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 } diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index 5c15b7ce11..e6ac1468bb 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -204,22 +204,22 @@ func TestUnmarshalFilterArgs(t *testing.T) { } 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]) } } @@ -347,7 +347,9 @@ func TestIntegrationAsym(t *testing.T) { f.Symmetric = false f.Key = key f.SignedWith = sigPubKey - f.Topics = topics[:] + f.Topics = make([][]byte, 2) + f.Topics[0] = topics[0][:] + f.Topics[1] = topics[1][:] f.MinPoW = DefaultMinimumPoW / 2 f.AllowP2P = true @@ -442,7 +444,9 @@ func TestIntegrationSym(t *testing.T) { var f WhisperFilterArgs f.Symmetric = true f.Key = symKeyID - f.Topics = topics[:] + f.Topics = make([][]byte, 2) + f.Topics[0] = topics[0][:] + f.Topics[1] = topics[1][:] f.MinPoW = 0.324 f.SignedWith = sigPubKey f.AllowP2P = false @@ -538,7 +542,9 @@ func TestIntegrationSymWithFilter(t *testing.T) { var f WhisperFilterArgs f.Symmetric = true f.Key = symKeyID - f.Topics = topics[:] + f.Topics = make([][]byte, 2) + f.Topics[0] = topics[0][:] + f.Topics[1] = topics[1][:] f.MinPoW = 0.324 f.SignedWith = sigPubKey f.AllowP2P = false diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index f18b4ee292..675340d03c 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -29,13 +29,13 @@ 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 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 - mutex sync.RWMutex + Messages map[common.Hash]*ReceivedMessage + mutex sync.RWMutex } type Filters struct { @@ -201,12 +201,14 @@ func (f *Filter) MatchTopic(topic TopicType) bool { return true } - for _, t := range f.Topics { - if t == topic { - return true + for _, bt := range f.Topics { + for j, b := range bt { + if topic[j] != b { + return false + } } } - return false + return true } func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go index bdd2e6d6c6..212b689d98 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) randomize(f.Topics[i][:]) f.Topics[i][0] = 0x01 } @@ -194,8 +195,8 @@ func TestMatchEnvelope(t *testing.T) { // encrypt symmetrically i := rand.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 @@ -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 { diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index 7d8fc1b696..47c9942cb6 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -118,7 +118,9 @@ func initialize(t *testing.T) { node.shh.Start(nil) topics := make([]TopicType, 0) topics = append(topics, sharedTopic) - f := Filter{KeySym: sharedKey, Topics: topics} + f := Filter{KeySym: sharedKey} + f.Topics = make([][]byte, 1) + f.Topics[0] = topics[0][:] node.filerId, err = node.shh.Watch(&f) if err != nil { t.Fatalf("failed to install the filter: %s.", err) diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index 7a7db360e8..0f7ecbc947 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -510,7 +510,7 @@ func TestCustomization(t *testing.T) { } params.KeySym = f.KeySym - params.Topic = f.Topics[2] + params.Topic = BytesToTopic(f.Topics[2]) params.PoW = smallPoW params.TTL = 3600 * 24 // one day msg := NewSentMessage(params) From 51cfbba9e024283d4400b2a36d44e4619b2a0f18 Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 20 Mar 2017 17:34:58 +0100 Subject: [PATCH 17/18] whisper: allowed filtering for variable topic size --- whisper/whisperv5/api_test.go | 1 + whisper/whisperv5/filter.go | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index e6ac1468bb..cc93261636 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -17,6 +17,7 @@ package whisperv5 import ( + "bytes" "encoding/json" "testing" "time" diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index 675340d03c..8230a26ff0 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -89,12 +89,12 @@ func (fs *Filters) Get(id string) *Filter { } func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { - var j int var msg *ReceivedMessage fs.mutex.RLock() defer fs.mutex.RUnlock() + j := -1 for _, watcher := range fs.watchers { j++ if p2pMessage && !watcher.AllowP2P { @@ -118,6 +118,7 @@ func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { } if match && msg != nil { + log.Trace(fmt.Sprintf("message decrypted [%x]", env.Hash())) watcher.Trigger(msg) } } @@ -202,10 +203,21 @@ func (f *Filter) MatchTopic(topic TopicType) bool { } for _, bt := range f.Topics { - for j, b := range bt { - if topic[j] != b { - return false - } + if MatchSingleTopic(topic, bt) { + return true + } + } + return false +} + +func MatchSingleTopic(topic TopicType, bt []byte) bool { + if len(bt) > 4 { + bt = bt[0:4] + } + + for j, b := range bt { + if topic[j] != b { + return false } } return true From 44fc7bb634ea4a59a726f1b806597c03048a25ef Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 20 Mar 2017 20:13:24 +0100 Subject: [PATCH 18/18] whisper: tests added --- whisper/whisperv5/api.go | 7 ++++- whisper/whisperv5/api_test.go | 44 ++++++++++++++++++++++++++++++++ whisper/whisperv5/filter_test.go | 37 +++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 349ef9b6ac..6bc065b192 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -233,7 +233,12 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { AllowP2P: args.AllowP2P, } - for _, bt := range args.Topics { + for i, bt := range args.Topics { + if len(bt) == 0 || len(bt) > 4 { + info := fmt.Sprintf("Subscribe: topic %d has wrong size: %d", i, len(bt)) + log.Error(info) + return "", errors.New(info) + } filter.Topics = append(filter.Topics, bt) } diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index cc93261636..e1c38f2faf 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -635,3 +635,47 @@ func TestKey(t *testing.T) { 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/filter_test.go b/whisper/whisperv5/filter_test.go index 212b689d98..aa8bb372b5 100644 --- a/whisper/whisperv5/filter_test.go +++ b/whisper/whisperv5/filter_test.go @@ -669,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[0:i+1]) + + f.Topics[4] = arr + match = f.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i) + } + + f.Topics[4][i]++ + match = f.MatchEnvelope(env) + if match { + t.Fatalf("MatchEnvelope symmetric with seed %d, step %d: false positive.", seed, i) + } + } +}