github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/whisper/whisperv6/api.go (about)

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