aboutsummaryrefslogblamecommitdiffstats
path: root/whisper/whisperv5/api.go
blob: 1a4e4d87992d8b566df85daf837748a082194d7c (plain) (tree)
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/>.

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(&params)
    if err != nil {
        return err
    }
    envelope, err := message.Wrap(&params)
    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
}