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