diff options
author | gluk256 <gluk256@users.noreply.github.com> | 2017-02-23 16:41:47 +0800 |
---|---|---|
committer | Jeffrey Wilcke <jeffrey@ethereum.org> | 2017-02-23 16:41:47 +0800 |
commit | 29fac7de448c85049a97cbec3dc0819122bd2cb0 (patch) | |
tree | eaa56d55f2ff5c15fca84a6d408d6aaff7d78404 /whisper/whisperv5 | |
parent | 555273495b413069e9422b04aa46251146c752b2 (diff) | |
download | dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.tar dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.tar.gz dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.tar.bz2 dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.tar.lz dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.tar.xz dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.tar.zst dexon-29fac7de448c85049a97cbec3dc0819122bd2cb0.zip |
Whisper API fixed (#3687)
* whisper: wnode updated for tests with geth
* whisper: updated processing of incoming messages
* whisper: symmetric encryption updated
* whisper: filter id type changed to enhance security
* whisper: allow filter without topic for asymmetric encryption
* whisper: POW updated
* whisper: logging updated
* whisper: spellchecker update
* whisper: error handling changed
* whisper: JSON field names fixed
Diffstat (limited to 'whisper/whisperv5')
-rw-r--r-- | whisper/whisperv5/api.go | 73 | ||||
-rw-r--r-- | whisper/whisperv5/api_test.go | 14 | ||||
-rw-r--r-- | whisper/whisperv5/benchmarks_test.go | 31 | ||||
-rw-r--r-- | whisper/whisperv5/doc.go | 4 | ||||
-rw-r--r-- | whisper/whisperv5/envelope.go | 10 | ||||
-rw-r--r-- | whisper/whisperv5/filter.go | 45 | ||||
-rw-r--r-- | whisper/whisperv5/filter_test.go | 37 | ||||
-rw-r--r-- | whisper/whisperv5/peer_test.go | 9 | ||||
-rw-r--r-- | whisper/whisperv5/whisper.go | 8 | ||||
-rw-r--r-- | whisper/whisperv5/whisper_test.go | 4 |
10 files changed, 157 insertions, 78 deletions
diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index ce41624df..4d33d2321 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -123,7 +123,7 @@ func (api *PublicWhisperAPI) GenerateSymKey(name string) error { } // AddSymKey stores the key under the 'name' id. -func (api *PublicWhisperAPI) AddSymKey(name string, key []byte) error { +func (api *PublicWhisperAPI) AddSymKey(name string, key hexutil.Bytes) error { if api.whisper == nil { return whisperOffLineErr } @@ -151,9 +151,9 @@ func (api *PublicWhisperAPI) DeleteSymKey(name string) error { // 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) (uint32, error) { +func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (string, error) { if api.whisper == nil { - return 0, whisperOffLineErr + return "", whisperOffLineErr } filter := Filter{ @@ -168,28 +168,28 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (uint32, error) { } filter.Topics = append(filter.Topics, args.Topics...) - if len(args.Topics) == 0 { + if len(args.Topics) == 0 && len(args.KeyName) != 0 { info := "NewFilter: at least one topic must be specified" glog.V(logger.Error).Infof(info) - return 0, errors.New(info) + return "", errors.New(info) } if len(args.KeyName) != 0 && len(filter.KeySym) == 0 { info := "NewFilter: key was not found by name: " + args.KeyName glog.V(logger.Error).Infof(info) - return 0, errors.New(info) + return "", errors.New(info) } if len(args.To) == 0 && len(filter.KeySym) == 0 { info := "NewFilter: filter must contain either symmetric or asymmetric key" glog.V(logger.Error).Infof(info) - return 0, errors.New(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" glog.V(logger.Error).Infof(info) - return 0, errors.New(info) + return "", errors.New(info) } if len(args.To) > 0 { @@ -197,13 +197,13 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (uint32, error) { if !ValidatePublicKey(dst) { info := "NewFilter: Invalid 'To' address" glog.V(logger.Error).Infof(info) - return 0, errors.New(info) + return "", errors.New(info) } filter.KeyAsym = api.whisper.GetIdentity(string(args.To)) if filter.KeyAsym == nil { info := "NewFilter: non-existent identity provided" glog.V(logger.Error).Infof(info) - return 0, errors.New(info) + return "", errors.New(info) } } @@ -211,21 +211,20 @@ func (api *PublicWhisperAPI) NewFilter(args WhisperFilterArgs) (uint32, error) { if !ValidatePublicKey(filter.Src) { info := "NewFilter: Invalid 'From' address" glog.V(logger.Error).Infof(info) - return 0, errors.New(info) + return "", errors.New(info) } } - id := api.whisper.Watch(&filter) - return id, nil + return api.whisper.Watch(&filter) } // UninstallFilter disables and removes an existing filter. -func (api *PublicWhisperAPI) UninstallFilter(filterId uint32) { +func (api *PublicWhisperAPI) UninstallFilter(filterId string) { api.whisper.Unwatch(filterId) } // GetFilterChanges retrieves all the new messages matched by a filter since the last retrieval. -func (api *PublicWhisperAPI) GetFilterChanges(filterId uint32) []WhisperMessage { +func (api *PublicWhisperAPI) GetFilterChanges(filterId string) []*WhisperMessage { f := api.whisper.GetFilter(filterId) if f != nil { newMail := f.Retrieve() @@ -235,14 +234,14 @@ func (api *PublicWhisperAPI) GetFilterChanges(filterId uint32) []WhisperMessage } // GetMessages retrieves all the known messages that match a specific filter. -func (api *PublicWhisperAPI) GetMessages(filterId uint32) []WhisperMessage { +func (api *PublicWhisperAPI) GetMessages(filterId string) []*WhisperMessage { all := api.whisper.Messages(filterId) return toWhisperMessages(all) } // toWhisperMessages converts a Whisper message to a RPC whisper message. -func toWhisperMessages(messages []*ReceivedMessage) []WhisperMessage { - msgs := make([]WhisperMessage, len(messages)) +func toWhisperMessages(messages []*ReceivedMessage) []*WhisperMessage { + msgs := make([]*WhisperMessage, len(messages)) for i, msg := range messages { msgs[i] = NewWhisperMessage(msg) } @@ -282,8 +281,8 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { } filter := api.whisper.GetFilter(args.FilterID) - if filter == nil && args.FilterID > 0 { - info := fmt.Sprintf("Post: wrong filter id %d", args.FilterID) + if filter == nil && len(args.FilterID) > 0 { + info := fmt.Sprintf("Post: wrong filter id %s", args.FilterID) glog.V(logger.Error).Infof(info) return errors.New(info) } @@ -299,7 +298,7 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { if (params.Topic == TopicType{}) { sz := len(filter.Topics) if sz < 1 { - info := fmt.Sprintf("Post: no topics in filter # %d", args.FilterID) + info := fmt.Sprintf("Post: no topics in filter # %s", args.FilterID) glog.V(logger.Error).Infof(info) return errors.New(info) } else if sz == 1 { @@ -374,17 +373,17 @@ type PostArgs struct { Payload hexutil.Bytes `json:"payload"` WorkTime uint32 `json:"worktime"` PoW float64 `json:"pow"` - FilterID uint32 `json:"filterID"` + FilterID string `json:"filterID"` PeerID hexutil.Bytes `json:"peerID"` } type WhisperFilterArgs struct { - To string - From string - KeyName string - PoW float64 - Topics []TopicType - AcceptP2P bool + To string `json:"to"` + From string `json:"from"` + KeyName string `json:"keyname"` + PoW float64 `json:"pow"` + Topics []TopicType `json:"topics"` + AcceptP2P bool `json:"p2p"` } // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a @@ -397,7 +396,7 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { KeyName string `json:"keyname"` PoW float64 `json:"pow"` Topics []interface{} `json:"topics"` - AcceptP2P bool `json:"acceptP2P"` + AcceptP2P bool `json:"p2p"` } if err := json.Unmarshal(b, &obj); err != nil { return err @@ -438,6 +437,7 @@ 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"` @@ -449,15 +449,22 @@ type WhisperMessage struct { } // NewWhisperMessage converts an internal message into an API version. -func NewWhisperMessage(message *ReceivedMessage) WhisperMessage { - return WhisperMessage{ +func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage { + msg := WhisperMessage{ + Topic: common.ToHex(message.Topic[:]), Payload: common.ToHex(message.Payload), Padding: common.ToHex(message.Padding), - From: common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())), - To: common.ToHex(crypto.FromECDSAPub(message.Dst)), Sent: message.Sent, TTL: message.TTL, PoW: message.PoW, Hash: common.ToHex(message.EnvelopeHash.Bytes()), } + + if message.Dst != nil { + msg.To = common.ToHex(crypto.FromECDSAPub(message.Dst)) + } + if isMessageSigned(message.Raw[0]) { + msg.From = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())) + } + return &msg } diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index f7deab39c..ea0a2c40b 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -42,7 +42,7 @@ func TestBasic(t *testing.T) { t.Fatalf("wrong version: %d.", ver) } - mail := api.GetFilterChanges(1) + mail := api.GetFilterChanges("non-existent-id") if len(mail) != 0 { t.Fatalf("failed GetFilterChanges: premature result") } @@ -152,7 +152,7 @@ func TestUnmarshalFilterArgs(t *testing.T) { "keyname":"testname", "pow":2.34, "topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"], - "acceptP2P":true + "p2p":true }`) var f WhisperFilterArgs @@ -212,8 +212,8 @@ func TestUnmarshalPostArgs(t *testing.T) { "payload":"0x7061796C6F61642073686F756C642062652070736575646F72616E646F6D", "worktime":777, "pow":3.1416, - "filterID":64, - "peerID":"0xf26e7779" + "filterid":"test-filter-id", + "peerid":"0xf26e7779" }`) var a PostArgs @@ -249,15 +249,15 @@ func TestUnmarshalPostArgs(t *testing.T) { if a.PoW != 3.1416 { t.Fatalf("wrong pow: %f.", a.PoW) } - if a.FilterID != 64 { - t.Fatalf("wrong FilterID: %d.", a.FilterID) + 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) } } -func waitForMessage(api *PublicWhisperAPI, id uint32, target int) bool { +func waitForMessage(api *PublicWhisperAPI, id string, target int) bool { for i := 0; i < 64; i++ { all := api.GetMessages(id) if len(all) >= target { diff --git a/whisper/whisperv5/benchmarks_test.go b/whisper/whisperv5/benchmarks_test.go index f2eef3c47..417b2881b 100644 --- a/whisper/whisperv5/benchmarks_test.go +++ b/whisper/whisperv5/benchmarks_test.go @@ -34,7 +34,6 @@ func BenchmarkDeriveOneTimeKey(b *testing.B) { } } -//func TestEncryptionSym(b *testing.T) { func BenchmarkEncryptionSym(b *testing.B) { InitSingleTest() @@ -181,3 +180,33 @@ func BenchmarkDecryptionAsymInvalid(b *testing.B) { } } } + +func increment(x []byte) { + for i := 0; i < len(x); i++ { + x[i]++ + if x[i] != 0 { + break + } + } +} + +func BenchmarkPoW(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + params.Payload = make([]byte, 32) + params.PoW = 10.0 + params.TTL = 1 + + for i := 0; i < b.N; i++ { + increment(params.Payload) + msg := NewSentMessage(params) + _, err := msg.Wrap(params) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + } +} diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index b82a82468..70c7008a7 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -55,8 +55,8 @@ const ( saltLength = 12 AESNonceMaxLength = 12 - MaxMessageLength = 0xFFFF // todo: remove this restriction after testing. this should be regulated by PoW. - MinimumPoW = 1.0 // todo: review after testing. + MaxMessageLength = 0x0FFFFF // todo: remove this restriction after testing. this should be regulated by PoW. + MinimumPoW = 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/envelope.go b/whisper/whisperv5/envelope.go index 1b976705d..8812ae207 100644 --- a/whisper/whisperv5/envelope.go +++ b/whisper/whisperv5/envelope.go @@ -116,12 +116,16 @@ func (e *Envelope) Seal(options *MessageParams) error { } if target > 0 && bestBit < target { - return errors.New("Failed to reach the PoW target") + return errors.New("Failed to reach the PoW target, insufficient work time") } return nil } +func (e *Envelope) size() int { + return len(e.Data) + len(e.Version) + len(e.AESNonce) + len(e.Salt) + 20 +} + func (e *Envelope) PoW() float64 { if e.pow == 0 { e.calculatePoW(0) @@ -137,14 +141,14 @@ func (e *Envelope) calculatePoW(diff uint32) { h = crypto.Keccak256(buf) firstBit := common.FirstBitSet(common.BigD(h)) x := math.Pow(2, float64(firstBit)) - x /= float64(len(e.Data)) // we only count e.Data, other variable-sized members are checked in Whisper.add() + x /= float64(e.size()) x /= float64(e.TTL + diff) e.pow = x } func (e *Envelope) powToFirstBit(pow float64) int { x := pow - x *= float64(len(e.Data)) + x *= float64(e.size()) x *= float64(e.TTL) bits := math.Log2(x) bits = math.Ceil(bits) diff --git a/whisper/whisperv5/filter.go b/whisper/whisperv5/filter.go index a386aa164..832ebe3f6 100644 --- a/whisper/whisperv5/filter.go +++ b/whisper/whisperv5/filter.go @@ -18,6 +18,8 @@ package whisperv5 import ( "crypto/ecdsa" + crand "crypto/rand" + "fmt" "sync" "github.com/ethereum/go-ethereum/common" @@ -39,20 +41,41 @@ type Filter struct { } type Filters struct { - id uint32 // can contain any value except zero - watchers map[uint32]*Filter + watchers map[string]*Filter whisper *Whisper mutex sync.RWMutex } func NewFilters(w *Whisper) *Filters { return &Filters{ - watchers: make(map[uint32]*Filter), + watchers: make(map[string]*Filter), whisper: w, } } -func (fs *Filters) Install(watcher *Filter) uint32 { +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) } @@ -60,21 +83,23 @@ func (fs *Filters) Install(watcher *Filter) uint32 { fs.mutex.Lock() defer fs.mutex.Unlock() - fs.id++ - fs.watchers[fs.id] = watcher - return fs.id + id, err := fs.generateRandomID() + if err == nil { + fs.watchers[id] = watcher + } + return id, err } -func (fs *Filters) Uninstall(id uint32) { +func (fs *Filters) Uninstall(id string) { fs.mutex.Lock() defer fs.mutex.Unlock() delete(fs.watchers, id) } -func (fs *Filters) Get(i uint32) *Filter { +func (fs *Filters) Get(id string) *Filter { fs.mutex.RLock() defer fs.mutex.RUnlock() - return fs.watchers[i] + return fs.watchers[id] } func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go index 5bf607ccc..d69fb40db 100644 --- a/whisper/whisperv5/filter_test.go +++ b/whisper/whisperv5/filter_test.go @@ -43,7 +43,7 @@ func InitDebugTest(i int64) { type FilterTestCase struct { f *Filter - id uint32 + id string alive bool msgCnt int } @@ -100,14 +100,17 @@ func TestInstallFilters(t *testing.T) { filters := NewFilters(w) tst := generateTestCases(t, SizeTestFilters) - var j uint32 + var err error + var j string for i := 0; i < SizeTestFilters; i++ { - j = filters.Install(tst[i].f) + j, err = filters.Install(tst[i].f) + if err != nil { + t.Fatalf("seed %d: failed to install filter: %s", seed, err) + } tst[i].id = j - } - - if j < SizeTestFilters-1 { - t.Fatalf("seed %d: wrong index %d", seed, j) + if len(j) != 40 { + t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j)) + } } for _, testCase := range tst { @@ -519,17 +522,25 @@ func TestWatchers(t *testing.T) { var i int var j uint32 var e *Envelope + var x, firstID string + var err error w := New() filters := NewFilters(w) tst := generateTestCases(t, NumFilters) for i = 0; i < NumFilters; i++ { tst[i].f.Src = nil - j = filters.Install(tst[i].f) - tst[i].id = j + x, err = filters.Install(tst[i].f) + if err != nil { + t.Fatalf("failed to install filter with seed %d: %s.", seed, err) + } + tst[i].id = x + if len(firstID) == 0 { + firstID = x + } } - last := j + lastID := x var envelopes [NumMessages]*Envelope for i = 0; i < NumMessages; i++ { @@ -571,9 +582,9 @@ func TestWatchers(t *testing.T) { // another round with a cloned filter clone := cloneFilter(tst[0].f) - filters.Uninstall(last) + filters.Uninstall(lastID) total = 0 - last = NumFilters - 1 + last := NumFilters - 1 tst[last].f = clone filters.Install(clone) for i = 0; i < NumFilters; i++ { @@ -640,7 +651,7 @@ func TestWatchers(t *testing.T) { t.Fatalf("failed with seed %d: total: got %d, want 0.", seed, total) } - f := filters.Get(1) + f := filters.Get(firstID) if f == nil { t.Fatalf("failed to get the filter with seed %d.", seed) } diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index cc98ba2d6..cce2c92ba 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -79,7 +79,7 @@ type TestNode struct { shh *Whisper id *ecdsa.PrivateKey server *p2p.Server - filerId uint32 + filerId string } var result TestData @@ -122,7 +122,10 @@ func initialize(t *testing.T) { topics := make([]TopicType, 0) topics = append(topics, sharedTopic) f := Filter{KeySym: sharedKey, Topics: topics} - node.filerId = node.shh.Watch(&f) + node.filerId, err = node.shh.Watch(&f) + if err != nil { + t.Fatalf("failed to install the filter: %s.", err) + } node.id, err = crypto.HexToECDSA(keys[i]) if err != nil { t.Fatalf("failed convert the key: %s.", keys[i]) @@ -187,7 +190,7 @@ func checkPropagation(t *testing.T) { for i := 0; i < NumNodes; i++ { f := nodes[i].shh.GetFilter(nodes[i].filerId) if f == nil { - t.Fatalf("failed to get filterId %d from node %d.", nodes[i].filerId, i) + t.Fatalf("failed to get filterId %s from node %d.", nodes[i].filerId, i) } mail := f.Retrieve() diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index a027fd84b..2a6ff5f40 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -272,16 +272,16 @@ func (w *Whisper) GetSymKey(name string) []byte { // Watch installs a new message handler to run in case a matching packet arrives // from the whisper network. -func (w *Whisper) Watch(f *Filter) uint32 { +func (w *Whisper) Watch(f *Filter) (string, error) { return w.filters.Install(f) } -func (w *Whisper) GetFilter(id uint32) *Filter { +func (w *Whisper) GetFilter(id string) *Filter { return w.filters.Get(id) } // Unwatch removes an installed message handler. -func (w *Whisper) Unwatch(id uint32) { +func (w *Whisper) Unwatch(id string) { w.filters.Uninstall(id) } @@ -575,7 +575,7 @@ func (w *Whisper) Envelopes() []*Envelope { } // Messages retrieves all the decrypted messages matching a filter id. -func (w *Whisper) Messages(id uint32) []*ReceivedMessage { +func (w *Whisper) Messages(id string) []*ReceivedMessage { result := make([]*ReceivedMessage, 0) w.poolMu.RLock() defer w.poolMu.RUnlock() diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index c2ae35a3e..312dacfc4 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -44,7 +44,7 @@ func TestWhisperBasic(t *testing.T) { if uint64(w.Version()) != ProtocolVersion { t.Fatalf("failed whisper Version: %v.", shh.Version) } - if w.GetFilter(0) != nil { + if w.GetFilter("non-existent") != nil { t.Fatalf("failed GetFilter.") } @@ -69,7 +69,7 @@ func TestWhisperBasic(t *testing.T) { if len(mail) != 0 { t.Fatalf("failed w.Envelopes().") } - m := w.Messages(0) + m := w.Messages("non-existent") if len(m) != 0 { t.Fatalf("failed w.Messages.") } |