api.go 17 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 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/>.

17
package whisperv5
18 19

import (
20
	"context"
21
	"crypto/ecdsa"
22 23
	"errors"
	"fmt"
24
	"sync"
25
	"time"
26 27

	"github.com/ethereum/go-ethereum/common"
28
	"github.com/ethereum/go-ethereum/common/hexutil"
29
	"github.com/ethereum/go-ethereum/crypto"
30
	"github.com/ethereum/go-ethereum/log"
31
	"github.com/ethereum/go-ethereum/p2p/discover"
32
	"github.com/ethereum/go-ethereum/rpc"
33 34
)

35 36 37 38 39
const (
	filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds
)

var (
40
	ErrSymAsym              = errors.New("specify either a symmetric or an asymmetric key")
41 42 43 44 45
	ErrInvalidSymmetricKey  = errors.New("invalid symmetric key")
	ErrInvalidPublicKey     = errors.New("invalid public key")
	ErrInvalidSigningPubKey = errors.New("invalid signing public key")
	ErrTooLowPoW            = errors.New("message rejected, PoW too low")
	ErrNoTopics             = errors.New("missing topic(s)")
46
)
47

48 49
// PublicWhisperAPI provides the whisper RPC service that can be
// use publicly without security implications.
50
type PublicWhisperAPI struct {
51 52 53 54
	w *Whisper

	mu       sync.Mutex
	lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
55 56 57
}

// NewPublicWhisperAPI create a new RPC whisper service.
58
func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
59 60 61 62 63 64 65
	api := &PublicWhisperAPI{
		w:        w,
		lastUsed: make(map[string]time.Time),
	}

	go api.run()
	return api
66 67
}

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
// run the api event loop.
// this loop deletes filter that have not been used within filterTimeout
func (api *PublicWhisperAPI) run() {
	timeout := time.NewTicker(2 * time.Minute)
	for {
		<-timeout.C

		api.mu.Lock()
		for id, lastUsed := range api.lastUsed {
			if time.Since(lastUsed).Seconds() >= filterTimeout {
				delete(api.lastUsed, id)
				if err := api.w.Unsubscribe(id); err != nil {
					log.Error("could not unsubscribe whisper filter", "error", err)
				}
				log.Debug("delete whisper filter (timeout)", "id", id)
			}
		}
		api.mu.Unlock()
gluk256's avatar
gluk256 committed
86 87 88
	}
}

89 90 91
// Version returns the Whisper sub-protocol version.
func (api *PublicWhisperAPI) Version(ctx context.Context) string {
	return ProtocolVersionStr
gluk256's avatar
gluk256 committed
92 93
}

94 95 96
// Info contains diagnostic information.
type Info struct {
	Memory         int     `json:"memory"`         // Memory size of the floating messages in bytes.
97
	Messages       int     `json:"messages"`       // Number of floating messages.
98 99
	MinPow         float64 `json:"minPow"`         // Minimal accepted PoW
	MaxMessageSize uint32  `json:"maxMessageSize"` // Maximum accepted message size
100 101
}

102 103 104 105 106
// Info returns diagnostic information about the whisper node.
func (api *PublicWhisperAPI) Info(ctx context.Context) Info {
	stats := api.w.Stats()
	return Info{
		Memory:         stats.memoryUsed,
107
		Messages:       len(api.w.messageQueue) + len(api.w.p2pMsgQueue),
108 109
		MinPow:         api.w.MinPow(),
		MaxMessageSize: api.w.MaxMessageSize(),
110 111 112
	}
}

113 114 115 116
// SetMaxMessageSize sets the maximum message size that is accepted.
// Upper limit is defined in whisperv5.MaxMessageSize.
func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) {
	return true, api.w.SetMaxMessageSize(size)
117 118
}

119 120 121
// SetMinPow sets the minimum PoW for a message before it is accepted.
func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
	return true, api.w.SetMinimumPoW(pow)
122 123
}

124 125 126
// MarkTrustedPeer marks a peer trusted. , which will allow it to send historic (expired) messages.
// Note: This function is not adding new nodes, the node needs to exists as a peer.
func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) {
127 128
	n, err := discover.ParseNode(enode)
	if err != nil {
129
		return false, err
130
	}
131
	return true, api.w.AllowP2PMessagesFromPeer(n.ID[:])
132 133
}

134 135 136 137
// NewKeyPair generates a new public and private key pair for message decryption and encryption.
// It returns an ID that can be used to refer to the keypair.
func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) {
	return api.w.NewKeyPair()
138 139
}

