| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- // 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 whisperv5
- import (
- "encoding/json"
- "errors"
- "fmt"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/discover"
- )
- var whisperOfflineErr = errors.New("whisper is offline")
- // PublicWhisperAPI provides the whisper RPC service.
- type PublicWhisperAPI struct {
- whisper *Whisper
- }
- // NewPublicWhisperAPI create a new RPC whisper service.
- func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
- return &PublicWhisperAPI{whisper: w}
- }
- // Start starts the Whisper worker threads.
- func (api *PublicWhisperAPI) Start() error {
- if api.whisper == nil {
- return whisperOfflineErr
- }
- return api.whisper.Start(nil)
- }
- // Stop stops the Whisper worker threads.
- func (api *PublicWhisperAPI) Stop() error {
- if api.whisper == nil {
- return whisperOfflineErr
- }
- return api.whisper.Stop()
- }
- // Version returns the Whisper version this node offers.
- func (api *PublicWhisperAPI) Version() (hexutil.Uint, error) {
- if api.whisper == nil {
- return 0, whisperOfflineErr
- }
- return hexutil.Uint(api.whisper.Version()), nil
- }
- // Info returns the Whisper statistics for diagnostics.
- func (api *PublicWhisperAPI) Info() (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- return api.whisper.Stats(), nil
- }
- // SetMaxMessageLength sets the maximal message length allowed by this node
- func (api *PublicWhisperAPI) SetMaxMessageLength(val int) error {
- if api.whisper == nil {
- return whisperOfflineErr
- }
- return api.whisper.SetMaxMessageLength(val)
- }
- // SetMinimumPoW sets the minimal PoW required by this node
- func (api *PublicWhisperAPI) SetMinimumPoW(val float64) error {
- if api.whisper == nil {
- return whisperOfflineErr
- }
- return api.whisper.SetMinimumPoW(val)
- }
- // AllowP2PMessagesFromPeer marks specific peer trusted, which will allow it
- // to send historic (expired) messages.
- func (api *PublicWhisperAPI) AllowP2PMessagesFromPeer(enode string) error {
- if api.whisper == nil {
- return whisperOfflineErr
- }
- n, err := discover.ParseNode(enode)
- if err != nil {
- return errors.New("failed to parse enode of trusted peer: " + err.Error())
- }
- return api.whisper.AllowP2PMessagesFromPeer(n.ID[:])
- }
- // HasKeyPair checks if the whisper node is configured with the private key
- // of the specified public pair.
- func (api *PublicWhisperAPI) HasKeyPair(id string) (bool, error) {
- if api.whisper == nil {
- return false, whisperOfflineErr
- }
- return api.whisper.HasKeyPair(id), nil
- }
- // DeleteKeyPair deletes the specifies key if it exists.
- func (api *PublicWhisperAPI) DeleteKeyPair(id string) (bool, error) {
- if api.whisper == nil {
- return false, whisperOfflineErr
- }
- return api.whisper.DeleteKeyPair(id), nil
- }
- // NewKeyPair generates a new cryptographic identity for the client, and injects
- // it into the known identities for message decryption.
- func (api *PublicWhisperAPI) NewKeyPair() (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- return api.whisper.NewKeyPair()
- }
- // GetPublicKey returns the public key for identity id
- func (api *PublicWhisperAPI) GetPublicKey(id string) (hexutil.Bytes, error) {
- if api.whisper == nil {
- return nil, whisperOfflineErr
- }
- key, err := api.whisper.GetPrivateKey(id)
- if err != nil {
- return nil, err
- }
- return crypto.FromECDSAPub(&key.PublicKey), nil
- }
- // GetPrivateKey returns the private key for identity id
- func (api *PublicWhisperAPI) GetPrivateKey(id string) (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- key, err := api.whisper.GetPrivateKey(id)
- if err != nil {
- return "", err
- }
- return common.ToHex(crypto.FromECDSA(key)), nil
- }
- // GenerateSymmetricKey generates a random symmetric key and stores it under id,
- // which is then returned. Will be used in the future for session key exchange.
- func (api *PublicWhisperAPI) GenerateSymmetricKey() (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- return api.whisper.GenerateSymKey()
- }
- // AddSymmetricKeyDirect stores the key, and returns its id.
- func (api *PublicWhisperAPI) AddSymmetricKeyDirect(key hexutil.Bytes) (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- return api.whisper.AddSymKeyDirect(key)
- }
- // AddSymmetricKeyFromPassword generates the key from password, stores it, and returns its id.
- func (api *PublicWhisperAPI) AddSymmetricKeyFromPassword(password string) (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- return api.whisper.AddSymKeyFromPassword(password)
- }
- // HasSymmetricKey returns true if there is a key associated with the given id.
- // Otherwise returns false.
- func (api *PublicWhisperAPI) HasSymmetricKey(id string) (bool, error) {
- if api.whisper == nil {
- return false, whisperOfflineErr
- }
- res := api.whisper.HasSymKey(id)
- return res, nil
- }
- // GetSymmetricKey returns the symmetric key associated with the given id.
- func (api *PublicWhisperAPI) GetSymmetricKey(name string) (hexutil.Bytes, error) {
- if api.whisper == nil {
- return nil, whisperOfflineErr
- }
- b, err := api.whisper.GetSymKey(name)
- if err != nil {
- return nil, err
- }
- return b, nil
- }
- // DeleteSymmetricKey deletes the key associated with the name string if it exists.
- func (api *PublicWhisperAPI) DeleteSymmetricKey(name string) (bool, error) {
- if api.whisper == nil {
- return false, whisperOfflineErr
- }
- res := api.whisper.DeleteSymKey(name)
- return res, nil
- }
- // Subscribe creates and registers a new filter to watch for inbound whisper messages.
- // Returns the ID of the newly created filter.
- func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) {
- if api.whisper == nil {
- return "", whisperOfflineErr
- }
- filter := Filter{
- PoW: args.MinPoW,
- Messages: make(map[common.Hash]*ReceivedMessage),
- AllowP2P: args.AllowP2P,
- }
- var err error
- for i, bt := range args.Topics {
- if len(bt) == 0 || len(bt) > 4 {
- return "", errors.New(fmt.Sprintf("subscribe: topic %d has wrong size: %d", i, len(bt)))
- }
- filter.Topics = append(filter.Topics, bt)
- }
- if err = ValidateKeyID(args.Key); err != nil {
- return "", errors.New("subscribe: " + err.Error())
- }
- if len(args.Sig) > 0 {
- sb := common.FromHex(args.Sig)
- if sb == nil {
- return "", errors.New("subscribe: sig parameter is invalid")
- }
- filter.Src = crypto.ToECDSAPub(sb)
- if !ValidatePublicKey(filter.Src) {
- return "", errors.New("subscribe: invalid 'sig' field")
- }
- }
- if args.Symmetric {
- if len(args.Topics) == 0 {
- return "", errors.New("subscribe: at least one topic must be specified with symmetric encryption")
- }
- symKey, err := api.whisper.GetSymKey(args.Key)
- if err != nil {
- return "", errors.New("subscribe: invalid key ID")
- }
- if !validateSymmetricKey(symKey) {
- return "", errors.New("subscribe: retrieved key is invalid")
- }
- filter.KeySym = symKey
- filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
- } else {
- filter.KeyAsym, err = api.whisper.GetPrivateKey(args.Key)
- if err != nil {
- return "", errors.New("subscribe: invalid key ID")
- }
- if filter.KeyAsym == nil {
- return "", errors.New("subscribe: non-existent identity provided")
- }
- }
- return api.whisper.Subscribe(&filter)
- }
- // Unsubscribe disables and removes an existing filter.
- func (api *PublicWhisperAPI) Unsubscribe(id string) {
- api.whisper.Unsubscribe(id)
- }
- // GetSubscriptionMessages retrieves all the new messages matched by the corresponding
- // subscription filter since the last retrieval.
- func (api *PublicWhisperAPI) GetNewSubscriptionMessages(id string) []*WhisperMessage {
- f := api.whisper.GetFilter(id)
- if f != nil {
- newMail := f.Retrieve()
- return toWhisperMessages(newMail)
- }
- return toWhisperMessages(nil)
- }
- // GetMessages retrieves all the floating messages that match a specific subscription filter.
- // It is likely to be called once per session, right after Subscribe call.
- func (api *PublicWhisperAPI) GetFloatingMessages(id string) []*WhisperMessage {
- all := api.whisper.Messages(id)
- return toWhisperMessages(all)
- }
- // toWhisperMessages converts a Whisper message to a RPC whisper message.
- func toWhisperMessages(messages []*ReceivedMessage) []*WhisperMessage {
- msgs := make([]*WhisperMessage, len(messages))
- for i, msg := range messages {
- msgs[i] = NewWhisperMessage(msg)
- }
- return msgs
- }
- // Post creates a whisper message and injects it into the network for distribution.
- func (api *PublicWhisperAPI) Post(args PostArgs) error {
- if api.whisper == nil {
- return whisperOfflineErr
- }
- var err error
- params := MessageParams{
- TTL: args.TTL,
- WorkTime: args.PowTime,
- PoW: args.PowTarget,
- Payload: args.Payload,
- Padding: args.Padding,
- }
- if len(args.Key) == 0 {
- return errors.New("post: key is missing")
- }
- if len(args.Sig) > 0 {
- params.Src, err = api.whisper.GetPrivateKey(args.Sig)
- if err != nil {
- return err
- }
- if params.Src == nil {
- return errors.New("post: empty identity")
- }
- }
- if len(args.Topic) == TopicLength {
- params.Topic = BytesToTopic(args.Topic)
- } else if len(args.Topic) != 0 {
- return errors.New(fmt.Sprintf("post: wrong topic size %d", len(args.Topic)))
- }
- if args.Type == "sym" {
- if err = ValidateKeyID(args.Key); err != nil {
- return err
- }
- params.KeySym, err = api.whisper.GetSymKey(args.Key)
- if err != nil {
- return err
- }
- if !validateSymmetricKey(params.KeySym) {
- return errors.New("post: key for symmetric encryption is invalid")
- }
- if len(params.Topic) == 0 {
- return errors.New("post: topic is missing for symmetric encryption")
- }
- } else if args.Type == "asym" {
- kb := common.FromHex(args.Key)
- if kb == nil {
- return errors.New("post: public key for asymmetric encryption is invalid")
- }
- params.Dst = crypto.ToECDSAPub(kb)
- if !ValidatePublicKey(params.Dst) {
- return errors.New("post: public key for asymmetric encryption is invalid")
- }
- } else {
- return errors.New("post: wrong type (sym/asym)")
- }
- // encrypt and send
- message, err := NewSentMessage(¶ms)
- if err != nil {
- return err
- }
- envelope, err := message.Wrap(¶ms)
- if err != nil {
- return err
- }
- if envelope.size() > api.whisper.maxMsgLength {
- return errors.New("post: message is too big")
- }
- if len(args.TargetPeer) != 0 {
- n, err := discover.ParseNode(args.TargetPeer)
- if err != nil {
- return errors.New("post: failed to parse enode of target peer: " + err.Error())
- }
- return api.whisper.SendP2PMessage(n.ID[:], envelope)
- } else if args.PowTarget < api.whisper.minPoW {
- return errors.New("post: target PoW is less than minimum PoW, the message can not be sent")
- }
- return api.whisper.Send(envelope)
- }
- type PostArgs struct {
- Type string `json:"type"` // "sym"/"asym" (symmetric or asymmetric)
- TTL uint32 `json:"ttl"` // time-to-live in seconds
- Sig string `json:"sig"` // id of the signing key
- Key string `json:"key"` // key id (in case of sym) or public key (in case of asym)
- Topic hexutil.Bytes `json:"topic"` // topic (4 bytes)
- Padding hexutil.Bytes `json:"padding"` // optional padding bytes
- Payload hexutil.Bytes `json:"payload"` // payload to be encrypted
- PowTime uint32 `json:"powTime"` // maximal time in seconds to be spent on PoW
- PowTarget float64 `json:"powTarget"` // minimal PoW required for this message
- TargetPeer string `json:"targetPeer"` // peer id (for p2p message only)
- }
- type WhisperFilterArgs struct {
- Symmetric bool // encryption type
- Key string // id of the key to be used for decryption
- Sig string // public key of the sender to be verified
- MinPoW float64 // minimal PoW requirement
- Topics [][]byte // list of topics (up to 4 bytes each) to match
- AllowP2P bool // indicates wheather direct p2p messages are allowed for this filter
- }
- // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a
- // JSON message blob into a WhisperFilterArgs structure.
- func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) {
- // Unmarshal the JSON message and sanity check
- var obj struct {
- Type string `json:"type"`
- Key string `json:"key"`
- Sig string `json:"sig"`
- MinPoW float64 `json:"minPoW"`
- Topics []interface{} `json:"topics"`
- AllowP2P bool `json:"allowP2P"`
- }
- if err := json.Unmarshal(b, &obj); err != nil {
- return err
- }
- switch obj.Type {
- case "sym":
- args.Symmetric = true
- case "asym":
- args.Symmetric = false
- default:
- return errors.New("wrong type (sym/asym)")
- }
- args.Key = obj.Key
- args.Sig = obj.Sig
- args.MinPoW = obj.MinPoW
- args.AllowP2P = obj.AllowP2P
- // Construct the topic array
- if obj.Topics != nil {
- topics := make([]string, len(obj.Topics))
- for i, field := range obj.Topics {
- switch value := field.(type) {
- case string:
- topics[i] = value
- case nil:
- return fmt.Errorf("topic[%d] is empty", i)
- default:
- return fmt.Errorf("topic[%d] is not a string", i)
- }
- }
- topicsDecoded := make([][]byte, len(topics))
- for j, s := range topics {
- x := common.FromHex(s)
- if x == nil || len(x) > TopicLength {
- return fmt.Errorf("topic[%d] is invalid", j)
- }
- topicsDecoded[j] = x
- }
- args.Topics = topicsDecoded
- }
- return nil
- }
- // WhisperMessage is the RPC representation of a whisper message.
- type WhisperMessage struct {
- Topic string `json:"topic"`
- Payload string `json:"payload"`
- Padding string `json:"padding"`
- Src string `json:"sig"`
- Dst string `json:"recipientPublicKey"`
- Timestamp uint32 `json:"timestamp"`
- TTL uint32 `json:"ttl"`
- PoW float64 `json:"pow"`
- Hash string `json:"hash"`
- }
- // NewWhisperMessage converts an internal message into an API version.
- func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage {
- msg := WhisperMessage{
- Payload: common.ToHex(message.Payload),
- Padding: common.ToHex(message.Padding),
- Timestamp: message.Sent,
- TTL: message.TTL,
- PoW: message.PoW,
- Hash: common.ToHex(message.EnvelopeHash.Bytes()),
- }
- if len(message.Topic) == TopicLength {
- msg.Topic = common.ToHex(message.Topic[:])
- }
- if message.Dst != nil {
- b := crypto.FromECDSAPub(message.Dst)
- if b != nil {
- msg.Dst = common.ToHex(b)
- }
- }
- if isMessageSigned(message.Raw[0]) {
- b := crypto.FromECDSAPub(message.SigToPubKey())
- if b != nil {
- msg.Src = common.ToHex(b)
- }
- }
- return &msg
- }
|