github.com/mandrigin/go-ethereum@v1.7.4-0.20180116162341-02aeb3d76652/whisper/whisperv6/api.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package whisperv6 18 19 import ( 20 "context" 21 "crypto/ecdsa" 22 "errors" 23 "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 const ( 36 filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds 37 ) 38 39 var ( 40 ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") 41 ErrInvalidSymmetricKey = errors.New("invalid symmetric key") 42 ErrInvalidPublicKey = errors.New("invalid public key") 43 ErrInvalidSigningPubKey = errors.New("invalid signing public key") 44 ErrTooLowPoW = errors.New("message rejected, PoW too low") 45 ErrNoTopics = errors.New("missing topic(s)") 46 ) 47 48 // PublicWhisperAPI provides the whisper RPC service that can be 49 // use publicly without security implications. 50 type PublicWhisperAPI struct { 51 w *Whisper 52 53 mu sync.Mutex 54 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 api := &PublicWhisperAPI{ 60 w: w, 61 lastUsed: make(map[string]time.Time), 62 } 63 64 go api.run() 65 return api 66 } 67 68 // run the api event loop. 69 // this loop deletes filter that have not been used within filterTimeout 70 func (api *PublicWhisperAPI) run() { 71 timeout := time.NewTicker(2 * time.Minute) 72 for { 73 <-timeout.C 74 75 api.mu.Lock() 76 for id, lastUsed := range api.lastUsed { 77 if time.Since(lastUsed).Seconds() >= filterTimeout { 78 delete(api.lastUsed, id) 79 if err := api.w.Unsubscribe(id); err != nil { 80 log.Error("could not unsubscribe whisper filter", "error", err) 81 } 82 log.Debug("delete whisper filter (timeout)", "id", id) 83 } 84 } 85 api.mu.Unlock() 86 } 87 } 88 89 // Version returns the Whisper sub-protocol version. 90 func (api *PublicWhisperAPI) Version(ctx context.Context) string { 91 return ProtocolVersionStr 92 } 93 94 // Info contains diagnostic information. 95 type Info struct { 96 Memory int `json:"memory"` // Memory size of the floating messages in bytes. 97 Messages int `json:"messages"` // Number of floating messages. 98 MinPow float64 `json:"minPow"` // Minimal accepted PoW 99 MaxMessageSize uint32 `json:"maxMessageSize"` // Maximum accepted message size 100 } 101 102 // Info returns diagnostic information about the whisper node. 103 func (api *PublicWhisperAPI) Info(ctx context.Context) Info { 104 stats := api.w.Stats() 105 return Info{ 106 Memory: stats.memoryUsed, 107 Messages: len(api.w.messageQueue) + len(api.w.p2pMsgQueue), 108 MinPow: api.w.MinPow(), 109 MaxMessageSize: api.w.MaxMessageSize(), 110 } 111 } 112 113 // SetMaxMessageSize sets the maximum message size that is accepted. 114 // Upper limit is defined by MaxMessageSize. 115 func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) { 116 return true, api.w.SetMaxMessageSize(size) 117 } 118 119 // SetMinPow sets the minimum PoW, and notifies the peers. 120 func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { 121 return true, api.w.SetMinimumPoW(pow) 122 } 123 124 // SetBloomFilter sets the new value of bloom filter, and notifies the peers. 125 func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) { 126 return true, api.w.SetBloomFilter(bloom) 127 } 128 129 // MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages. 130 // Note: This function is not adding new nodes, the node needs to exists as a peer. 131 func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) { 132 n, err := discover.ParseNode(enode) 133 if err != nil { 134 return false, err 135 } 136 return true, api.w.AllowP2PMessagesFromPeer(n.ID[:]) 137 } 138 139 // NewKeyPair generates a new public and private key pair for message decryption and encryption. 140 // It returns an ID that can be used to refer to the keypair. 141 func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) { 142 return api.w.NewKeyPair() 143 } 144 145 // AddPrivateKey imports the given private key. 146 func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) { 147 key, err := crypto.ToECDSA(privateKey) 148 if err != nil { 149 return "", err 150 } 151 return api.w.AddKeyPair(key) 152 } 153 154 // DeleteKeyPair removes the key with the given key if it exists. 155 func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) { 156 if ok := api.w.DeleteKeyPair(key); ok { 157 return true, nil 158 } 159 return false, fmt.Errorf("key pair %s not found", key) 160 } 161 162 // HasKeyPair returns an indication if the node has a key pair that is associated with the given id. 163 func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool { 164 return api.w.HasKeyPair(id) 165 } 166 167 // GetPublicKey returns the public key associated with the given key. The key is the hex 168 // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. 169 func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) { 170 key, err := api.w.GetPrivateKey(id) 171 if err != nil { 172 return hexutil.Bytes{}, err 173 } 174 return crypto.FromECDSAPub(&key.PublicKey), nil 175 } 176 177 // GetPublicKey returns the private key associated with the given key. The key is the hex 178 // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. 179 func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) { 180 key, err := api.w.GetPrivateKey(id) 181 if err != nil { 182 return hexutil.Bytes{}, err 183 } 184 return crypto.FromECDSA(key), nil 185 } 186 187 // NewSymKey generate a random symmetric key. 188 // It returns an ID that can be used to refer to the key. 189 // Can be used encrypting and decrypting messages where the key is known to both parties. 190 func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) { 191 return api.w.GenerateSymKey() 192 } 193 194 // AddSymKey import a symmetric key. 195 // It returns an ID that can be used to refer to the key. 196 // Can be used encrypting and decrypting messages where the key is known to both parties. 197 func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) { 198 return api.w.AddSymKeyDirect([]byte(key)) 199 } 200 201 // GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID. 202 func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) { 203 return api.w.AddSymKeyFromPassword(passwd) 204 } 205 206 // HasSymKey returns an indication if the node has a symmetric key associated with the given key. 207 func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool { 208 return api.w.HasSymKey(id) 209 } 210 211 // GetSymKey returns the symmetric key associated with the given id. 212 func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) { 213 return api.w.GetSymKey(id) 214 } 215 216 // DeleteSymKey deletes the symmetric key that is associated with the given id. 217 func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool { 218 return api.w.DeleteSymKey(id) 219 } 220 221 //go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go 222 223 // NewMessage represents a new whisper message that is posted through the RPC. 224 type NewMessage struct { 225 SymKeyID string `json:"symKeyID"` 226 PublicKey []byte `json:"pubKey"` 227 Sig string `json:"sig"` 228 TTL uint32 `json:"ttl"` 229 Topic TopicType `json:"topic"` 230 Payload []byte `json:"payload"` 231 Padding []byte `json:"padding"` 232 PowTime uint32 `json:"powTime"` 233 PowTarget float64 `json:"powTarget"` 234 TargetPeer string `json:"targetPeer"` 235 } 236 237 type newMessageOverride struct { 238 PublicKey hexutil.Bytes 239 Payload hexutil.Bytes 240 Padding hexutil.Bytes 241 } 242 243 // Post a message on the Whisper network. 244 func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) { 245 var ( 246 symKeyGiven = len(req.SymKeyID) > 0 247 pubKeyGiven = len(req.PublicKey) > 0 248 err error 249 ) 250 251 // user must specify either a symmetric or an asymmetric key 252 if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { 253 return false, ErrSymAsym 254 } 255 256 params := &MessageParams{ 257 TTL: req.TTL, 258 Payload: req.Payload, 259 Padding: req.Padding, 260 WorkTime: req.PowTime, 261 PoW: req.PowTarget, 262 Topic: req.Topic, 263 } 264 265 // Set key that is used to sign the message 266 if len(req.Sig) > 0 { 267 if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil { 268 return false, err 269 } 270 } 271 272 // Set symmetric key that is used to encrypt the message 273 if symKeyGiven { 274 if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption 275 return false, ErrNoTopics 276 } 277 if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { 278 return false, err 279 } 280 if !validateSymmetricKey(params.KeySym) { 281 return false, ErrInvalidSymmetricKey 282 } 283 } 284 285 // Set asymmetric key that is used to encrypt the message 286 if pubKeyGiven { 287 params.Dst = crypto.ToECDSAPub(req.PublicKey) 288 if !ValidatePublicKey(params.Dst) { 289 return false, ErrInvalidPublicKey 290 } 291 } 292 293 // encrypt and sent message 294 whisperMsg, err := NewSentMessage(params) 295 if err != nil { 296 return false, err 297 } 298 299 env, err := whisperMsg.Wrap(params) 300 if err != nil { 301 return false, err 302 } 303 304 // send to specific node (skip PoW check) 305 if len(req.TargetPeer) > 0 { 306 n, err := discover.ParseNode(req.TargetPeer) 307 if err != nil { 308 return false, fmt.Errorf("failed to parse target peer: %s", err) 309 } 310 return true, api.w.SendP2PMessage(n.ID[:], env) 311 } 312 313 // ensure that the message PoW meets the node's minimum accepted PoW 314 if req.PowTarget < api.w.MinPow() { 315 return false, ErrTooLowPoW 316 } 317 318 return true, api.w.Send(env) 319 } 320 321 //go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go 322 323 // Criteria holds various filter options for inbound messages. 324 type Criteria struct { 325 SymKeyID string `json:"symKeyID"` 326 PrivateKeyID string `json:"privateKeyID"` 327 Sig []byte `json:"sig"` 328 MinPow float64 `json:"minPow"` 329 Topics []TopicType `json:"topics"` 330 AllowP2P bool `json:"allowP2P"` 331 } 332 333 type criteriaOverride struct { 334 Sig hexutil.Bytes 335 } 336 337 // Messages set up a subscription that fires events when messages arrive that match 338 // the given set of criteria. 339 func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) { 340 var ( 341 symKeyGiven = len(crit.SymKeyID) > 0 342 pubKeyGiven = len(crit.PrivateKeyID) > 0 343 err error 344 ) 345 346 // ensure that the RPC connection supports subscriptions 347 notifier, supported := rpc.NotifierFromContext(ctx) 348 if !supported { 349 return nil, rpc.ErrNotificationsUnsupported 350 } 351 352 // user must specify either a symmetric or an asymmetric key 353 if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { 354 return nil, ErrSymAsym 355 } 356 357 filter := Filter{ 358 PoW: crit.MinPow, 359 Messages: make(map[common.Hash]*ReceivedMessage), 360 AllowP2P: crit.AllowP2P, 361 } 362 363 if len(crit.Sig) > 0 { 364 filter.Src = crypto.ToECDSAPub(crit.Sig) 365 if !ValidatePublicKey(filter.Src) { 366 return nil, ErrInvalidSigningPubKey 367 } 368 } 369 370 for i, bt := range crit.Topics { 371 if len(bt) == 0 || len(bt) > 4 { 372 return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt)) 373 } 374 filter.Topics = append(filter.Topics, bt[:]) 375 } 376 377 // listen for message that are encrypted with the given symmetric key 378 if symKeyGiven { 379 if len(filter.Topics) == 0 { 380 return nil, ErrNoTopics 381 } 382 key, err := api.w.GetSymKey(crit.SymKeyID) 383 if err != nil { 384 return nil, err 385 } 386 if !validateSymmetricKey(key) { 387 return nil, ErrInvalidSymmetricKey 388 } 389 filter.KeySym = key 390 filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) 391 } 392 393 // listen for messages that are encrypted with the given public key 394 if pubKeyGiven { 395 filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID) 396 if err != nil || filter.KeyAsym == nil { 397 return nil, ErrInvalidPublicKey 398 } 399 } 400 401 id, err := api.w.Subscribe(&filter) 402 if err != nil { 403 return nil, err 404 } 405 406 // create subscription and start waiting for message events 407 rpcSub := notifier.CreateSubscription() 408 go func() { 409 // for now poll internally, refactor whisper internal for channel support 410 ticker := time.NewTicker(250 * time.Millisecond) 411 defer ticker.Stop() 412 413 for { 414 select { 415 case <-ticker.C: 416 if filter := api.w.GetFilter(id); filter != nil { 417 for _, rpcMessage := range toMessage(filter.Retrieve()) { 418 if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil { 419 log.Error("Failed to send notification", "err", err) 420 } 421 } 422 } 423 case <-rpcSub.Err(): 424 api.w.Unsubscribe(id) 425 return 426 case <-notifier.Closed(): 427 api.w.Unsubscribe(id) 428 return 429 } 430 } 431 }() 432 433 return rpcSub, nil 434 } 435 436 //go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go 437 438 // Message is the RPC representation of a whisper message. 439 type Message struct { 440 Sig []byte `json:"sig,omitempty"` 441 TTL uint32 `json:"ttl"` 442 Timestamp uint32 `json:"timestamp"` 443 Topic TopicType `json:"topic"` 444 Payload []byte `json:"payload"` 445 Padding []byte `json:"padding"` 446 PoW float64 `json:"pow"` 447 Hash []byte `json:"hash"` 448 Dst []byte `json:"recipientPublicKey,omitempty"` 449 } 450 451 type messageOverride struct { 452 Sig hexutil.Bytes 453 Payload hexutil.Bytes 454 Padding hexutil.Bytes 455 Hash hexutil.Bytes 456 Dst hexutil.Bytes 457 } 458 459 // ToWhisperMessage converts an internal message into an API version. 460 func ToWhisperMessage(message *ReceivedMessage) *Message { 461 msg := Message{ 462 Payload: message.Payload, 463 Padding: message.Padding, 464 Timestamp: message.Sent, 465 TTL: message.TTL, 466 PoW: message.PoW, 467 Hash: message.EnvelopeHash.Bytes(), 468 Topic: message.Topic, 469 } 470 471 if message.Dst != nil { 472 b := crypto.FromECDSAPub(message.Dst) 473 if b != nil { 474 msg.Dst = b 475 } 476 } 477 478 if isMessageSigned(message.Raw[0]) { 479 b := crypto.FromECDSAPub(message.SigToPubKey()) 480 if b != nil { 481 msg.Sig = b 482 } 483 } 484 485 return &msg 486 } 487 488 // toMessage converts a set of messages to its RPC representation. 489 func toMessage(messages []*ReceivedMessage) []*Message { 490 msgs := make([]*Message, len(messages)) 491 for i, msg := range messages { 492 msgs[i] = ToWhisperMessage(msg) 493 } 494 return msgs 495 } 496 497 // GetFilterMessages returns the messages that match the filter criteria and 498 // are received between the last poll and now. 499 func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) { 500 api.mu.Lock() 501 f := api.w.GetFilter(id) 502 if f == nil { 503 api.mu.Unlock() 504 return nil, fmt.Errorf("filter not found") 505 } 506 api.lastUsed[id] = time.Now() 507 api.mu.Unlock() 508 509 receivedMessages := f.Retrieve() 510 messages := make([]*Message, 0, len(receivedMessages)) 511 for _, msg := range receivedMessages { 512 messages = append(messages, ToWhisperMessage(msg)) 513 } 514 515 return messages, nil 516 } 517 518 // DeleteMessageFilter deletes a filter. 519 func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) { 520 api.mu.Lock() 521 defer api.mu.Unlock() 522 523 delete(api.lastUsed, id) 524 return true, api.w.Unsubscribe(id) 525 } 526 527 // NewMessageFilter creates a new filter that can be used to poll for 528 // (new) messages that satisfy the given criteria. 529 func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) { 530 var ( 531 src *ecdsa.PublicKey 532 keySym []byte 533 keyAsym *ecdsa.PrivateKey 534 topics [][]byte 535 536 symKeyGiven = len(req.SymKeyID) > 0 537 asymKeyGiven = len(req.PrivateKeyID) > 0 538 539 err error 540 ) 541 542 // user must specify either a symmetric or an asymmetric key 543 if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) { 544 return "", ErrSymAsym 545 } 546 547 if len(req.Sig) > 0 { 548 src = crypto.ToECDSAPub(req.Sig) 549 if !ValidatePublicKey(src) { 550 return "", ErrInvalidSigningPubKey 551 } 552 } 553 554 if symKeyGiven { 555 if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { 556 return "", err 557 } 558 if !validateSymmetricKey(keySym) { 559 return "", ErrInvalidSymmetricKey 560 } 561 } 562 563 if asymKeyGiven { 564 if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil { 565 return "", err 566 } 567 } 568 569 if len(req.Topics) > 0 { 570 topics = make([][]byte, 1) 571 for _, topic := range req.Topics { 572 topics = append(topics, topic[:]) 573 } 574 } 575 576 f := &Filter{ 577 Src: src, 578 KeySym: keySym, 579 KeyAsym: keyAsym, 580 PoW: req.MinPow, 581 AllowP2P: req.AllowP2P, 582 Topics: topics, 583 Messages: make(map[common.Hash]*ReceivedMessage), 584 } 585 586 id, err := api.w.Subscribe(f) 587 if err != nil { 588 return "", err 589 } 590 591 api.mu.Lock() 592 api.lastUsed[id] = time.Now() 593 api.mu.Unlock() 594 595 return id, nil 596 }