github.com/MaynardMiner/ethereumprogpow@v1.8.23/swarm/pss/handshake.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // +build !nopsshandshake
    18  
    19  package pss
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/ethereumprogpow/ethereumprogpow/common"
    29  	"github.com/ethereumprogpow/ethereumprogpow/common/hexutil"
    30  	"github.com/ethereumprogpow/ethereumprogpow/crypto"
    31  	"github.com/ethereumprogpow/ethereumprogpow/p2p"
    32  	"github.com/ethereumprogpow/ethereumprogpow/rlp"
    33  	"github.com/ethereumprogpow/ethereumprogpow/rpc"
    34  	"github.com/ethereumprogpow/ethereumprogpow/swarm/log"
    35  )
    36  
    37  const (
    38  	IsActiveHandshake = true
    39  )
    40  
    41  var (
    42  	ctrlSingleton *HandshakeController
    43  )
    44  
    45  const (
    46  	defaultSymKeyRequestTimeout = 1000 * 8  // max wait ms to receive a response to a handshake symkey request
    47  	defaultSymKeyExpiryTimeout  = 1000 * 10 // ms to wait before allowing garbage collection of an expired symkey
    48  	defaultSymKeySendLimit      = 256       // amount of messages a symkey is valid for
    49  	defaultSymKeyCapacity       = 4         // max number of symkeys to store/send simultaneously
    50  )
    51  
    52  // symmetric key exchange message payload
    53  type handshakeMsg struct {
    54  	From    []byte
    55  	Limit   uint16
    56  	Keys    [][]byte
    57  	Request uint8
    58  	Topic   Topic
    59  }
    60  
    61  // internal representation of an individual symmetric key
    62  type handshakeKey struct {
    63  	symKeyID  *string
    64  	pubKeyID  *string
    65  	limit     uint16
    66  	count     uint16
    67  	expiredAt time.Time
    68  }
    69  
    70  // container for all in- and outgoing keys
    71  // for one particular peer (public key) and topic
    72  type handshake struct {
    73  	outKeys []handshakeKey
    74  	inKeys  []handshakeKey
    75  }
    76  
    77  // Initialization parameters for the HandshakeController
    78  //
    79  // SymKeyRequestExpiry: Timeout for waiting for a handshake reply
    80  // (default 8000 ms)
    81  //
    82  // SymKeySendLimit: Amount of messages symmetric keys issues by
    83  // this node is valid for (default 256)
    84  //
    85  // SymKeyCapacity: Ideal (and maximum) amount of symmetric keys
    86  // held per direction per peer (default 4)
    87  type HandshakeParams struct {
    88  	SymKeyRequestTimeout time.Duration
    89  	SymKeyExpiryTimeout  time.Duration
    90  	SymKeySendLimit      uint16
    91  	SymKeyCapacity       uint8
    92  }
    93  
    94  // Sane defaults for HandshakeController initialization
    95  func NewHandshakeParams() *HandshakeParams {
    96  	return &HandshakeParams{
    97  		SymKeyRequestTimeout: defaultSymKeyRequestTimeout * time.Millisecond,
    98  		SymKeyExpiryTimeout:  defaultSymKeyExpiryTimeout * time.Millisecond,
    99  		SymKeySendLimit:      defaultSymKeySendLimit,
   100  		SymKeyCapacity:       defaultSymKeyCapacity,
   101  	}
   102  }
   103  
   104  // Singleton object enabling semi-automatic Diffie-Hellman
   105  // exchange of ephemeral symmetric keys
   106  type HandshakeController struct {
   107  	pss                  *Pss
   108  	keyC                 map[string]chan []string // adds a channel to report when a handshake succeeds
   109  	lock                 sync.Mutex
   110  	symKeyRequestTimeout time.Duration
   111  	symKeyExpiryTimeout  time.Duration
   112  	symKeySendLimit      uint16
   113  	symKeyCapacity       uint8
   114  	symKeyIndex          map[string]*handshakeKey
   115  	handshakes           map[string]map[Topic]*handshake
   116  	deregisterFuncs      map[Topic]func()
   117  }
   118  
   119  // Attach HandshakeController to pss node
   120  //
   121  // Must be called before starting the pss node service
   122  func SetHandshakeController(pss *Pss, params *HandshakeParams) error {
   123  	ctrl := &HandshakeController{
   124  		pss:                  pss,
   125  		keyC:                 make(map[string]chan []string),
   126  		symKeyRequestTimeout: params.SymKeyRequestTimeout,
   127  		symKeyExpiryTimeout:  params.SymKeyExpiryTimeout,
   128  		symKeySendLimit:      params.SymKeySendLimit,
   129  		symKeyCapacity:       params.SymKeyCapacity,
   130  		symKeyIndex:          make(map[string]*handshakeKey),
   131  		handshakes:           make(map[string]map[Topic]*handshake),
   132  		deregisterFuncs:      make(map[Topic]func()),
   133  	}
   134  	api := &HandshakeAPI{
   135  		namespace: "pss",
   136  		ctrl:      ctrl,
   137  	}
   138  	pss.addAPI(rpc.API{
   139  		Namespace: api.namespace,
   140  		Version:   "0.2",
   141  		Service:   api,
   142  		Public:    true,
   143  	})
   144  	ctrlSingleton = ctrl
   145  	return nil
   146  }
   147  
   148  // Return all unexpired symmetric keys from store by
   149  // peer (public key), topic and specified direction
   150  func (ctl *HandshakeController) validKeys(pubkeyid string, topic *Topic, in bool) (validkeys []*string) {
   151  	ctl.lock.Lock()
   152  	defer ctl.lock.Unlock()
   153  	now := time.Now()
   154  	if _, ok := ctl.handshakes[pubkeyid]; !ok {
   155  		return []*string{}
   156  	} else if _, ok := ctl.handshakes[pubkeyid][*topic]; !ok {
   157  		return []*string{}
   158  	}
   159  	var keystore *[]handshakeKey
   160  	if in {
   161  		keystore = &(ctl.handshakes[pubkeyid][*topic].inKeys)
   162  	} else {
   163  		keystore = &(ctl.handshakes[pubkeyid][*topic].outKeys)
   164  	}
   165  
   166  	for _, key := range *keystore {
   167  		if key.limit <= key.count {
   168  			ctl.releaseKey(*key.symKeyID, topic)
   169  		} else if !key.expiredAt.IsZero() && key.expiredAt.Before(now) {
   170  			ctl.releaseKey(*key.symKeyID, topic)
   171  		} else {
   172  			validkeys = append(validkeys, key.symKeyID)
   173  		}
   174  	}
   175  	return
   176  }
   177  
   178  // Add all given symmetric keys with validity limits to store by
   179  // peer (public key), topic and specified direction
   180  func (ctl *HandshakeController) updateKeys(pubkeyid string, topic *Topic, in bool, symkeyids []string, limit uint16) {
   181  	ctl.lock.Lock()
   182  	defer ctl.lock.Unlock()
   183  	if _, ok := ctl.handshakes[pubkeyid]; !ok {
   184  		ctl.handshakes[pubkeyid] = make(map[Topic]*handshake)
   185  
   186  	}
   187  	if ctl.handshakes[pubkeyid][*topic] == nil {
   188  		ctl.handshakes[pubkeyid][*topic] = &handshake{}
   189  	}
   190  	var keystore *[]handshakeKey
   191  	expire := time.Now()
   192  	if in {
   193  		keystore = &(ctl.handshakes[pubkeyid][*topic].inKeys)
   194  	} else {
   195  		keystore = &(ctl.handshakes[pubkeyid][*topic].outKeys)
   196  		expire = expire.Add(time.Millisecond * ctl.symKeyExpiryTimeout)
   197  	}
   198  	for _, storekey := range *keystore {
   199  		storekey.expiredAt = expire
   200  	}
   201  	for i := 0; i < len(symkeyids); i++ {
   202  		storekey := handshakeKey{
   203  			symKeyID: &symkeyids[i],
   204  			pubKeyID: &pubkeyid,
   205  			limit:    limit,
   206  		}
   207  		*keystore = append(*keystore, storekey)
   208  		ctl.pss.symKeyPool[*storekey.symKeyID][*topic].protected = true
   209  	}
   210  	for i := 0; i < len(*keystore); i++ {
   211  		ctl.symKeyIndex[*(*keystore)[i].symKeyID] = &((*keystore)[i])
   212  	}
   213  }
   214  
   215  // Expire a symmetric key, making it elegible for garbage collection
   216  func (ctl *HandshakeController) releaseKey(symkeyid string, topic *Topic) bool {
   217  	if ctl.symKeyIndex[symkeyid] == nil {
   218  		log.Debug("no symkey", "symkeyid", symkeyid)
   219  		return false
   220  	}
   221  	ctl.symKeyIndex[symkeyid].expiredAt = time.Now()
   222  	log.Debug("handshake release", "symkeyid", symkeyid)
   223  	return true
   224  }
   225  
   226  // Checks all symmetric keys in given direction(s) by
   227  // specified peer (public key) and topic for expiry.
   228  // Expired means:
   229  // - expiry timestamp is set, and grace period is exceeded
   230  // - message validity limit is reached
   231  func (ctl *HandshakeController) cleanHandshake(pubkeyid string, topic *Topic, in bool, out bool) int {
   232  	ctl.lock.Lock()
   233  	defer ctl.lock.Unlock()
   234  	var deletecount int
   235  	var deletes []string
   236  	now := time.Now()
   237  	handshake := ctl.handshakes[pubkeyid][*topic]
   238  	log.Debug("handshake clean", "pubkey", pubkeyid, "topic", topic)
   239  	if in {
   240  		for i, key := range handshake.inKeys {
   241  			if key.expiredAt.Before(now) || (key.expiredAt.IsZero() && key.limit <= key.count) {
   242  				log.Trace("handshake in clean remove", "symkeyid", *key.symKeyID)
   243  				deletes = append(deletes, *key.symKeyID)
   244  				handshake.inKeys[deletecount] = handshake.inKeys[i]
   245  				deletecount++
   246  			}
   247  		}
   248  		handshake.inKeys = handshake.inKeys[:len(handshake.inKeys)-deletecount]
   249  	}
   250  	if out {
   251  		deletecount = 0
   252  		for i, key := range handshake.outKeys {
   253  			if key.expiredAt.Before(now) && (key.expiredAt.IsZero() && key.limit <= key.count) {
   254  				log.Trace("handshake out clean remove", "symkeyid", *key.symKeyID)
   255  				deletes = append(deletes, *key.symKeyID)
   256  				handshake.outKeys[deletecount] = handshake.outKeys[i]
   257  				deletecount++
   258  			}
   259  		}
   260  		handshake.outKeys = handshake.outKeys[:len(handshake.outKeys)-deletecount]
   261  	}
   262  	for _, keyid := range deletes {
   263  		delete(ctl.symKeyIndex, keyid)
   264  		ctl.pss.symKeyPool[keyid][*topic].protected = false
   265  	}
   266  	return len(deletes)
   267  }
   268  
   269  // Runs cleanHandshake() on all peers and topics
   270  func (ctl *HandshakeController) clean() {
   271  	peerpubkeys := ctl.handshakes
   272  	for pubkeyid, peertopics := range peerpubkeys {
   273  		for topic := range peertopics {
   274  			ctl.cleanHandshake(pubkeyid, &topic, true, true)
   275  		}
   276  	}
   277  }
   278  
   279  // Passed as a PssMsg handler for the topic handshake is activated on
   280  // Handles incoming key exchange messages and
   281  // ccunts message usage by symmetric key (expiry limit control)
   282  // Only returns error if key handler fails
   283  func (ctl *HandshakeController) handler(msg []byte, p *p2p.Peer, asymmetric bool, symkeyid string) error {
   284  	if !asymmetric {
   285  		if ctl.symKeyIndex[symkeyid] != nil {
   286  			if ctl.symKeyIndex[symkeyid].count >= ctl.symKeyIndex[symkeyid].limit {
   287  				return fmt.Errorf("discarding message using expired key: %s", symkeyid)
   288  			}
   289  			ctl.symKeyIndex[symkeyid].count++
   290  			log.Trace("increment symkey recv use", "symsymkeyid", symkeyid, "count", ctl.symKeyIndex[symkeyid].count, "limit", ctl.symKeyIndex[symkeyid].limit, "receiver", common.ToHex(crypto.FromECDSAPub(ctl.pss.PublicKey())))
   291  		}
   292  		return nil
   293  	}
   294  	keymsg := &handshakeMsg{}
   295  	err := rlp.DecodeBytes(msg, keymsg)
   296  	if err == nil {
   297  		err := ctl.handleKeys(symkeyid, keymsg)
   298  		if err != nil {
   299  			log.Error("handlekeys fail", "error", err)
   300  		}
   301  		return err
   302  	}
   303  	return nil
   304  }
   305  
   306  // Handle incoming key exchange message
   307  // Add keys received from peer to store
   308  // and enerate and send the amount of keys requested by peer
   309  //
   310  // TODO:
   311  // - flood guard
   312  // - keylength check
   313  // - update address hint if:
   314  //   1) leftmost bytes in new address do not match stored
   315  //   2) else, if new address is longer
   316  func (ctl *HandshakeController) handleKeys(pubkeyid string, keymsg *handshakeMsg) error {
   317  	// new keys from peer
   318  	if len(keymsg.Keys) > 0 {
   319  		log.Debug("received handshake keys", "pubkeyid", pubkeyid, "from", keymsg.From, "count", len(keymsg.Keys))
   320  		var sendsymkeyids []string
   321  		for _, key := range keymsg.Keys {
   322  			sendsymkey := make([]byte, len(key))
   323  			copy(sendsymkey, key)
   324  			sendsymkeyid, err := ctl.pss.setSymmetricKey(sendsymkey, keymsg.Topic, PssAddress(keymsg.From), false, false)
   325  			if err != nil {
   326  				return err
   327  			}
   328  			sendsymkeyids = append(sendsymkeyids, sendsymkeyid)
   329  		}
   330  		if len(sendsymkeyids) > 0 {
   331  			ctl.updateKeys(pubkeyid, &keymsg.Topic, false, sendsymkeyids, keymsg.Limit)
   332  
   333  			ctl.alertHandshake(pubkeyid, sendsymkeyids)
   334  		}
   335  	}
   336  
   337  	// peer request for keys
   338  	if keymsg.Request > 0 {
   339  		_, err := ctl.sendKey(pubkeyid, &keymsg.Topic, keymsg.Request)
   340  		if err != nil {
   341  			return err
   342  		}
   343  	}
   344  
   345  	return nil
   346  }
   347  
   348  // Send key exchange to peer (public key) valid for `topic`
   349  // Will send number of keys specified by `keycount` with
   350  // validity limits specified in `msglimit`
   351  // If number of valid outgoing keys is less than the ideal/max
   352  // amount, a request is sent for the amount of keys to make up
   353  // the difference
   354  func (ctl *HandshakeController) sendKey(pubkeyid string, topic *Topic, keycount uint8) ([]string, error) {
   355  
   356  	var requestcount uint8
   357  	to := PssAddress{}
   358  	if _, ok := ctl.pss.pubKeyPool[pubkeyid]; !ok {
   359  		return []string{}, errors.New("Invalid public key")
   360  	} else if psp, ok := ctl.pss.pubKeyPool[pubkeyid][*topic]; ok {
   361  		to = psp.address
   362  	}
   363  
   364  	recvkeys := make([][]byte, keycount)
   365  	recvkeyids := make([]string, keycount)
   366  	ctl.lock.Lock()
   367  	if _, ok := ctl.handshakes[pubkeyid]; !ok {
   368  		ctl.handshakes[pubkeyid] = make(map[Topic]*handshake)
   369  	}
   370  	ctl.lock.Unlock()
   371  
   372  	// check if buffer is not full
   373  	outkeys := ctl.validKeys(pubkeyid, topic, false)
   374  	if len(outkeys) < int(ctl.symKeyCapacity) {
   375  		//requestcount = uint8(self.symKeyCapacity - uint8(len(outkeys)))
   376  		requestcount = ctl.symKeyCapacity
   377  	}
   378  	// return if there's nothing to be accomplished
   379  	if requestcount == 0 && keycount == 0 {
   380  		return []string{}, nil
   381  	}
   382  
   383  	// generate new keys to send
   384  	for i := 0; i < len(recvkeyids); i++ {
   385  		var err error
   386  		recvkeyids[i], err = ctl.pss.GenerateSymmetricKey(*topic, to, true)
   387  		if err != nil {
   388  			return []string{}, fmt.Errorf("set receive symkey fail (pubkey %x topic %x): %v", pubkeyid, topic, err)
   389  		}
   390  		recvkeys[i], err = ctl.pss.GetSymmetricKey(recvkeyids[i])
   391  		if err != nil {
   392  			return []string{}, fmt.Errorf("GET Generated outgoing symkey fail (pubkey %x topic %x): %v", pubkeyid, topic, err)
   393  		}
   394  	}
   395  	ctl.updateKeys(pubkeyid, topic, true, recvkeyids, ctl.symKeySendLimit)
   396  
   397  	// encode and send the message
   398  	recvkeymsg := &handshakeMsg{
   399  		From:    ctl.pss.BaseAddr(),
   400  		Keys:    recvkeys,
   401  		Request: requestcount,
   402  		Limit:   ctl.symKeySendLimit,
   403  		Topic:   *topic,
   404  	}
   405  	log.Debug("sending our symkeys", "pubkey", pubkeyid, "symkeys", recvkeyids, "limit", ctl.symKeySendLimit, "requestcount", requestcount, "keycount", len(recvkeys))
   406  	recvkeybytes, err := rlp.EncodeToBytes(recvkeymsg)
   407  	if err != nil {
   408  		return []string{}, fmt.Errorf("rlp keymsg encode fail: %v", err)
   409  	}
   410  	// if the send fails it means this public key is not registered for this particular address AND topic
   411  	err = ctl.pss.SendAsym(pubkeyid, *topic, recvkeybytes)
   412  	if err != nil {
   413  		return []string{}, fmt.Errorf("Send symkey failed: %v", err)
   414  	}
   415  	return recvkeyids, nil
   416  }
   417  
   418  // Enables callback for keys received from a key exchange request
   419  func (ctl *HandshakeController) alertHandshake(pubkeyid string, symkeys []string) chan []string {
   420  	if len(symkeys) > 0 {
   421  		if _, ok := ctl.keyC[pubkeyid]; ok {
   422  			ctl.keyC[pubkeyid] <- symkeys
   423  			close(ctl.keyC[pubkeyid])
   424  			delete(ctl.keyC, pubkeyid)
   425  		}
   426  		return nil
   427  	}
   428  	if _, ok := ctl.keyC[pubkeyid]; !ok {
   429  		ctl.keyC[pubkeyid] = make(chan []string)
   430  	}
   431  	return ctl.keyC[pubkeyid]
   432  }
   433  
   434  type HandshakeAPI struct {
   435  	namespace string
   436  	ctrl      *HandshakeController
   437  }
   438  
   439  // Initiate a handshake session for a peer (public key) and topic
   440  // combination.
   441  //
   442  // If `sync` is set, the call will block until keys are received from peer,
   443  // or if the handshake request times out
   444  //
   445  // If `flush` is set, the max amount of keys will be sent to the peer
   446  // regardless of how many valid keys that currently exist in the store.
   447  //
   448  // Returns list of symmetric key ids that can be passed to pss.GetSymmetricKey()
   449  // for retrieval of the symmetric key bytes themselves.
   450  //
   451  // Fails if the incoming symmetric key store is already full (and `flush` is false),
   452  // or if the underlying key dispatcher fails
   453  func (api *HandshakeAPI) Handshake(pubkeyid string, topic Topic, sync bool, flush bool) (keys []string, err error) {
   454  	var hsc chan []string
   455  	var keycount uint8
   456  	if flush {
   457  		keycount = api.ctrl.symKeyCapacity
   458  	} else {
   459  		validkeys := api.ctrl.validKeys(pubkeyid, &topic, false)
   460  		keycount = api.ctrl.symKeyCapacity - uint8(len(validkeys))
   461  	}
   462  	if keycount == 0 {
   463  		return keys, errors.New("Incoming symmetric key store is already full")
   464  	}
   465  	if sync {
   466  		hsc = api.ctrl.alertHandshake(pubkeyid, []string{})
   467  	}
   468  	_, err = api.ctrl.sendKey(pubkeyid, &topic, keycount)
   469  	if err != nil {
   470  		return keys, err
   471  	}
   472  	if sync {
   473  		ctx, cancel := context.WithTimeout(context.Background(), api.ctrl.symKeyRequestTimeout)
   474  		defer cancel()
   475  		select {
   476  		case keys = <-hsc:
   477  			log.Trace("sync handshake response receive", "key", keys)
   478  		case <-ctx.Done():
   479  			return []string{}, errors.New("timeout")
   480  		}
   481  	}
   482  	return keys, nil
   483  }
   484  
   485  // Activate handshake functionality on a topic
   486  func (api *HandshakeAPI) AddHandshake(topic Topic) error {
   487  	api.ctrl.deregisterFuncs[topic] = api.ctrl.pss.Register(&topic, NewHandler(api.ctrl.handler))
   488  	return nil
   489  }
   490  
   491  // Deactivate handshake functionality on a topic
   492  func (api *HandshakeAPI) RemoveHandshake(topic *Topic) error {
   493  	if _, ok := api.ctrl.deregisterFuncs[*topic]; ok {
   494  		api.ctrl.deregisterFuncs[*topic]()
   495  	}
   496  	return nil
   497  }
   498  
   499  // Returns all valid symmetric keys in store per peer (public key)
   500  // and topic.
   501  //
   502  // The `in` and `out` parameters indicate for which direction(s)
   503  // symmetric keys will be returned.
   504  // If both are false, no keys (and no error) will be returned.
   505  func (api *HandshakeAPI) GetHandshakeKeys(pubkeyid string, topic Topic, in bool, out bool) (keys []string, err error) {
   506  	if in {
   507  		for _, inkey := range api.ctrl.validKeys(pubkeyid, &topic, true) {
   508  			keys = append(keys, *inkey)
   509  		}
   510  	}
   511  	if out {
   512  		for _, outkey := range api.ctrl.validKeys(pubkeyid, &topic, false) {
   513  			keys = append(keys, *outkey)
   514  		}
   515  	}
   516  	return keys, nil
   517  }
   518  
   519  // Returns the amount of messages the specified symmetric key
   520  // is still valid for under the handshake scheme
   521  func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error) {
   522  	storekey := api.ctrl.symKeyIndex[symkeyid]
   523  	if storekey == nil {
   524  		return 0, fmt.Errorf("invalid symkey id %s", symkeyid)
   525  	}
   526  	return storekey.limit - storekey.count, nil
   527  }
   528  
   529  // Returns the byte representation of the public key in ascii hex
   530  // associated with the given symmetric key
   531  func (api *HandshakeAPI) GetHandshakePublicKey(symkeyid string) (string, error) {
   532  	storekey := api.ctrl.symKeyIndex[symkeyid]
   533  	if storekey == nil {
   534  		return "", fmt.Errorf("invalid symkey id %s", symkeyid)
   535  	}
   536  	return *storekey.pubKeyID, nil
   537  }
   538  
   539  // Manually expire the given symkey
   540  //
   541  // If `flush` is set, garbage collection will be performed before returning.
   542  //
   543  // Returns true on successful removal, false otherwise
   544  func (api *HandshakeAPI) ReleaseHandshakeKey(pubkeyid string, topic Topic, symkeyid string, flush bool) (removed bool, err error) {
   545  	removed = api.ctrl.releaseKey(symkeyid, &topic)
   546  	if removed && flush {
   547  		api.ctrl.cleanHandshake(pubkeyid, &topic, true, true)
   548  	}
   549  	return
   550  }
   551  
   552  // Send symmetric message under the handshake scheme
   553  //
   554  // Overloads the pss.SendSym() API call, adding symmetric key usage count
   555  // for message expiry control
   556  func (api *HandshakeAPI) SendSym(symkeyid string, topic Topic, msg hexutil.Bytes) (err error) {
   557  	err = api.ctrl.pss.SendSym(symkeyid, topic, msg[:])
   558  	if api.ctrl.symKeyIndex[symkeyid] != nil {
   559  		if api.ctrl.symKeyIndex[symkeyid].count >= api.ctrl.symKeyIndex[symkeyid].limit {
   560  			return errors.New("attempted send with expired key")
   561  		}
   562  		api.ctrl.symKeyIndex[symkeyid].count++
   563  		log.Trace("increment symkey send use", "symkeyid", symkeyid, "count", api.ctrl.symKeyIndex[symkeyid].count, "limit", api.ctrl.symKeyIndex[symkeyid].limit, "receiver", common.ToHex(crypto.FromECDSAPub(api.ctrl.pss.PublicKey())))
   564  	}
   565  	return err
   566  }