github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/whisper/whisperv5/api.go (about) 1 // Copyright 2016 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package whisperv5 18 19 import ( 20 "context" 21 "crypto/ecdsa" 22 "errors" 23 "fmt" 24 "sync" 25 "time" 26 27 "github.com/SmartMeshFoundation/Spectrum/common" 28 "github.com/SmartMeshFoundation/Spectrum/common/hexutil" 29 "github.com/SmartMeshFoundation/Spectrum/crypto" 30 "github.com/SmartMeshFoundation/Spectrum/log" 31 "github.com/SmartMeshFoundation/Spectrum/p2p/discover" 32 "github.com/SmartMeshFoundation/Spectrum/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 in whisperv5.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 for a message before it is accepted. 120 func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { 121 return true, api.w.SetMinimumPoW(pow) 122 } 123 124 // MarkTrustedPeer marks a peer trusted. , which will allow it to send historic (expired) messages. 125 // Note: This function is not adding new nodes, the node needs to exists as a peer. 126 func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, enode string) (bool, error) { 127 n, err := discover.ParseNode(enode) 128 if err != nil { 129 return false, err 130 } 131 return true, api.w.AllowP2PMessagesFromPeer(n.ID[:]) 132 } 133 134 // NewKeyPair generates a new public and private key pair for message decryption and encryption. 135 // It returns an ID that can be used to refer to the keypair. 136 func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) { 137 return api.w.NewKeyPair() 138 } 139 140 // AddPrivateKey imports the given private key. 141 func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) { 142 key, err := crypto.ToECDSA(privateKey) 143 if err != nil { 144 return "", err 145 } 146 return api.w.AddKeyPair(key) 147 } 148 149 // DeleteKeyPair removes the key with the given key if it exists. 150 func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) { 151 if ok := api.w.DeleteKeyPair(key); ok { 152 return true, nil 153 } 154 return false, fmt.Errorf("key pair %s not found", key) 155 } 156 157 // HasKeyPair returns an indication if the node has a key pair that is associated with the given id. 158 func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool { 159 return api.w.HasKeyPair(id) 160 } 161 162 // GetPublicKey returns the public key associated with the given key. The key is the hex 163 // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. 164 func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) { 165 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 // GetPublicKey returns the private key associated with the given key. The key is the hex 173 // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. 174 func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) { 175 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 // NewSymKey generate a random symmetric key. 183 // It returns an ID that can be used to refer to the key. 184 // Can be used encrypting and decrypting messages where the key is known to both parties. 185 func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) { 186 return api.w.GenerateSymKey() 187 } 188 189 // AddSymKey import a symmetric key. 190 // It returns an ID that can be used to refer to the key. 191 // Can be used encrypting and decrypting messages where the key is known to both parties. 192 func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) { 193 return api.w.AddSymKeyDirect([]byte(key)) 194 } 195 196 // GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID. 197 func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) { 198 return api.w.AddSymKeyFromPassword(passwd) 199 } 200 201 // HasSymKey returns an indication if the node has a symmetric key associated with the given key. 202 func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool { 203 return api.w.HasSymKey(id) 204 } 205 206 // GetSymKey returns the symmetric key associated with the given id. 207 func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) { 208 return api.w.GetSymKey(id) 209 } 210 211 // DeleteSymKey deletes the symmetric key that is associated with the given id. 212 func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool { 213 return api.w.DeleteSymKey(id) 214 } 215 216 //go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go 217 218 // NewMessage represents a new whisper message that is posted through the RPC. 219 type NewMessage struct { 220 SymKeyID string `json:"symKeyID"` 221 PublicKey []byte `json:"pubKey"` 222 Sig string `json:"sig"` 223 TTL uint32 `json:"ttl"` 224 Topic TopicType `json:"topic"` 225 Payload []byte `json:"payload"` 226 Padding []byte `json:"padding"` 227 PowTime uint32 `json:"powTime"` 228 PowTarget float64 `json:"powTarget"` 229 TargetPeer string `json:"targetPeer"` 230 } 231 232 type newMessageOverride struct { 233 PublicKey hexutil.Bytes 234 Payload hexutil.Bytes 235 Padding hexutil.Bytes 236 } 237 238 // Post a message on the Whisper network. 239 func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) { 240 var ( 241 symKeyGiven = len(req.SymKeyID) > 0 242 pubKeyGiven = len(req.PublicKey) > 0 243 err error 244 ) 245 246 // user must specify either a symmetric or an asymmetric key 247 if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { 248 return false, ErrSymAsym 249 } 250 251 params := &MessageParams{ 252 TTL: req.TTL, 253 Payload: req.Payload, 254 Padding: req.Padding, 255 WorkTime: req.PowTime, 256 PoW: req.PowTarget, 257 Topic: req.Topic, 258 } 259 260 // Set key that is used to sign the message 261 if len(req.Sig) > 0 { 262 if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil { 263 return false, err 264 } 265 } 266 267 // Set symmetric key that is used to encrypt the message 268 if symKeyGiven { 269 if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption 270 return false, ErrNoTopics 271 } 272 if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { 273 return false, err 274 } 275 if !validateSymmetricKey(params.KeySym) { 276 return false, ErrInvalidSymmetricKey 277 } 278 } 279 280 // Set asymmetric key that is used to encrypt the message 281 if pubKeyGiven { 282 params.Dst = crypto.ToECDSAPub(req.PublicKey) 283 if !ValidatePublicKey(params.Dst) { 284 return false, ErrInvalidPublicKey 285 } 286 } 287 288 // encrypt and sent message 289 whisperMsg, err := NewSentMessage(params) 290 if err != nil { 291 return false, err 292 } 293 294 env, err := whisperMsg.Wrap(params) 295 if err != nil { 296 return false, err 297 } 298 299 // send to specific node (skip PoW check) 300 if len(req.TargetPeer) > 0 { 301 n, err := discover.ParseNode(req.TargetPeer) 302 if err != nil { 303 return false, fmt.Errorf("failed to parse target peer: %s", err) 304 } 305 return true, api.w.SendP2PMessage(n.ID[:], env) 306 } 307 308 // ensure that the message PoW meets the node's minimum accepted PoW 309 if req.PowTarget < api.w.MinPow() { 310 return false, ErrTooLowPoW 311 } 312 313 return true, api.w.Send(env) 314 } 315 316 //go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go 317 318 // Criteria holds various filter options for inbound messages. 319 type Criteria struct { 320 SymKeyID string `json:"symKeyID"` 321 PrivateKeyID string `json:"privateKeyID"` 322 Sig []byte `json:"sig"` 323 MinPow float64 `json:"minPow"` 324 Topics []TopicType `json:"topics"` 325 AllowP2P bool `json:"allowP2P"` 326 } 327 328 type criteriaOverride struct { 329 Sig hexutil.Bytes 330 } 331 332 // Messages set up a subscription that fires events when messages arrive that match 333 // the given set of criteria. 334 func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) { 335 var ( 336 symKeyGiven = len(crit.SymKeyID) > 0 337 pubKeyGiven = len(crit.PrivateKeyID) > 0 338 err error 339 ) 340 341 // ensure that the RPC connection supports subscriptions 342 notifier, supported := rpc.NotifierFromContext(ctx) 343 if !supported { 344 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 filter := Filter{ 353 PoW: crit.MinPow, 354 Messages: make(map[common.Hash]*ReceivedMessage), 355 AllowP2P: crit.AllowP2P, 356 } 357 358 if len(crit.Sig) > 0 { 359 filter.Src = crypto.ToECDSAPub(crit.Sig) 360 if !ValidatePublicKey(filter.Src) { 361 return nil, ErrInvalidSigningPubKey 362 } 363 } 364 365 for i, bt := range crit.Topics { 366 if len(bt) == 0 || len(bt) > 4 { 367 return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt)) 368 } 369 filter.Topics = append(filter.Topics, bt[:]) 370 } 371 372 // listen for message that are encrypted with the given symmetric key 373 if symKeyGiven { 374 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 filter.KeySym = key 385 filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) 386 } 387 388 // listen for messages that are encrypted with the given public key 389 if pubKeyGiven { 390 filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID) 391 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 // create subscription and start waiting for message events 402 rpcSub := notifier.CreateSubscription() 403 go func() { 404 // for now poll internally, refactor whisper internal for channel support 405 ticker := time.NewTicker(250 * time.Millisecond) 406 defer ticker.Stop() 407 408 for { 409 select { 410 case <-ticker.C: 411 if filter := api.w.GetFilter(id); filter != nil { 412 for _, rpcMessage := range toMessage(filter.Retrieve()) { 413 if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil { 414 log.Error("Failed to send notification", "err", err) 415 } 416 } 417 } 418 case <-rpcSub.Err(): 419 api.w.Unsubscribe(id) 420 return 421 case <-notifier.Closed(): 422 api.w.Unsubscribe(id) 423 return 424 } 425 } 426 }() 427 428 return rpcSub, nil 429 } 430 431 //go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go 432 433 // Message is the RPC representation of a whisper message. 434 type Message struct { 435 Sig []byte `json:"sig,omitempty"` 436 TTL uint32 `json:"ttl"` 437 Timestamp uint32 `json:"timestamp"` 438 Topic TopicType `json:"topic"` 439 Payload []byte `json:"payload"` 440 Padding []byte `json:"padding"` 441 PoW float64 `json:"pow"` 442 Hash []byte `json:"hash"` 443 Dst []byte `json:"recipientPublicKey,omitempty"` 444 } 445 446 type messageOverride struct { 447 Sig hexutil.Bytes 448 Payload hexutil.Bytes 449 Padding hexutil.Bytes 450 Hash hexutil.Bytes 451 Dst hexutil.Bytes 452 } 453 454 // ToWhisperMessage converts an internal message into an API version. 455 func ToWhisperMessage(message *ReceivedMessage) *Message { 456 msg := Message{ 457 Payload: message.Payload, 458 Padding: message.Padding, 459 Timestamp: message.Sent, 460 TTL: message.TTL, 461 PoW: message.PoW, 462 Hash: message.EnvelopeHash.Bytes(), 463 Topic: message.Topic, 464 } 465 466 if message.Dst != nil { 467 b := crypto.FromECDSAPub(message.Dst) 468 if b != nil { 469 msg.Dst = b 470 } 471 } 472 473 if isMessageSigned(message.Raw[0]) { 474 b := crypto.FromECDSAPub(message.SigToPubKey()) 475 if b != nil { 476 msg.Sig = b 477 } 478 } 479 480 return &msg 481 } 482 483 // toMessage converts a set of messages to its RPC representation. 484 func toMessage(messages []*ReceivedMessage) []*Message { 485 msgs := make([]*Message, len(messages)) 486 for i, msg := range messages { 487 msgs[i] = ToWhisperMessage(msg) 488 } 489 return msgs 490 } 491 492 // GetFilterMessages returns the messages that match the filter criteria and 493 // are received between the last poll and now. 494 func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) { 495 api.mu.Lock() 496 f := api.w.GetFilter(id) 497 if f == nil { 498 api.mu.Unlock() 499 return nil, fmt.Errorf("filter not found") 500 } 501 api.lastUsed[id] = time.Now() 502 api.mu.Unlock() 503 504 receivedMessages := f.Retrieve() 505 messages := make([]*Message, 0, len(receivedMessages)) 506 for _, msg := range receivedMessages { 507 messages = append(messages, ToWhisperMessage(msg)) 508 } 509 510 return messages, nil 511 } 512 513 // DeleteMessageFilter deletes a filter. 514 func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) { 515 api.mu.Lock() 516 defer api.mu.Unlock() 517 518 delete(api.lastUsed, id) 519 return true, api.w.Unsubscribe(id) 520 } 521 522 // NewMessageFilter creates a new filter that can be used to poll for 523 // (new) messages that satisfy the given criteria. 524 func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) { 525 var ( 526 src *ecdsa.PublicKey 527 keySym []byte 528 keyAsym *ecdsa.PrivateKey 529 topics [][]byte 530 531 symKeyGiven = len(req.SymKeyID) > 0 532 asymKeyGiven = len(req.PrivateKeyID) > 0 533 534 err error 535 ) 536 537 // user must specify either a symmetric or an asymmetric key 538 if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) { 539 return "", ErrSymAsym 540 } 541 542 if len(req.Sig) > 0 { 543 src = crypto.ToECDSAPub(req.Sig) 544 if !ValidatePublicKey(src) { 545 return "", ErrInvalidSigningPubKey 546 } 547 } 548 549 if symKeyGiven { 550 if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { 551 return "", err 552 } 553 if !validateSymmetricKey(keySym) { 554 return "", ErrInvalidSymmetricKey 555 } 556 } 557 558 if asymKeyGiven { 559 if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil { 560 return "", err 561 } 562 } 563 564 if len(req.Topics) > 0 { 565 topics = make([][]byte, 1) 566 for _, topic := range req.Topics { 567 topics = append(topics, topic[:]) 568 } 569 } 570 571 f := &Filter{ 572 Src: src, 573 KeySym: keySym, 574 KeyAsym: keyAsym, 575 PoW: req.MinPow, 576 AllowP2P: req.AllowP2P, 577 Topics: topics, 578 Messages: make(map[common.Hash]*ReceivedMessage), 579 } 580 581 id, err := api.w.Subscribe(f) 582 if err != nil { 583 return "", err 584 } 585 586 api.mu.Lock() 587 api.lastUsed[id] = time.Now() 588 api.mu.Unlock() 589 590 return id, nil 591 }