367c329b88
* whisper: fixes warnings from the code linter * whisper: more non-API-breaking changes The remaining lint errors are because of auto-generated files and one is because an exported function has a non- exported return type. Changing this would break the API, and will be part of another commit for easier reversal. * whisper: un-export NewSentMessage to please the linter This is an API change, which is why it's in its own commit. This change was initiated after the linter complained that the returned type wasn't exported. I chose to un-export the function instead of exporting the type, because that type is an implementation detail that I would like to change in the near future to make the code more readable and with an increased coverage. * whisper: update gencodec output after upgrading it to new lint standards
264 lines
7.0 KiB
Go
264 lines
7.0 KiB
Go
// Copyright 2016 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package whisperv6
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
)
|
|
|
|
// Filter represents a Whisper message filter
|
|
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 [][]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
|
|
}
|
|
|
|
// Filters represents a collection of filters
|
|
type Filters struct {
|
|
watchers map[string]*Filter
|
|
whisper *Whisper
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewFilters returns a newly created filter collection
|
|
func NewFilters(w *Whisper) *Filters {
|
|
return &Filters{
|
|
watchers: make(map[string]*Filter),
|
|
whisper: w,
|
|
}
|
|
}
|
|
|
|
// Install will add a new filter to the filter collection
|
|
func (fs *Filters) Install(watcher *Filter) (string, error) {
|
|
if watcher.KeySym != nil && watcher.KeyAsym != nil {
|
|
return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
|
|
}
|
|
|
|
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()
|
|
|
|
if fs.watchers[id] != nil {
|
|
return "", fmt.Errorf("failed to generate unique ID")
|
|
}
|
|
|
|
if watcher.expectsSymmetricEncryption() {
|
|
watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
|
|
}
|
|
|
|
fs.watchers[id] = watcher
|
|
return id, err
|
|
}
|
|
|
|
// Uninstall will remove a filter whose id has been specified from
|
|
// the filter collection
|
|
func (fs *Filters) Uninstall(id string) bool {
|
|
fs.mutex.Lock()
|
|
defer fs.mutex.Unlock()
|
|
if fs.watchers[id] != nil {
|
|
delete(fs.watchers, id)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Get returns a filter from the collection with a specific ID
|
|
func (fs *Filters) Get(id string) *Filter {
|
|
fs.mutex.RLock()
|
|
defer fs.mutex.RUnlock()
|
|
return fs.watchers[id]
|
|
}
|
|
|
|
// NotifyWatchers notifies any filter that has declared interest
|
|
// for the envelope's topic.
|
|
func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
|
|
var msg *ReceivedMessage
|
|
|
|
fs.mutex.RLock()
|
|
defer fs.mutex.RUnlock()
|
|
|
|
i := -1 // only used for logging info
|
|
for _, watcher := range fs.watchers {
|
|
i++
|
|
if p2pMessage && !watcher.AllowP2P {
|
|
log.Trace(fmt.Sprintf("msg [%x], filter [%d]: p2p messages are not allowed", env.Hash(), i))
|
|
continue
|
|
}
|
|
|
|
var match bool
|
|
if msg != nil {
|
|
match = watcher.MatchMessage(msg)
|
|
} else {
|
|
match = watcher.MatchEnvelope(env)
|
|
if match {
|
|
msg = env.Open(watcher)
|
|
if msg == nil {
|
|
log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", i)
|
|
}
|
|
} else {
|
|
log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", i)
|
|
}
|
|
}
|
|
|
|
if match && msg != nil {
|
|
log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
|
|
if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
|
|
watcher.Trigger(msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *Filter) processEnvelope(env *Envelope) *ReceivedMessage {
|
|
if f.MatchEnvelope(env) {
|
|
msg := env.Open(f)
|
|
if msg != nil {
|
|
return msg
|
|
}
|
|
|
|
log.Trace("processing envelope: failed to open", "hash", env.Hash().Hex())
|
|
} else {
|
|
log.Trace("processing envelope: does not match", "hash", env.Hash().Hex())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *Filter) expectsAsymmetricEncryption() bool {
|
|
return f.KeyAsym != nil
|
|
}
|
|
|
|
func (f *Filter) expectsSymmetricEncryption() bool {
|
|
return f.KeySym != nil
|
|
}
|
|
|
|
// Trigger adds a yet-unknown message to the filter's list of
|
|
// received messages.
|
|
func (f *Filter) Trigger(msg *ReceivedMessage) {
|
|
f.mutex.Lock()
|
|
defer f.mutex.Unlock()
|
|
|
|
if _, exist := f.Messages[msg.EnvelopeHash]; !exist {
|
|
f.Messages[msg.EnvelopeHash] = msg
|
|
}
|
|
}
|
|
|
|
// Retrieve will return the list of all received messages associated
|
|
// to a filter.
|
|
func (f *Filter) Retrieve() (all []*ReceivedMessage) {
|
|
f.mutex.Lock()
|
|
defer f.mutex.Unlock()
|
|
|
|
all = make([]*ReceivedMessage, 0, len(f.Messages))
|
|
for _, msg := range f.Messages {
|
|
all = append(all, msg)
|
|
}
|
|
|
|
f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages
|
|
return all
|
|
}
|
|
|
|
// MatchMessage checks if the filter matches an already decrypted
|
|
// message (i.e. a Message that has already been handled by
|
|
// MatchEnvelope when checked by a previous filter)
|
|
func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
|
|
if f.PoW > 0 && msg.PoW < f.PoW {
|
|
return false
|
|
}
|
|
|
|
if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
|
|
return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) && f.MatchTopic(msg.Topic)
|
|
} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
|
|
return f.SymKeyHash == msg.SymKeyHash && f.MatchTopic(msg.Topic)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// MatchEnvelope checks if it's worth decrypting the message. If
|
|
// it returns `true`, client code is expected to attempt decrypting
|
|
// the message and subsequently call MatchMessage.
|
|
func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
|
|
if f.PoW > 0 && envelope.pow < f.PoW {
|
|
return false
|
|
}
|
|
|
|
return f.MatchTopic(envelope.Topic)
|
|
}
|
|
|
|
// MatchTopic checks that the filter captures a given topic.
|
|
func (f *Filter) MatchTopic(topic TopicType) bool {
|
|
if len(f.Topics) == 0 {
|
|
// any topic matches
|
|
return true
|
|
}
|
|
|
|
for _, bt := range f.Topics {
|
|
if matchSingleTopic(topic, bt) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func matchSingleTopic(topic TopicType, bt []byte) bool {
|
|
if len(bt) > TopicLength {
|
|
bt = bt[:TopicLength]
|
|
}
|
|
|
|
if len(bt) < TopicLength {
|
|
return false
|
|
}
|
|
|
|
for j, b := range bt {
|
|
if topic[j] != b {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsPubKeyEqual checks that two public keys are equal
|
|
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
|
|
if !ValidatePublicKey(a) {
|
|
return false
|
|
} else if !ValidatePublicKey(b) {
|
|
return false
|
|
}
|
|
// the curve is always the same, just compare the points
|
|
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
|
|
}
|