140 141 142 143 144
// AddPrivateKey imports the given private key.
func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) {
	key, err := crypto.ToECDSA(privateKey)
	if err != nil {
		return "", err
145
	}
146
	return api.w.AddKeyPair(key)
147 148
}

149 150 151 152
// DeleteKeyPair removes the key with the given key if it exists.
func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
	if ok := api.w.DeleteKeyPair(key); ok {
		return true, nil
153
	}
154
	return false, fmt.Errorf("key pair %s not found", key)
155 156
}

157 158 159 160 161 162 163 164 165
// HasKeyPair returns an indication if the node has a key pair that is associated with the given id.
func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool {
	return api.w.HasKeyPair(id)
}

// GetPublicKey returns the public key associated with the given key. The key is the hex
// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) {
	key, err := api.w.GetPrivateKey(id)
166
	if err != nil {
167
		return hexutil.Bytes{}, err
168
	}
169
	return crypto.FromECDSAPub(&key.PublicKey), nil
170 171
}

172 173 174 175
// GetPublicKey returns the private key associated with the given key. The key is the hex
// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) {
	key, err := api.w.GetPrivateKey(id)
176
	if err != nil {
177
		return hexutil.Bytes{}, err
178
	}
179
	return crypto.FromECDSA(key), nil
180 181
}

182 183 184 185 186
// NewSymKey generate a random symmetric key.
// It returns an ID that can be used to refer to the key.
// Can be used encrypting and decrypting messages where the key is known to both parties.
func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) {
	return api.w.GenerateSymKey()
187 188
}

189 190 191 192 193
// AddSymKey import a symmetric key.
// It returns an ID that can be used to refer to the key.
// Can be used encrypting and decrypting messages where the key is known to both parties.
func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) {
	return api.w.AddSymKeyDirect([]byte(key))
194 195
}

196 197 198
// GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID.
func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
	return api.w.AddSymKeyFromPassword(passwd)
199
}
200

201 202 203
// HasSymKey returns an indication if the node has a symmetric key associated with the given key.
func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool {
	return api.w.HasSymKey(id)
204
}
205

206 207 208
// GetSymKey returns the symmetric key associated with the given id.
func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) {
	return api.w.GetSymKey(id)
209
}
210

211 212 213
// DeleteSymKey deletes the symmetric key that is associated with the given id.
func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool {
	return api.w.DeleteSymKey(id)
214
}
215

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
//go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go

// NewMessage represents a new whisper message that is posted through the RPC.
type NewMessage struct {
	SymKeyID   string    `json:"symKeyID"`
	PublicKey  []byte    `json:"pubKey"`
	Sig        string    `json:"sig"`
	TTL        uint32    `json:"ttl"`
	Topic      TopicType `json:"topic"`
	Payload    []byte    `json:"payload"`
	Padding    []byte    `json:"padding"`
	PowTime    uint32    `json:"powTime"`
	PowTarget  float64   `json:"powTarget"`
	TargetPeer string    `json:"targetPeer"`
}
231

232 233 234 235 236
type newMessageOverride struct {
	PublicKey hexutil.Bytes
	Payload   hexutil.Bytes
	Padding   hexutil.Bytes
}
237

238 239 240 241 242 243 244 245
// Post a message on the Whisper network.
func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) {
	var (
		symKeyGiven = len(req.SymKeyID) > 0
		pubKeyGiven = len(req.PublicKey) > 0
		err         error
	)

246
	// user must specify either a symmetric or an asymmetric key
247
	if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
248
		return false, ErrSymAsym
249 250
	}

251 252 253 254 255 256 257
	params := &MessageParams{
		TTL:      req.TTL,
		Payload:  req.Payload,
		Padding:  req.Padding,
		WorkTime: req.PowTime,
		PoW:      req.PowTarget,
		Topic:    req.Topic,
258 259
	}

260 261 262 263
	// Set key that is used to sign the message
	if len(req.Sig) > 0 {
		if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil {
			return false, err
264 265 266
		}
	}

267 268 269
	// Set symmetric key that is used to encrypt the message
	if symKeyGiven {
		if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption
270
			return false, ErrNoTopics
271
		}
272 273
		if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
			return false, err
274
		}
275
		if !validateSymmetricKey(params.KeySym) {
276
			return false, ErrInvalidSymmetricKey
277
		}
278 279 280 281 282 283
	}

	// Set asymmetric key that is used to encrypt the message
	if pubKeyGiven {
		params.Dst = crypto.ToECDSAPub(req.PublicKey)
		if !ValidatePublicKey(params.Dst) {
284
			return false, ErrInvalidPublicKey
285 286 287
		}
	}

