Skip to content
Snippets Groups Projects
Select Git revision
  • 18a4efddd35b2ccc9e861334d05a86b4412a26ee
  • release default protected
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • hotfix/TestHostPool_UpdateNdf_AddFilter
  • XX-4719/announcementChannels
  • xx-4717/logLevel
  • jonah/noob-channel
  • master protected
  • XX-4707/tagDiskJson
  • xx-4698/notification-retry
  • hotfix/notifylockup
  • syncNodes
  • hotfix/localCB
  • XX-4677/NewChanManagerMobile
  • XX-4689/DmSync
  • duplicatePrefix
  • XX-4601/HavenInvites
  • finalizedUICallbacks
  • XX-4673/AdminKeySync
  • debugNotifID
  • anne/test
  • v4.7.5
  • v4.7.4
  • v4.7.3
  • v4.7.2
  • v4.7.1
  • v4.6.3
  • v4.6.1
  • v4.5.0
  • v4.4.4
  • v4.3.11
  • v4.3.8
  • v4.3.7
  • v4.3.6
  • v4.3.5
  • v4.2.0
  • v4.3.0
  • v4.3.4
  • v4.3.3
  • v4.3.2
  • v4.3.1
41 results

session.go

  • user avatar
    18a4efdd
    History
    session.go 20.21 KiB
    ///////////////////////////////////////////////////////////////////////////////
    // Copyright © 2020 xx network SEZC                                          //
    //                                                                           //
    // Use of this source code is governed by a license that can be found in the //
    // LICENSE file                                                              //
    ///////////////////////////////////////////////////////////////////////////////
    
    package e2e
    
    import (
    	"encoding/json"
    	"fmt"
    	"github.com/cloudflare/circl/dh/sidh"
    	"github.com/pkg/errors"
    	jww "github.com/spf13/jwalterweatherman"
    	"gitlab.com/elixxir/client/interfaces/params"
    	"gitlab.com/elixxir/client/storage/utility"
    	"gitlab.com/elixxir/client/storage/versioned"
    	"gitlab.com/elixxir/crypto/cyclic"
    	dh "gitlab.com/elixxir/crypto/diffieHellman"
    	"gitlab.com/elixxir/crypto/hash"
    	"gitlab.com/xx_network/crypto/randomness"
    	"gitlab.com/xx_network/primitives/id"
    	"gitlab.com/xx_network/primitives/netTime"
    	"math"
    	"math/big"
    	"sync"
    	"testing"
    )
    
    const currentSessionVersion = 0
    const sessionPrefix = "session{ID:%s}"
    const sessionKey = "session"
    
    type Session struct {
    	//pointer to relationship
    	relationship *relationship
    	//prefixed kv
    	kv *versioned.KV
    	//params
    	e2eParams params.E2ESessionParams
    
    	partner *id.ID
    
    	//type
    	t RelationshipType
    
    	// Underlying key
    	baseKey *cyclic.Int
    	// Own Private Key
    	myPrivKey *cyclic.Int
    	// Partner Public Key
    	partnerPubKey *cyclic.Int
    
    	// SIDH Keys of the same
    	mySIDHPrivKey     *sidh.PrivateKey
    	partnerSIDHPubKey *sidh.PublicKey
    
    	// ID of the session which teh partner public key comes from for this
    	// sessions creation.  Shares a partner public key if a Send session,
    	// shares a myPrivateKey if a Receive session
    	partnerSource SessionID
    	//fingerprint of relationship
    	relationshipFingerprint []byte
    
    	//denotes if the other party has confirmed this key
    	negotiationStatus Negotiation
    
    	// Number of keys used before the system attempts a rekey
    	rekeyThreshold uint32
    
    	// Received Keys dirty bits
    	// Each bit represents a single Key
    	keyState *utility.StateVector
    
    	//mutex
    	mux sync.RWMutex
    }
    
    // As this is serialized by json, any field that should be serialized
    // must be exported
    // Utility struct to write part of session data to disk
    type SessionDisk struct {
    	E2EParams params.E2ESessionParams
    
    	//session type
    	Type uint8
    
    	// Underlying key
    	BaseKey []byte
    	// Own Private Key
    	MyPrivKey []byte
    	// Partner Public Key
    	PartnerPubKey []byte
    	// Own SIDH Private Key
    	MySIDHPrivKey []byte
    	// Note: only 3 bit patterns: 001, 010, 100
    	MySIDHVariant byte
    	// Partner SIDH Public Key
    	PartnerSIDHPubKey []byte
    	// Note: only 3 bit patterns: 001, 010, 100
    	PartnerSIDHVariant byte
    
    	// ID of the session which triggered this sessions creation.
    	Trigger []byte
    	// relationship fp
    	RelationshipFingerprint []byte
    
    	//denotes if the other party has confirmed this key
    	Confirmation uint8
    
    	// Number of keys usable before rekey
    	RekeyThreshold uint32
    
    	Partner []byte
    }
    
    /*CONSTRUCTORS*/
    //Generator which creates all keys and structures
    func newSession(ship *relationship, t RelationshipType, myPrivKey, partnerPubKey,
    	baseKey *cyclic.Int, mySIDHPrivKey *sidh.PrivateKey,
    	partnerSIDHPubKey *sidh.PublicKey, trigger SessionID,
    	relationshipFingerprint []byte, negotiationStatus Negotiation,
    	e2eParams params.E2ESessionParams) *Session {
    
    	if e2eParams.MinKeys < 10 {
    		jww.FATAL.Panicf("Cannot create a session with a minimum "+
    			"number of keys (%d) less than 10", e2eParams.MinKeys)
    	}
    
    	session := &Session{
    		e2eParams:               e2eParams,
    		relationship:            ship,
    		t:                       t,
    		myPrivKey:               myPrivKey,
    		partnerPubKey:           partnerPubKey,
    		mySIDHPrivKey:           mySIDHPrivKey,
    		partnerSIDHPubKey:       partnerSIDHPubKey,
    		baseKey:                 baseKey,
    		relationshipFingerprint: relationshipFingerprint,
    		negotiationStatus:       negotiationStatus,
    		partnerSource:           trigger,
    		partner:                 ship.manager.partner.DeepCopy(),
    	}
    
    	session.kv = session.generate(ship.kv)
    
    	grp := session.relationship.manager.ctx.grp
    	myPubKey := dh.GeneratePublicKey(session.myPrivKey, grp)
    
    	jww.INFO.Printf("New Session with Partner %s:\n\tType: %s"+
    		"\n\tBaseKey: %s\n\tRelationship Fingerprint: %v\n\tNumKeys: %d"+
    		"\n\tMy Public Key: %s\n\tPartner Public Key: %s"+
    		"\n\tMy Public SIDH: %s\n\tPartner Public SIDH: %s",
    		ship.manager.partner,
    		t,
    		session.baseKey.TextVerbose(16, 0),
    		session.relationshipFingerprint,
    		session.rekeyThreshold,
    		myPubKey.TextVerbose(16, 0),
    		session.partnerPubKey.TextVerbose(16, 0),
    		utility.StringSIDHPrivKey(session.mySIDHPrivKey),
    		utility.StringSIDHPubKey(session.partnerSIDHPubKey))
    
    	err := session.save()
    	if err != nil {
    		jww.FATAL.Printf("Failed to make new session for Partner %s: %s",
    			ship.manager.partner, err)
    	}
    
    	return session
    }
    
    // Load session and state vector from kv and populate runtime fields
    func loadSession(ship *relationship, kv *versioned.KV,
    	relationshipFingerprint []byte) (*Session, error) {
    
    	session := Session{
    		relationship: ship,
    		kv:           kv,
    	}
    
    	obj, err := kv.Get(sessionKey, currentSessionVersion)
    	if err != nil {
    		return nil, errors.WithMessagef(err, "Failed to load %s",
    			kv.GetFullKey(sessionKey, currentSessionVersion))
    	}
    
    	// TODO: Not necessary until we have versions on this object...
    	//obj, err := sessionUpgradeTable.Upgrade(obj)
    
    	err = session.unmarshal(obj.Data)
    	if err != nil {
    		return nil, err
    	}
    
    	if session.t == Receive {
    		// register key fingerprints
    		ship.manager.ctx.fa.add(session.getUnusedKeys())
    	}
    	session.relationshipFingerprint = relationshipFingerprint
    
    	if !session.partner.Cmp(ship.manager.partner) {
    		return nil, errors.Errorf("Stored partner (%s) did not match "+
    			"relationship partner (%s)", session.partner, ship.manager.partner)
    	}
    
    	return &session, nil
    }
    
    func (s *Session) save() error {
    
    	now := netTime.Now()
    
    	data, err := s.marshal()
    	if err != nil {
    		return err
    	}
    
    	obj := versioned.Object{
    		Version:   currentSessionVersion,
    		Timestamp: now,
    		Data:      data,
    	}
    
    	return s.kv.Set(sessionKey, currentSessionVersion, &obj)
    }
    
    /*METHODS*/
    // Done all unused key fingerprints
    
    // Delete removes this session and its key states from the storage
    func (s *Session) Delete() {
    	s.mux.Lock()
    	defer s.mux.Unlock()
    
    	s.relationship.manager.ctx.fa.remove(s.getUnusedKeys())
    
    	stateVectorErr := s.keyState.Delete()
    	sessionErr := s.kv.Delete(sessionKey, currentSessionVersion)
    
    	if stateVectorErr != nil && sessionErr != nil {
    		jww.ERROR.Printf("Error deleting state vector %s: %v", s.keyState, stateVectorErr.Error())
    		jww.ERROR.Panicf("Error deleting session with key %v: %v", sessionKey, sessionErr)
    	} else if sessionErr != nil {
    		jww.ERROR.Panicf("Error deleting session with key %v: %v", sessionKey, sessionErr)
    	} else if stateVectorErr != nil {
    		jww.ERROR.Panicf("Error deleting state vector %s: %v", s.keyState, stateVectorErr.Error())
    	}
    }
    
    // GetBaseKey retrieves the base key.
    func (s *Session) GetBaseKey() *cyclic.Int {
    	// no lock is needed because this cannot be edited
    	return s.baseKey.DeepCopy()
    }
    
    func (s *Session) GetMyPrivKey() *cyclic.Int {
    	// no lock is needed because this cannot be edited
    	return s.myPrivKey.DeepCopy()
    }
    
    func (s *Session) GetPartnerPubKey() *cyclic.Int {
    	// no lock is needed because this cannot be edited
    	return s.partnerPubKey.DeepCopy()
    }
    
    func (s *Session) GetMySIDHPrivKey() *sidh.PrivateKey {
    	// no lock is needed because this should never be edited
    	return s.mySIDHPrivKey
    }
    
    func (s *Session) GetPartnerSIDHPubKey() *sidh.PublicKey {
    	// no lock is needed because this should never be edited
    	return s.partnerSIDHPubKey
    }
    
    func (s *Session) GetSource() SessionID {
    	// no lock is needed because this cannot be edited
    	return s.partnerSource
    }
    
    //underlying definition of session id
    func getSessionIDFromBaseKey(baseKey *cyclic.Int) SessionID {
    	// no lock is needed because this cannot be edited
    	sid := SessionID{}
    	h, _ := hash.NewCMixHash()
    	h.Write(baseKey.Bytes())
    	copy(sid[:], h.Sum(nil))
    	return sid
    }
    
    //underlying definition of session id
    // FOR TESTING PURPOSES ONLY
    func GetSessionIDFromBaseKeyForTesting(baseKey *cyclic.Int, i interface{}) SessionID {
    	switch i.(type) {
    	case *testing.T, *testing.M, *testing.B, *testing.PB:
    		break
    	default:
    		jww.FATAL.Panicf("GetSessionIDFromBaseKeyForTesting is restricted to testing only. Got %T", i)
    	}
    	return getSessionIDFromBaseKey(baseKey)
    }
    
    //Blake2B hash of base key used for storage
    func (s *Session) GetID() SessionID {
    	return getSessionIDFromBaseKey(s.baseKey)
    }
    
    // returns the ID of the partner for this session
    func (s *Session) GetPartner() *id.ID {
    	if s.relationship != nil {
    		return s.relationship.manager.partner.DeepCopy()
    	} else {
    		return nil
    	}
    }
    
    //ekv functions
    func (s *Session) marshal() ([]byte, error) {
    	sd := SessionDisk{}
    
    	sd.E2EParams = s.e2eParams
    	sd.Type = uint8(s.t)
    	sd.BaseKey = s.baseKey.Bytes()
    	sd.MyPrivKey = s.myPrivKey.Bytes()
    	sd.PartnerPubKey = s.partnerPubKey.Bytes()
    	sd.MySIDHPrivKey = make([]byte, s.mySIDHPrivKey.Size())
    	sd.PartnerSIDHPubKey = make([]byte, s.partnerSIDHPubKey.Size())
    
    	s.mySIDHPrivKey.Export(sd.MySIDHPrivKey)
    	sd.MySIDHVariant = byte(s.mySIDHPrivKey.Variant())
    
    	s.partnerSIDHPubKey.Export(sd.PartnerSIDHPubKey)
    	sd.PartnerSIDHVariant = byte(s.partnerSIDHPubKey.Variant())
    
    	sd.Trigger = s.partnerSource[:]
    	sd.RelationshipFingerprint = s.relationshipFingerprint
    	sd.Partner = s.partner.Bytes()
    
    	// assume in progress confirmations and session creations have failed on
    	// reset, therefore do not store their pending progress
    	if s.negotiationStatus == Sending {
    		sd.Confirmation = uint8(Unconfirmed)
    	} else if s.negotiationStatus == NewSessionTriggered {
    		sd.Confirmation = uint8(Confirmed)
    	} else {
    		sd.Confirmation = uint8(s.negotiationStatus)
    	}
    
    	sd.RekeyThreshold = s.rekeyThreshold
    
    	return json.Marshal(&sd)
    }
    
    func (s *Session) unmarshal(b []byte) error {
    
    	sd := SessionDisk{}
    
    	err := json.Unmarshal(b, &sd)
    
    	if err != nil {
    		return err
    	}
    
    	grp := s.relationship.manager.ctx.grp
    
    	s.e2eParams = sd.E2EParams
    	s.t = RelationshipType(sd.Type)
    	s.baseKey = grp.NewIntFromBytes(sd.BaseKey)
    	s.myPrivKey = grp.NewIntFromBytes(sd.MyPrivKey)
    	s.partnerPubKey = grp.NewIntFromBytes(sd.PartnerPubKey)
    
    	mySIDHVariant := sidh.KeyVariant(sd.MySIDHVariant)
    	s.mySIDHPrivKey = utility.NewSIDHPrivateKey(mySIDHVariant)
    	err = s.mySIDHPrivKey.Import(sd.MySIDHPrivKey)
    	if err != nil {
    		return err
    	}
    
    	partnerSIDHVariant := sidh.KeyVariant(sd.PartnerSIDHVariant)
    	s.partnerSIDHPubKey = utility.NewSIDHPublicKey(partnerSIDHVariant)
    	err = s.partnerSIDHPubKey.Import(sd.PartnerSIDHPubKey)
    	if err != nil {
    		return err
    	}
    
    	s.negotiationStatus = Negotiation(sd.Confirmation)
    	s.rekeyThreshold = sd.RekeyThreshold
    	s.relationshipFingerprint = sd.RelationshipFingerprint
    	s.partner, _ = id.Unmarshal(sd.Partner)
    	copy(s.partnerSource[:], sd.Trigger)
    
    	s.keyState, err = utility.LoadStateVector(s.kv, "")
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
    
    //key usage
    // Pops the first unused key, skipping any which are denoted as used.
    // will return if the remaining keys are designated as rekeys
    func (s *Session) PopKey() (*Key, error) {
    	if s.keyState.GetNumAvailable() <= uint32(s.e2eParams.NumRekeys) {
    		return nil, errors.New("no more keys left, remaining reserved " +
    			"for rekey")
    	}
    	keyNum, err := s.keyState.Next()
    	if err != nil {
    		return nil, err
    	}
    
    	return newKey(s, keyNum), nil
    }
    
    func (s *Session) PopReKey() (*Key, error) {
    	keyNum, err := s.keyState.Next()
    	if err != nil {
    		return nil, err
    	}
    
    	return newKey(s, keyNum), nil
    }
    
    func (s *Session) GetRelationshipFingerprint() []byte {
    	return s.relationshipFingerprint
    }
    
    // returns the state of the session, which denotes if the Session is active,
    // functional but in need of a rekey, empty of Send key, or empty of rekeys
    func (s *Session) Status() Status {
    	// copy the num available so it stays consistent as this function does its
    	// checks
    	numAvailable := s.keyState.GetNumAvailable()
    	numUsed := s.keyState.GetNumUsed()
    
    	if numAvailable == 0 {
    		return RekeyEmpty
    	} else if numAvailable <= uint32(s.e2eParams.NumRekeys) {
    		return Empty
    		// do not need to make a copy of getNumKeys becasue it is static and
    		// only used once
    	} else if numUsed >= s.rekeyThreshold {
    		return RekeyNeeded
    	} else {
    		return Active
    	}
    }
    
    // Sets the negotiation status, this tracks the state of the key negotiation,
    // only certain movements are allowed
    //   Unconfirmed <--> Sending --> Sent --> Confirmed <--> NewSessionTriggered --> NewSessionCreated
    //				  -------------->
    //
    // Saves the session unless the status is sending so that on reload the rekey
    // will be redone if it was in the process of sending
    
    // Moving from Unconfirmed to Sending and from Confirmed to NewSessionTriggered
    // is handled by  Session.triggerNegotiation() which is called by the
    // Manager as part of Manager.TriggerNegotiations() and will be rejected
    // from this function
    
    var legalStateChanges = [][]bool{
    	// Unconf  Sending  Sent   Confi  NewTrig  NewCreat
    	{false, false, false, false, false, false}, // Unc
    	{true, false, true, true, false, false},    // Sending
    	{false, false, false, true, false, false},  // Sent
    	{false, false, false, false, true, false},  // Confi
    	{false, false, false, true, false, true},   // NewTrig
    	{false, false, true, false, false, false},  // NewCreat
    }
    
    func (s *Session) SetNegotiationStatus(status Negotiation) {
    	if err := s.TrySetNegotiationStatus(status); err != nil {
    		jww.FATAL.Panicf("Failed to set Negotiation status: %s", err)
    	}
    }
    
    func (s *Session) TrySetNegotiationStatus(status Negotiation) error {
    	s.mux.Lock()
    	defer s.mux.Unlock()
    	//only allow the correct state changes to propagate
    	if !legalStateChanges[s.negotiationStatus][status] {
    		return errors.Errorf("Negotiation status change from %s to %s "+
    			"is not valid", s.negotiationStatus, status)
    	}
    
    	// the states of Sending and NewSessionTriggered are not saved to disk when
    	// moved from Unconfirmed or Confirmed respectively so the actions are
    	// re-triggered if there is a crash and reload. As a result, a save when
    	// reverting states is unnecessary
    	save := !((s.negotiationStatus == Sending && status == Unconfirmed) ||
    		(s.negotiationStatus == NewSessionTriggered && status == Confirmed))
    
    	//change the state
    	oldStatus := s.negotiationStatus
    	s.negotiationStatus = status
    
    	//save the status if appropriate
    	if save {
    		if err := s.save(); err != nil {
    			jww.FATAL.Panicf("Failed to save Session %s when moving from %s to %s", s, oldStatus, status)
    		}
    	}
    
    	return nil
    }
    
    // This function, in a mostly thread safe manner, checks if the session needs a
    // negotiation, returns if it does while updating the session to denote the
    // negotiation was triggered
    // WARNING: This function relies on proper action by the caller for data safety.
    // When triggering the creation of a new session (the first case) it does not
    // store to disk the fact that it has triggered the session. This is because
    // every session should only partnerSource one other session and in the event that
    // session partnerSource does not resolve before a crash, by not storing it the
    // partnerSource will automatically happen again when reloading after the crash.
    // In order to ensure the session creation is not triggered again after the
    // reload, it is the responsibility of the caller to call
    // Session.SetConfirmationStatus(NewSessionCreated) .
    func (s *Session) triggerNegotiation() bool {
    	// Due to the fact that a read lock cannot be transitioned to a
    	// write lock, the state checks need to happen a second time because it
    	// is possible for another thread to take the read lock and update the
    	// state between this thread releasing it and regaining it again. In this
    	// case, such double locking is preferable because the majority of the time,
    	// the checked cases will turn out to be false.
    	s.mux.RLock()
    	// If we've used more keys than the RekeyThreshold, it's time for a rekey
    	if s.keyState.GetNumUsed() >= s.rekeyThreshold &&
    		s.negotiationStatus < NewSessionTriggered {
    		s.mux.RUnlock()
    		s.mux.Lock()
    		if s.keyState.GetNumUsed() >= s.rekeyThreshold &&
    			s.negotiationStatus < NewSessionTriggered {
    			//partnerSource a rekey to create a new session
    			s.negotiationStatus = NewSessionTriggered
    			// no save is make after the update because we do not want this state
    			// saved to disk. The caller will shortly execute the operation,
    			// and then move to the next state. If a crash occurs before, by not
    			// storing this state this operation will be repeated after reload
    			// The save function has been modified so if another call causes a
    			// save, "NewSessionTriggered" will be overwritten with "Confirmed"
    			// in the saved data.
    			s.mux.Unlock()
    			return true
    		} else {
    			s.mux.Unlock()
    			return false
    		}
    	} else if s.negotiationStatus == Unconfirmed {
    		// retrigger this sessions negotiation
    		s.mux.RUnlock()
    		s.mux.Lock()
    		if s.negotiationStatus == Unconfirmed {
    			s.negotiationStatus = Sending
    			// no save is made after the update because we do not want this state
    			// saved to disk. The caller will shortly execute the operation,
    			// and then move to the next state. If a crash occurs before, by not
    			// storing this state this operation will be repeated after reload
    			// The save function has been modified so if another call causes a
    			// save, "Sending" will be overwritten with "Unconfirmed"
    			// in the saved data.
    			s.mux.Unlock()
    			return true
    		} else {
    			s.mux.Unlock()
    			return false
    		}
    	}
    	s.mux.RUnlock()
    	return false
    }
    
    // checks if the session has been confirmed
    func (s *Session) NegotiationStatus() Negotiation {
    	s.mux.RLock()
    	defer s.mux.RUnlock()
    	return s.negotiationStatus
    }
    
    // checks if the session has been confirmed
    func (s *Session) IsConfirmed() bool {
    	c := s.NegotiationStatus()
    	//fmt.Println(c)
    	return c >= Confirmed
    }
    
    func (s *Session) String() string {
    	partner := s.GetPartner()
    	if partner != nil {
    		return fmt.Sprintf("{Partner: %s, ID: %s}",
    			partner, s.GetID())
    	} else {
    		return fmt.Sprintf("{Partner: nil, ID: %s}", s.GetID())
    	}
    }
    
    /*PRIVATE*/
    func (s *Session) useKey(keynum uint32) {
    	s.keyState.Use(keynum)
    }
    
    // generates keys from the base data stored in the session object.
    // myPrivKey will be generated if not present
    func (s *Session) generate(kv *versioned.KV) *versioned.KV {
    	grp := s.relationship.manager.ctx.grp
    
    	//generate private key if it is not present
    	if s.myPrivKey == nil {
    		stream := s.relationship.manager.ctx.rng.GetStream()
    		s.myPrivKey = dh.GeneratePrivateKey(len(grp.GetPBytes()),
    			grp, stream)
    		// Get the variant opposite my partners variant
    		sidhVariant := utility.GetCompatibleSIDHVariant(
    			s.partnerSIDHPubKey.Variant())
    		s.mySIDHPrivKey = utility.NewSIDHPrivateKey(sidhVariant)
    		s.mySIDHPrivKey.Generate(stream)
    		stream.Close()
    	}
    
    	// compute the base key if it is not already there
    	if s.baseKey == nil {
    		s.baseKey = GenerateE2ESessionBaseKey(s.myPrivKey,
    			s.partnerPubKey, grp, s.mySIDHPrivKey,
    			s.partnerSIDHPubKey)
    	}
    
    	kv = kv.Prefix(makeSessionPrefix(s.GetID()))
    
    	p := s.e2eParams
    	h, _ := hash.NewCMixHash()
    
    	//generate rekeyThreshold and keying info
    	numKeys := uint32(randomness.RandInInterval(big.NewInt(
    		int64(p.MaxKeys-p.MinKeys)),
    		s.baseKey.Bytes(), h).Int64() + int64(p.MinKeys))
    
    	// start rekeying when enough keys have been used
    	s.rekeyThreshold = uint32(math.Ceil(s.e2eParams.RekeyThreshold*float64(numKeys)))
    
    	// the total number of keys should be the number of rekeys plus the
    	// number of keys to use
    	numKeys = numKeys + uint32(s.e2eParams.NumRekeys)
    
    	// create the new state vectors. This will cause disk operations
    	// storing them
    
    	// To generate the state vector key correctly,
    	// basekey must be computed as the session ID is the hash of basekey
    	var err error
    	s.keyState, err = utility.NewStateVector(kv, "", numKeys)
    	if err != nil {
    		jww.FATAL.Printf("Failed key generation: %s", err)
    	}
    
    	//register keys for reception if this is a reception session
    	if s.t == Receive {
    		//register keys
    		s.relationship.manager.ctx.fa.add(s.getUnusedKeys())
    	}
    
    	return kv
    }
    
    //returns key objects for all unused keys
    func (s *Session) getUnusedKeys() []*Key {
    	keyNums := s.keyState.GetUnusedKeyNums()
    
    	keys := make([]*Key, len(keyNums))
    	for i, keyNum := range keyNums {
    		keys[i] = newKey(s, keyNum)
    	}
    
    	return keys
    }
    
    //builds the
    func makeSessionPrefix(sid SessionID) string {
    	return fmt.Sprintf(sessionPrefix, sid)
    }