288 289 290 291 292
	// encrypt and sent message
	whisperMsg, err := NewSentMessage(params)
	if err != nil {
		return false, err
	}
293

294 295 296 297 298 299 300 301 302 303 304 305 306
	env, err := whisperMsg.Wrap(params)
	if err != nil {
		return false, err
	}

	// send to specific node (skip PoW check)
	if len(req.TargetPeer) > 0 {
		n, err := discover.ParseNode(req.TargetPeer)
		if err != nil {
			return false, fmt.Errorf("failed to parse target peer: %s", err)
		}
		return true, api.w.SendP2PMessage(n.ID[:], env)
	}
307

308 309
	// ensure that the message PoW meets the node's minimum accepted PoW
	if req.PowTarget < api.w.MinPow() {
310
		return false, ErrTooLowPoW
311
	}
312 313

	return true, api.w.Send(env)
314 315
}

316 317 318 319 320 321 322 323 324 325
//go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go

// Criteria holds various filter options for inbound messages.
type Criteria struct {
	SymKeyID     string      `json:"symKeyID"`
	PrivateKeyID string      `json:"privateKeyID"`
	Sig          []byte      `json:"sig"`
	MinPow       float64     `json:"minPow"`
	Topics       []TopicType `json:"topics"`
	AllowP2P     bool        `json:"allowP2P"`
326 327
}

328 329
type criteriaOverride struct {
	Sig hexutil.Bytes
330 331
}

332 333 334 335 336 337 338 339 340 341 342 343 344
// Messages set up a subscription that fires events when messages arrive that match
// the given set of criteria.
func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) {
	var (
		symKeyGiven = len(crit.SymKeyID) > 0
		pubKeyGiven = len(crit.PrivateKeyID) > 0
		err         error
	)

	// ensure that the RPC connection supports subscriptions
	notifier, supported := rpc.NotifierFromContext(ctx)
	if !supported {
		return nil, rpc.ErrNotificationsUnsupported
345 346
	}

347
	// user must specify either a symmetric or an asymmetric key
348
	if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
349
		return nil, ErrSymAsym
350 351
	}

352 353 354 355
	filter := Filter{
		PoW:      crit.MinPow,
		Messages: make(map[common.Hash]*ReceivedMessage),
		AllowP2P: crit.AllowP2P,
356 357
	}

358 359 360
	if len(crit.Sig) > 0 {
		filter.Src = crypto.ToECDSAPub(crit.Sig)
		if !ValidatePublicKey(filter.Src) {
361
			return nil, ErrInvalidSigningPubKey
362 363 364
		}
	}

365 366 367 368 369
	for i, bt := range crit.Topics {
		if len(bt) == 0 || len(bt) > 4 {
			return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt))
		}
		filter.Topics = append(filter.Topics, bt[:])
370 371
	}

372 373 374
	// listen for message that are encrypted with the given symmetric key
	if symKeyGiven {
		if len(filter.Topics) == 0 {
375
			return nil, ErrNoTopics
376
		}
377
		key, err := api.w.GetSymKey(crit.SymKeyID)
378
		if err != nil {
379
			return nil, err
380
		}
381
		if !validateSymmetricKey(key) {
382
			return nil, ErrInvalidSymmetricKey
383
		}
384 385
		filter.KeySym = key
		filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
386 387
	}

388 389 390 391
	// listen for messages that are encrypted with the given public key
	if pubKeyGiven {
		filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID)
		if err != nil || filter.KeyAsym == nil {
392
			return nil, ErrInvalidPublicKey
393
		}
394
	}
395 396

	id, err := api.w.Subscribe(&filter)
397
	if err != nil {
398
		return nil, err
399 400
	}

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
	// create subscription and start waiting for message events
	rpcSub := notifier.CreateSubscription()
	go func() {
		// for now poll internally, refactor whisper internal for channel support
		ticker := time.NewTicker(250 * time.Millisecond)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				if filter := api.w.GetFilter(id); filter != nil {
					for _, rpcMessage := range toMessage(filter.Retrieve()) {
						if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil {
							log.Error("Failed to send notification", "err", err)
						}
					}
				}
			case <-rpcSub.Err():
				api.w.Unsubscribe(id)
				return
			case <-notifier.Closed():
				api.w.Unsubscribe(id)
				return
424 425
			}
		}
426
	}()
427

428
	return rpcSub, nil
429 430
}

431 432 433 434 435 436 437 438 439 440 441 442 443
//go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go

// Message is the RPC representation of a whisper message.
type Message struct {
	Sig       []byte    `json:"sig,omitempty"`
	TTL       uint32    `json:"ttl"`
	Timestamp uint32    `json:"timestamp"`
	Topic     TopicType `json:"topic"`
	Payload   []byte    `json:"payload"`
	Padding   []byte    `json:"padding"`
	PoW       float64   `json:"pow"`
	Hash      []byte    `json:"hash"`
	Dst       []byte    `json:"recipientPublicKey,omitempty"`
444 445
}

446 447 448 449 450 451 452 453 454 455 456 457 458
type messageOverride struct {
	Sig     hexutil.Bytes
	Payload hexutil.Bytes
	Padding hexutil.Bytes
	Hash    hexutil.Bytes
	Dst     hexutil.Bytes
}

// ToWhisperMessage converts an internal message into an API version.
func ToWhisperMessage(message *ReceivedMessage) *Message {
	msg := Message{
		Payload:   message.Payload,
		Padding:   message.Padding,
459 460 461
		Timestamp: message.Sent,
		TTL:       message.TTL,
		PoW:       message.PoW,
462 463
		Hash:      message.EnvelopeHash.Bytes(),
		Topic:     message.Topic,
464
	}
gluk256's avatar
gluk256 committed
465 466

	if message.Dst != nil {
467 468
		b := crypto.FromECDSAPub(message.Dst)
		if b != nil {
469
			msg.Dst = b
470
		}
gluk256's avatar
gluk256 committed
471
	}
472

gluk256's avatar
gluk256 committed
473
	if isMessageSigned(message.Raw[0]) {
474 475
		b := crypto.FromECDSAPub(message.SigToPubKey())
		if b != nil {
476
			msg.Sig = b
477
		}
gluk256's avatar
gluk256 committed
478
	}
479

gluk256's avatar
gluk256 committed
480
	return &msg
481
}
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536

// toMessage converts a set of messages to its RPC representation.
func toMessage(messages []*ReceivedMessage) []*Message {
	msgs := make([]*Message, len(messages))
	for i, msg := range messages {
		msgs[i] = ToWhisperMessage(msg)
	}
	return msgs
}

// GetFilterMessages returns the messages that match the filter criteria and
// are received between the last poll and now.
func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) {
	api.mu.Lock()
	f := api.w.GetFilter(id)
	if f == nil {
		api.mu.Unlock()
		return nil, fmt.Errorf("filter not found")
	}
	api.lastUsed[id] = time.Now()
	api.mu.Unlock()

	receivedMessages := f.Retrieve()
	messages := make([]*Message, 0, len(receivedMessages))
	for _, msg := range receivedMessages {
		messages = append(messages, ToWhisperMessage(msg))
	}

	return messages, nil
}

// DeleteMessageFilter deletes a filter.
func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) {
	api.mu.Lock()
	defer api.mu.Unlock()

	delete(api.lastUsed, id)
	return true, api.w.Unsubscribe(id)
}

// NewMessageFilter creates a new filter that can be used to poll for
// (new) messages that satisfy the given criteria.
func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) {
	var (
		src     *ecdsa.PublicKey
		keySym  []byte
		keyAsym *ecdsa.PrivateKey
		topics  [][]byte

		symKeyGiven  = len(req.SymKeyID) > 0
		asymKeyGiven = len(req.PrivateKeyID) > 0

		err error
	)

537
	// user must specify either a symmetric or an asymmetric key
538
	if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) {
539
		return "", ErrSymAsym
540 541 542 543 544
	}

	if len(req.Sig) > 0 {
		src = crypto.ToECDSAPub(req.Sig)
		if !ValidatePublicKey(src) {
545
			return "", ErrInvalidSigningPubKey
546 547 548 549 550 551 552 553
		}
	}

	if symKeyGiven {
		if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
			return "", err
		}
		if !validateSymmetricKey(keySym) {
554
			return "", ErrInvalidSymmetricKey
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
		}
	}

	if asymKeyGiven {
		if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil {
			return "", err
		}
	}

	if len(req.Topics) > 0 {
		topics = make([][]byte, 1)
		for _, topic := range req.Topics {
			topics = append(topics, topic[:])
		}
	}

	f := &Filter{
		Src:      src,
		KeySym:   keySym,
		KeyAsym:  keyAsym,
		PoW:      req.MinPow,
		AllowP2P: req.AllowP2P,
		Topics:   topics,
		Messages: make(map[common.Hash]*ReceivedMessage),
	}

	id, err := api.w.Subscribe(f)
	if err != nil {
		return "", err
	}

	api.mu.Lock()
	api.lastUsed[id] = time.Now()
	api.mu.Unlock()

	return id, nil
}