diff --git a/auth/callbacks.go b/auth/callbacks.go
index 781164bb89e374f166b0ace4d4fb639e7c250d10..273ff6b0b7af5b36ff53d44e265ebaeaab0183cd 100644
--- a/auth/callbacks.go
+++ b/auth/callbacks.go
@@ -7,10 +7,6 @@
 package auth
 
 import (
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/client/cmix/identity/receptionID"
-	"gitlab.com/elixxir/client/cmix/rounds"
-	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/xx_network/primitives/id"
 	"sync"
 )
@@ -42,27 +38,8 @@ func (p *partnerCallbacks) DeletePartnerCallback(partnerId *id.ID) {
 
 // getPartnerCallback returns the Callbacks for the given partnerId
 func (p *partnerCallbacks) getPartnerCallback(partnerId *id.ID) Callbacks {
-	return p.callbacks[*partnerId]
-}
-
-// DefaultAuthCallbacks is a simple structure for providing a default Callbacks implementation
-// It should generally not be used.
-type DefaultAuthCallbacks struct{}
-
-// Confirm will be called when an auth Confirm message is processed.
-func (a DefaultAuthCallbacks) Confirm(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	jww.ERROR.Printf("No valid auth callback assigned!")
-}
-
-// Request will be called when an auth Request message is processed.
-func (a DefaultAuthCallbacks) Request(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	jww.ERROR.Printf("No valid auth callback assigned!")
-}
+	p.RLock()
+	defer p.RUnlock()
 
-// Reset will be called when an auth Reset operation occurs.
-func (a DefaultAuthCallbacks) Reset(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	jww.ERROR.Printf("No valid auth callback assigned!")
+	return p.callbacks[*partnerId]
 }
diff --git a/auth/interface.go b/auth/interface.go
index c6f5a5193a13b6cebb42a4e930a0f58d6c52e23d..ea1b108744ed2fceb201eb6268c2d54d684f8002 100644
--- a/auth/interface.go
+++ b/auth/interface.go
@@ -100,6 +100,10 @@ type State interface {
 	// auth callback for the given partner ID.
 	DeletePartnerCallback(partnerId *id.ID)
 
+	// DeletePartner deletes the request and/or confirmation for the given
+	// partner.
+	DeletePartner(partner *id.ID) error
+
 	// Closer stops listening to auth.
 	io.Closer
 }
diff --git a/auth/receivedConfirm.go b/auth/receivedConfirm.go
index 538329e1b532a1a34d813f866dfb739319c09d3b..bf25d5661743e226f2d7cb2024cff457365ab20c 100644
--- a/auth/receivedConfirm.go
+++ b/auth/receivedConfirm.go
@@ -113,13 +113,11 @@ func (rcs *receivedConfirmService) Process(msg format.Message,
 		Facts:          make([]fact.Fact, 0),
 	}
 
-	authState.partnerCallbacks.RLock()
 	if cb := authState.partnerCallbacks.getPartnerCallback(c.ID); cb != nil {
 		cb.Confirm(c, receptionID, round)
 	} else {
 		authState.callbacks.Confirm(c, receptionID, round)
 	}
-	authState.partnerCallbacks.RUnlock()
 }
 
 func (rcs *receivedConfirmService) String() string {
diff --git a/auth/receivedRequest.go b/auth/receivedRequest.go
index b410b04a3e29cca95dedd8bc14d310507e8bce57..4e3325996a6ca5c558d3531a019c68c92f026bf3 100644
--- a/auth/receivedRequest.go
+++ b/auth/receivedRequest.go
@@ -130,21 +130,17 @@ func (rrs *receivedRequestService) Process(message format.Message,
 			} else if authState.params.ReplayRequests {
 				//if we did not already accept, auto replay the request
 				if rrs.reset {
-					authState.partnerCallbacks.RLock()
 					if cb := authState.partnerCallbacks.getPartnerCallback(c.ID); cb != nil {
 						cb.Reset(c, receptionID, round)
 					} else {
 						authState.callbacks.Reset(c, receptionID, round)
 					}
-					authState.partnerCallbacks.RUnlock()
 				} else {
-					authState.partnerCallbacks.RLock()
 					if cb := authState.partnerCallbacks.getPartnerCallback(c.ID); cb != nil {
 						cb.Request(c, receptionID, round)
 					} else {
 						authState.callbacks.Request(c, receptionID, round)
 					}
-					authState.partnerCallbacks.RUnlock()
 				}
 			}
 			//if not confirm, and params.replay requests is true, we need to replay
@@ -254,8 +250,6 @@ func (rrs *receivedRequestService) Process(message format.Message,
 	}
 
 	// auto-confirm if we should
-	authState.partnerCallbacks.RLock()
-	defer authState.partnerCallbacks.RUnlock()
 	if autoConfirm || reset {
 		_, _ = authState.confirm(c, authState.params.getConfirmTag(reset))
 		//handle callbacks
diff --git a/auth/state.go b/auth/state.go
index 43e1d9735fde5bd0e5c4bbc396ec3ff74727c4b5..3b82f15e46049abd5cc8be2367fb4abb45b68e71 100644
--- a/auth/state.go
+++ b/auth/state.go
@@ -115,13 +115,11 @@ func (s *state) CallAllReceivedRequests() {
 		rr := rrList[i]
 		eph := receptionID.BuildIdentityFromRound(rr.GetContact().ID,
 			rr.GetRound())
-		s.partnerCallbacks.RLock()
 		if cb := s.partnerCallbacks.getPartnerCallback(rr.GetContact().ID); cb != nil {
 			cb.Request(rr.GetContact(), eph, rr.GetRound())
 		} else {
 			s.callbacks.Request(rr.GetContact(), eph, rr.GetRound())
 		}
-		s.partnerCallbacks.RUnlock()
 	}
 }
 
@@ -144,6 +142,22 @@ func (s *state) Close() error {
 	return nil
 }
 
+// DeletePartner deletes the request and/or confirmation for the given partner.
+func (s *state) DeletePartner(partner *id.ID) error {
+	err := s.store.DeleteRequest(partner)
+	err2 := s.store.DeleteConfirmation(partner)
+
+	// Only return an error if both failed to delete
+	if err != nil && err2 != nil {
+		return errors.Errorf("Failed to delete partner: no requests or "+
+			"confirmations found: %s, %s", err, err2)
+	}
+
+	s.DeletePartnerCallback(partner)
+
+	return nil
+}
+
 // AddPartnerCallback that overrides the generic auth callback for the given partnerId
 func (s *state) AddPartnerCallback(partnerId *id.ID, cb Callbacks) {
 	s.partnerCallbacks.AddPartnerCallback(partnerId, cb)
diff --git a/auth/utils_test.go b/auth/utils_test.go
index 4d6b50ade9db9ff930e3824af62515a098578e77..4c53ff91a95c40e4b138258592cb292b12d8e749 100644
--- a/auth/utils_test.go
+++ b/auth/utils_test.go
@@ -88,6 +88,8 @@ func (m mockE2eHandler) Unregister(listenerID receive.ListenerID) {
 	return
 }
 
+func (m mockE2eHandler) UnregisterUserListeners(*id.ID) {}
+
 func (m mockE2eHandler) AddPartner(partnerID *id.ID,
 	partnerPubKey, myPrivKey *cyclic.Int,
 	partnerSIDHPubKey *sidh.PublicKey, mySIDHPrivKey *sidh.PrivateKey,
diff --git a/bindings/connect.go b/bindings/connect.go
index 81a482a327304a640417717dfe55bc409b17b05d..98f9795398040d1cb372e8c54fb4d273efd84dbc 100644
--- a/bindings/connect.go
+++ b/bindings/connect.go
@@ -6,6 +6,7 @@ import (
 	"gitlab.com/elixxir/client/connect"
 	e2e2 "gitlab.com/elixxir/client/e2e"
 	"gitlab.com/elixxir/crypto/contact"
+	"time"
 )
 
 // connectionTrackerSingleton is used to track connections so they can be
@@ -44,7 +45,9 @@ func (c *Cmix) Connect(e2eId int, recipientContact []byte) (
 		return nil, err
 	}
 
-	connection, err := connect.Connect(cont, e2eClient.api, connect.GetDefaultParams())
+	p := connect.GetDefaultParams()
+	p.Timeout = 45 * time.Second
+	connection, err := connect.Connect(cont, e2eClient.api, p)
 	if err != nil {
 		return nil, err
 	}
@@ -85,6 +88,7 @@ func (c *Connection) GetPartner() []byte {
 // RegisterListener is used for E2E reception
 // and allows for reading data sent from the partner.Manager
 // Returns marshalled ListenerID
-func (c *Connection) RegisterListener(messageType int, newListener Listener) {
-	_ = c.connection.RegisterListener(catalog.MessageType(messageType), listener{l: newListener})
+func (c *Connection) RegisterListener(messageType int, newListener Listener) error {
+	_, err := c.connection.RegisterListener(catalog.MessageType(messageType), listener{l: newListener})
+	return err
 }
diff --git a/bindings/e2e.go b/bindings/e2e.go
index 289de4218cad4a4fd485b3524826e0f702e05f3c..bd849f9ad1304b14b6eaa3a20e8177059c82c7d6 100644
--- a/bindings/e2e.go
+++ b/bindings/e2e.go
@@ -7,15 +7,10 @@
 package bindings
 
 import (
-	"encoding/json"
-	"gitlab.com/elixxir/client/auth"
 	"gitlab.com/elixxir/client/cmix/identity/receptionID"
 	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/xxdk"
 	"gitlab.com/elixxir/crypto/contact"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/xx_network/crypto/signature/rsa"
-	"gitlab.com/xx_network/primitives/id"
 )
 
 // e2eTrackerSingleton is used to track E2e objects so that
@@ -46,14 +41,14 @@ func LoginE2e(cmixId int, callbacks AuthCallbacks, identity []byte) (*E2e, error
 		return nil, err
 	}
 
-	newIdentity, err := unmarshalIdentity(identity, cmix.api.GetStorage().GetE2EGroup())
+	newIdentity, err := xxdk.UnmarshalReceptionIdentity(identity)
 	if err != nil {
 		return nil, err
 	}
 
-	var authCallbacks auth.Callbacks
+	var authCallbacks xxdk.AuthCallbacks
 	if callbacks == nil {
-		authCallbacks = auth.DefaultAuthCallbacks{}
+		authCallbacks = xxdk.DefaultAuthCallbacks{}
 	} else {
 		authCallbacks = &authCallback{bindingsCbs: callbacks}
 	}
@@ -62,6 +57,7 @@ func LoginE2e(cmixId int, callbacks AuthCallbacks, identity []byte) (*E2e, error
 	if err != nil {
 		return nil, err
 	}
+
 	return e2eTrackerSingleton.make(newE2e), nil
 }
 
@@ -74,14 +70,14 @@ func LoginE2eEphemeral(cmixId int, callbacks AuthCallbacks, identity []byte) (*E
 		return nil, err
 	}
 
-	newIdentity, err := unmarshalIdentity(identity, cmix.api.GetStorage().GetE2EGroup())
+	newIdentity, err := xxdk.UnmarshalReceptionIdentity(identity)
 	if err != nil {
 		return nil, err
 	}
 
-	var authCallbacks auth.Callbacks
+	var authCallbacks xxdk.AuthCallbacks
 	if callbacks == nil {
-		authCallbacks = auth.DefaultAuthCallbacks{}
+		authCallbacks = xxdk.DefaultAuthCallbacks{}
 	} else {
 		authCallbacks = &authCallback{bindingsCbs: callbacks}
 	}
@@ -104,9 +100,9 @@ func LoginE2eLegacy(cmixId int, callbacks AuthCallbacks) (*E2e, error) {
 		return nil, err
 	}
 
-	var authCallbacks auth.Callbacks
+	var authCallbacks xxdk.AuthCallbacks
 	if callbacks == nil {
-		authCallbacks = auth.DefaultAuthCallbacks{}
+		authCallbacks = xxdk.DefaultAuthCallbacks{}
 	} else {
 		authCallbacks = &authCallback{bindingsCbs: callbacks}
 	}
@@ -120,38 +116,7 @@ func LoginE2eLegacy(cmixId int, callbacks AuthCallbacks) (*E2e, error) {
 
 // GetContact returns a marshalled contact.Contact object for the E2e ReceptionIdentity
 func (e *E2e) GetContact() []byte {
-	return e.api.GetReceptionIdentity().GetContact(e.api.GetStorage().GetE2EGroup()).Marshal()
-}
-
-// unmarshalIdentity is a helper function for taking in a marshalled xxdk.ReceptionIdentity and making it an object
-func unmarshalIdentity(marshaled []byte, e2eGrp *cyclic.Group) (xxdk.ReceptionIdentity, error) {
-	newIdentity := xxdk.ReceptionIdentity{}
-
-	// Unmarshal given identity into ReceptionIdentity object
-	givenIdentity := ReceptionIdentity{}
-	err := json.Unmarshal(marshaled, &givenIdentity)
-	if err != nil {
-		return xxdk.ReceptionIdentity{}, err
-	}
-
-	newIdentity.ID, err = id.Unmarshal(givenIdentity.ID)
-	if err != nil {
-		return xxdk.ReceptionIdentity{}, err
-	}
-
-	newIdentity.DHKeyPrivate = e2eGrp.NewInt(1)
-	err = newIdentity.DHKeyPrivate.UnmarshalJSON(givenIdentity.DHKeyPrivate)
-	if err != nil {
-		return xxdk.ReceptionIdentity{}, err
-	}
-
-	newIdentity.RSAPrivatePem, err = rsa.LoadPrivateKeyFromPem(givenIdentity.RSAPrivatePem)
-	if err != nil {
-		return xxdk.ReceptionIdentity{}, err
-	}
-
-	newIdentity.Salt = givenIdentity.Salt
-	return newIdentity, nil
+	return e.api.GetReceptionIdentity().GetContact().Marshal()
 }
 
 // AuthCallbacks is the bindings-specific interface for auth.Callbacks methods.
@@ -180,19 +145,19 @@ func convertAuthCallbacks(requestor contact.Contact,
 }
 
 // Confirm will be called when an auth Confirm message is processed.
-func (a *authCallback) Confirm(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	a.bindingsCbs.Confirm(convertAuthCallbacks(requestor, receptionID, round))
+func (a *authCallback) Confirm(partner contact.Contact,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round, _ *xxdk.E2e) {
+	a.bindingsCbs.Confirm(convertAuthCallbacks(partner, receptionID, round))
 }
 
 // Request will be called when an auth Request message is processed.
-func (a *authCallback) Request(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	a.bindingsCbs.Request(convertAuthCallbacks(requestor, receptionID, round))
+func (a *authCallback) Request(partner contact.Contact,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round, _ *xxdk.E2e) {
+	a.bindingsCbs.Request(convertAuthCallbacks(partner, receptionID, round))
 }
 
 // Reset will be called when an auth Reset operation occurs.
-func (a *authCallback) Reset(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	a.bindingsCbs.Reset(convertAuthCallbacks(requestor, receptionID, round))
+func (a *authCallback) Reset(partner contact.Contact,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round, _ *xxdk.E2e) {
+	a.bindingsCbs.Reset(convertAuthCallbacks(partner, receptionID, round))
 }
diff --git a/bindings/e2eAuth.go b/bindings/e2eAuth.go
index b483671d90d3deb616bc0bd2e13018882e859ec1..77083daa290bfcd1dbd27ffe714e2d456624475d 100644
--- a/bindings/e2eAuth.go
+++ b/bindings/e2eAuth.go
@@ -8,6 +8,7 @@
 package bindings
 
 import (
+	"gitlab.com/elixxir/client/xxdk"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/primitives/id"
@@ -215,7 +216,8 @@ func (e *E2e) AddPartnerCallback(partnerID []byte, cb AuthCallbacks) error {
 		return err
 	}
 
-	e.api.GetAuth().AddPartnerCallback(partnerId, &authCallback{bindingsCbs: cb})
+	e.api.GetAuth().AddPartnerCallback(partnerId,
+		xxdk.MakeAuthCallbacksAdapter(&authCallback{bindingsCbs: cb}, e.api))
 	return nil
 }
 
diff --git a/bindings/contact.go b/bindings/identity.go
similarity index 83%
rename from bindings/contact.go
rename to bindings/identity.go
index 251c810f40c6e31a6212b8e2eaee770c86724d00..2718672f50b22b8017d84e9c1799eac4667905d6 100644
--- a/bindings/contact.go
+++ b/bindings/identity.go
@@ -5,7 +5,6 @@ import (
 	"gitlab.com/elixxir/client/xxdk"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/primitives/fact"
-	"gitlab.com/xx_network/crypto/signature/rsa"
 )
 
 // ReceptionIdentity struct
@@ -27,23 +26,12 @@ type ReceptionIdentity struct {
 
 // MakeIdentity generates a new cryptographic identity for receiving messages
 func (c *Cmix) MakeIdentity() ([]byte, error) {
-	s := c.api.GetRng().GetStream()
-	defer s.Close()
-	ident, err := xxdk.MakeReceptionIdentity(s, c.api.GetStorage().GetE2EGroup())
-
-	dhPrivJson, err := ident.DHKeyPrivate.MarshalJSON()
+	ident, err := xxdk.MakeReceptionIdentity(c.api)
 	if err != nil {
 		return nil, err
 	}
-	//create the identity object
-	I := ReceptionIdentity{
-		ID:            ident.ID.Marshal(),
-		RSAPrivatePem: rsa.CreatePrivateKeyPem(ident.RSAPrivatePem),
-		Salt:          ident.Salt,
-		DHKeyPrivate:  dhPrivJson,
-	}
 
-	return json.Marshal(&I)
+	return ident.Marshal()
 }
 
 // GetIDFromContact accepts a marshalled contact.Contact object & returns a marshalled id.ID object
@@ -122,3 +110,32 @@ func GetFactsFromContact(marshaled []byte) ([]byte, error) {
 	}
 	return factsListMarshaled, nil
 }
+
+// StoreReceptionIdentity stores the given identity in Cmix storage with the given key
+// This is the ideal way to securely store identities, as the caller of this function
+// is only required to store the given key separately rather than the keying material
+func StoreReceptionIdentity(key string, identity []byte, cmixId int) error {
+	cmix, err := cmixTrackerSingleton.get(cmixId)
+	if err != nil {
+		return err
+	}
+	receptionIdentity, err := xxdk.UnmarshalReceptionIdentity(identity)
+	if err != nil {
+		return err
+	}
+	return xxdk.StoreReceptionIdentity(key, receptionIdentity, cmix.api)
+}
+
+// LoadReceptionIdentity loads the given identity in Cmix storage with the given key
+func LoadReceptionIdentity(key string, cmixId int) ([]byte, error) {
+	cmix, err := cmixTrackerSingleton.get(cmixId)
+	if err != nil {
+		return nil, err
+	}
+	storageObj, err := cmix.api.GetStorage().Get(key)
+	if err != nil {
+		return nil, err
+	}
+
+	return storageObj.Data, nil
+}
diff --git a/bindings/contact_test.go b/bindings/identity_test.go
similarity index 100%
rename from bindings/contact_test.go
rename to bindings/identity_test.go
diff --git a/bindings/utilities.go b/bindings/utilities.go
index 96c7b346e6a920109d5678fa9802fe5fdfa37435..7eb109730a21b1c4cc519ee7b1e8d154362f62a0 100644
--- a/bindings/utilities.go
+++ b/bindings/utilities.go
@@ -5,6 +5,7 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"google.golang.org/grpc/grpclog"
+	"log"
 )
 
 // sets level of logging. All logs the set level and above will be displayed
@@ -25,6 +26,7 @@ func LogLevel(level int) error {
 	threshold := jww.Threshold(level)
 	jww.SetLogThreshold(threshold)
 	jww.SetStdoutThreshold(threshold)
+	jww.SetFlags(log.LstdFlags | log.Lmicroseconds)
 
 	switch threshold {
 	case jww.LevelTrace:
diff --git a/cmd/broadcast.go b/cmd/broadcast.go
index af7ea93d85cf1160fee8eaaecbbe75acfadb91b1..c3221e938a4ad6814f27a8d032aa5561da971356 100644
--- a/cmd/broadcast.go
+++ b/cmd/broadcast.go
@@ -24,12 +24,11 @@ var broadcastCmd = &cobra.Command{
 	Short: "Send broadcast messages",
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
-		client := initClient()
+		client := initE2e()
 
 		// Write user contact to file
-		user := client.GetUser()
-		jww.INFO.Printf("User: %s", user.ReceptionID)
-		jww.INFO.Printf("User Transmission: %s", user.TransmissionID)
+		user := client.GetReceptionIdentity()
+		jww.INFO.Printf("User: %s", user.ID)
 		writeContact(user.GetContact())
 
 		err := client.StartNetworkFollower(5 * time.Second)
@@ -40,8 +39,8 @@ var broadcastCmd = &cobra.Command{
 		// Wait until connected or crash on timeout
 		connected := make(chan bool, 10)
 		client.GetCmix().AddHealthCallback(
-			func(isconnected bool) {
-				connected <- isconnected
+			func(isConnected bool) {
+				connected <- isConnected
 			})
 		waitUntilConnected(connected)
 		/* Set up underlying crypto broadcast.Channel */
@@ -69,9 +68,10 @@ var broadcastCmd = &cobra.Command{
 				jww.FATAL.Panicf("description cannot be empty")
 			}
 
+			var cryptChannel *crypto.Channel
 			if viper.GetBool("new") {
 				// Create a new broadcast channel
-				channel, pk, err = crypto.NewChannel(name, desc, client.GetRng().GetStream())
+				cryptChannel, pk, err = crypto.NewChannel(name, desc, client.GetRng().GetStream())
 				if err != nil {
 					jww.FATAL.Panicf("Failed to create new channel: %+v", err)
 				}
@@ -99,7 +99,7 @@ var broadcastCmd = &cobra.Command{
 					jww.FATAL.Panicf("Failed to generate channel ID: %+v", err)
 				}
 
-				channel = &crypto.Channel{
+				cryptChannel = &crypto.Channel{
 					ReceptionID: rid,
 					Name:        name,
 					Description: desc,
@@ -109,7 +109,7 @@ var broadcastCmd = &cobra.Command{
 			}
 
 			// Save channel to disk
-			cBytes, err := channel.Marshal()
+			cBytes, err := cryptChannel.Marshal()
 			if err != nil {
 				jww.ERROR.Printf("Failed to marshal channel to bytes: %+v", err)
 			}
diff --git a/cmd/callbacks.go b/cmd/callbacks.go
index 13900622395ed2e6b787df1b2d7d22c426cb8c61..d5d75a321f7e215062738184bbaab954812c29b6 100644
--- a/cmd/callbacks.go
+++ b/cmd/callbacks.go
@@ -26,20 +26,18 @@ import (
 type authCallbacks struct {
 	autoConfirm bool
 	confCh      chan *id.ID
-	client      *xxdk.E2e
 }
 
-func makeAuthCallbacks(client *xxdk.E2e, autoConfirm bool) *authCallbacks {
+func makeAuthCallbacks(autoConfirm bool) *authCallbacks {
 	return &authCallbacks{
 		autoConfirm: autoConfirm,
 		confCh:      make(chan *id.ID, 10),
-		client:      client,
 	}
 }
 
 func (a *authCallbacks) Request(requestor contact.Contact,
 	receptionID receptionID.EphemeralIdentity,
-	round rounds.Round) {
+	round rounds.Round, client *xxdk.E2e) {
 	msg := fmt.Sprintf("Authentication channel request from: %s\n",
 		requestor.ID)
 	jww.INFO.Printf(msg)
@@ -48,9 +46,9 @@ func (a *authCallbacks) Request(requestor contact.Contact,
 		jww.INFO.Printf("Channel Request: %s",
 			requestor.ID)
 		if viper.GetBool("verify-sends") { // Verify message sends were successful
-			acceptChannelVerified(a.client, requestor.ID)
+			acceptChannelVerified(client, requestor.ID)
 		} else {
-			acceptChannel(a.client, requestor.ID)
+			acceptChannel(client, requestor.ID)
 		}
 
 		a.confCh <- requestor.ID
@@ -60,14 +58,14 @@ func (a *authCallbacks) Request(requestor contact.Contact,
 
 func (a *authCallbacks) Confirm(requestor contact.Contact,
 	receptionID receptionID.EphemeralIdentity,
-	round rounds.Round) {
+	round rounds.Round, client *xxdk.E2e) {
 	jww.INFO.Printf("Channel Confirmed: %s", requestor.ID)
 	a.confCh <- requestor.ID
 }
 
 func (a *authCallbacks) Reset(requestor contact.Contact,
 	receptionID receptionID.EphemeralIdentity,
-	round rounds.Round) {
+	round rounds.Round, client *xxdk.E2e) {
 	msg := fmt.Sprintf("Authentication channel reset from: %s\n",
 		requestor.ID)
 	jww.INFO.Printf(msg)
diff --git a/cmd/fileTransfer.go b/cmd/fileTransfer.go
index bdafb1edf63440c3f1109516f687a66ba2778b37..468bc50fe270ecc3a9114cc8aedf8627ddcd0f90 100644
--- a/cmd/fileTransfer.go
+++ b/cmd/fileTransfer.go
@@ -36,11 +36,11 @@ var ftCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 
 		// Initialise a new client
-		client := initClient()
+		client := initE2e()
 
 		// Print user's reception ID and save contact file
-		user := client.GetUser()
-		jww.INFO.Printf("User: %s", user.ReceptionID)
+		user := client.GetReceptionIdentity()
+		jww.INFO.Printf("User: %s", user.ID)
 		writeContact(user.GetContact())
 
 		// Start the network follower
@@ -152,7 +152,7 @@ func initFileTransferManager(client *xxdk.E2e, maxThroughput int) (
 
 	// Create new manager
 	manager, err := ft.NewManager(p,
-		client.GetUser().ReceptionID,
+		client.GetReceptionIdentity().ID,
 		client.GetCmix(),
 		client.GetStorage(),
 		client.GetRng())
@@ -169,7 +169,7 @@ func initFileTransferManager(client *xxdk.E2e, maxThroughput int) (
 
 	e2eParams := ftE2e.DefaultParams()
 	e2eFt, err := ftE2e.NewWrapper(receiveCB, e2eParams, manager,
-		client.GetUser().ReceptionID, client.GetE2E(), client.GetCmix())
+		client.GetReceptionIdentity().ID, client.GetE2E(), client.GetCmix())
 	if err != nil {
 		jww.FATAL.Panicf(
 			"[FT] Failed to create new e2e file transfer wrapper: %+v", err)
diff --git a/cmd/group.go b/cmd/group.go
index 8ea7fd73870ec504605674be1f3c2817412abe75..1520d97c648ed47d952636c9f8aba703c04295cd 100644
--- a/cmd/group.go
+++ b/cmd/group.go
@@ -34,11 +34,11 @@ var groupCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 
-		client := initClient()
+		client := initE2e()
 
 		// Print user's reception ID
-		user := client.GetUser()
-		jww.INFO.Printf("User: %s", user.ReceptionID)
+		user := client.GetReceptionIdentity()
+		jww.INFO.Printf("User: %s", user.ID)
 
 		err := client.StartNetworkFollower(5 * time.Second)
 		if err != nil {
@@ -155,7 +155,7 @@ func createGroup(name, msg []byte, filePath string, gm groupChat.GroupChat) {
 	userIdStrings := ReadLines(filePath)
 	userIDs := make([]*id.ID, 0, len(userIdStrings))
 	for _, userIdStr := range userIdStrings {
-		userID, _ := parseRecipient(userIdStr)
+		userID := parseRecipient(userIdStr)
 		userIDs = append(userIDs, userID)
 	}
 
@@ -174,7 +174,7 @@ func createGroup(name, msg []byte, filePath string, gm groupChat.GroupChat) {
 
 // resendRequests resends group requests for the group ID.
 func resendRequests(groupIdString string, gm groupChat.GroupChat) {
-	groupID, _ := parseRecipient(groupIdString)
+	groupID := parseRecipient(groupIdString)
 	rids, status, err := gm.ResendRequest(groupID)
 	if err != nil {
 		jww.FATAL.Panicf("[GC] Failed to resend requests to group %s: %+v",
@@ -212,7 +212,7 @@ func joinGroup(reqChan chan groupStore.Group, timeout time.Duration,
 
 // leaveGroup leaves the group.
 func leaveGroup(groupIdString string, gm groupChat.GroupChat) {
-	groupID, _ := parseRecipient(groupIdString)
+	groupID := parseRecipient(groupIdString)
 	jww.INFO.Printf("[GC] Leaving group %s.", groupID)
 
 	err := gm.LeaveGroup(groupID)
@@ -226,7 +226,7 @@ func leaveGroup(groupIdString string, gm groupChat.GroupChat) {
 
 // sendGroup send the message to the group.
 func sendGroup(groupIdString string, msg []byte, gm groupChat.GroupChat) {
-	groupID, _ := parseRecipient(groupIdString)
+	groupID := parseRecipient(groupIdString)
 
 	jww.INFO.Printf("[GC] Sending to group %s message %q", groupID, msg)
 
@@ -272,7 +272,7 @@ func listGroups(gm groupChat.GroupChat) {
 
 // showGroup prints all the information of the group.
 func showGroup(groupIdString string, gm groupChat.GroupChat) {
-	groupID, _ := parseRecipient(groupIdString)
+	groupID := parseRecipient(groupIdString)
 
 	grp, ok := gm.GetGroup(groupID)
 	if !ok {
diff --git a/cmd/init.go b/cmd/init.go
index 6c42b9cfc6a887d5f1dd3aacec85eb36ab47fb9e..75ea00e6c75bee432de3164d6f721291028ef97b 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -9,9 +9,6 @@
 package cmd
 
 import (
-	"fmt"
-	"gitlab.com/elixxir/client/xxdk"
-
 	"github.com/spf13/cobra"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
@@ -23,17 +20,10 @@ var initCmd = &cobra.Command{
 	Short: "Initialize a user ID but do not connect to the network",
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
-		client := createClient()
-		e2e, err := xxdk.LoadOrInitE2e(client)
-		if err != nil {
-			jww.FATAL.Panicf("%+v", err)
-		}
-		user := client.GetUser()
-		user.E2eDhPublicKey = e2e.GetHistoricalDHPubkey()
+		_, receptionIdentity := initCmix()
 
-		jww.INFO.Printf("User: %s", user.ReceptionID)
-		writeContact(user.GetContact())
-		fmt.Printf("%s\n", user.ReceptionID)
+		jww.INFO.Printf("User: %s", receptionIdentity.ID)
+		writeContact(receptionIdentity.GetContact())
 	},
 }
 
diff --git a/cmd/root.go b/cmd/root.go
index a5e2cf3a978e9989a6acd60bf2fd89b863bda640..ce2b9987acfd1fe75cff381072b4c9a0b459bfbf 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -167,6 +167,9 @@ EnretBzQkeKeBwoB2u6NTiOmUjk=
 	testNetCert = ``
 )
 
+// Key used for storing xxdk.ReceptionIdentity objects
+const identityStorageKey = "identityStorageKey"
+
 var authCbs *authCallbacks
 
 // Execute adds all child commands to the root command and sets flags
@@ -194,35 +197,33 @@ var rootCmd = &cobra.Command{
 			pprof.StartCPUProfile(f)
 		}
 
-		client := initClient()
+		client := initE2e()
 
 		jww.INFO.Printf("Client Initialized...")
 
-		user := client.GetUser()
-		jww.INFO.Printf("USERPUBKEY: %s",
-			user.E2eDhPublicKey.TextVerbose(16, 0))
-		jww.INFO.Printf("User: %s", user.ReceptionID)
-		writeContact(user.GetContact())
-
-		// get Recipient and/or set it to myself
-		isPrecanPartner := false
-		recipientContact := readContact()
-		recipientID := recipientContact.ID
-
-		// Try to get recipientID from destid
-		if recipientID == nil {
-			recipientID, isPrecanPartner = parseRecipient(
-				viper.GetString("destid"))
-		}
-
-		// Set it to myself
-		if recipientID == nil {
-			jww.INFO.Printf("sending message to self")
-			recipientID = user.ReceptionID
-			recipientContact = user.GetContact()
+		receptionIdentity := client.GetReceptionIdentity()
+		jww.INFO.Printf("User: %s", receptionIdentity.ID)
+		writeContact(receptionIdentity.GetContact())
+
+		var recipientContact contact.Contact
+		var recipientID *id.ID
+
+		destFile := viper.GetString("destfile")
+		destId := viper.GetString("destid")
+		sendId := viper.GetString("sendid")
+		if destFile != "" {
+			recipientContact = readContact(destFile)
+			recipientID = recipientContact.ID
+		} else if destId == "0" || sendId == destId {
+			jww.INFO.Printf("Sending message to self")
+			recipientID = receptionIdentity.ID
+			recipientContact = receptionIdentity.GetContact()
+		} else {
+			recipientID = parseRecipient(destId)
 		}
+		isPrecanPartner := isPrecanID(recipientID)
 
-		jww.INFO.Printf("Client: %s, Partner: %s", user.ReceptionID,
+		jww.INFO.Printf("Client: %s, Partner: %s", receptionIdentity.ID,
 			recipientID)
 
 		client.GetE2E().EnableUnsafeReception()
@@ -240,8 +241,8 @@ var rootCmd = &cobra.Command{
 		// Wait until connected or crash on timeout
 		connected := make(chan bool, 10)
 		client.GetCmix().AddHealthCallback(
-			func(isconnected bool) {
-				connected <- isconnected
+			func(isConnected bool) {
+				connected <- isConnected
 			})
 		waitUntilConnected(connected)
 
@@ -292,6 +293,7 @@ var rootCmd = &cobra.Command{
 			authConfirmed = true
 		}
 
+		jww.INFO.Printf("Preexisting E2e partners: %+v", client.GetE2E().GetAllPartnerIDs())
 		if client.GetE2E().HasAuthenticatedChannel(recipientID) {
 			jww.INFO.Printf("Authenticated channel already in "+
 				"place for %s", recipientID)
@@ -317,19 +319,22 @@ var rootCmd = &cobra.Command{
 			authConfirmed = false
 		}
 
-		go func() {
-			for {
-				authID := <-authCbs.confCh
-				if authID.Cmp(recipientID) {
-					authConfirmed = true
+		if !unsafe && !authConfirmed {
+			// Signal for authConfirm callback in a separate thread
+			go func() {
+				for {
+					authID := <-authCbs.confCh
+					if authID.Cmp(recipientID) {
+						authConfirmed = true
+					}
 				}
-			}
-		}()
+			}()
 
-		if !unsafe && !authConfirmed {
 			jww.INFO.Printf("Waiting for authentication channel"+
 				" confirmation with partner %s", recipientID)
 			scnt := uint(0)
+
+			// Wait until authConfirmed
 			waitSecs := viper.GetUint("auth-timeout")
 			for !authConfirmed && scnt < waitSecs {
 				time.Sleep(1 * time.Second)
@@ -343,6 +348,8 @@ var rootCmd = &cobra.Command{
 			}
 			jww.INFO.Printf("Authentication channel confirmation"+
 				" took %d seconds", scnt)
+			jww.INFO.Printf("Authenticated partners saved: %v\n    PartnersList: %+v",
+				!client.GetStorage().GetKV().IsMemStore(), client.GetE2E().GetAllPartnerIDs())
 		}
 
 		// DeleteFingerprint this recipient
@@ -533,7 +540,8 @@ var rootCmd = &cobra.Command{
 	},
 }
 
-func createClient() *xxdk.Cmix {
+// initCmix returns a newly-initialized xxdk.Cmix object and its stored xxdk.ReceptionIdentity
+func initCmix() (*xxdk.Cmix, xxdk.ReceptionIdentity) {
 	logLevel := viper.GetUint("logLevel")
 	initLog(logLevel, viper.GetString("log"))
 	jww.INFO.Printf(Version())
@@ -547,6 +555,9 @@ func createClient() *xxdk.Cmix {
 	backupPath := viper.GetString("backupIn")
 	backupPass := []byte(viper.GetString("backupPass"))
 
+	// FIXME: All branches of the upcoming path
+	var knownReception xxdk.ReceptionIdentity
+
 	// create a new client if none exist
 	if _, err := os.Stat(storeDir); errors.Is(err, fs.ErrNotExist) {
 		// Load NDF
@@ -556,7 +567,7 @@ func createClient() *xxdk.Cmix {
 		}
 
 		if precannedID != 0 {
-			err = xxdk.NewPrecannedClient(precannedID,
+			knownReception, err = xxdk.NewPrecannedClient(precannedID,
 				string(ndfJSON), storeDir, pass)
 		} else if protoUserPath != "" {
 			protoUserJson, err := utils.ReadFile(protoUserPath)
@@ -625,12 +636,33 @@ func createClient() *xxdk.Cmix {
 	}
 
 	params := initParams()
-
 	client, err := xxdk.OpenCmix(storeDir, pass, params)
 	if err != nil {
 		jww.FATAL.Panicf("%+v", err)
 	}
-	return client
+
+	// If there is a known xxdk.ReceptionIdentity, store it now
+	if knownReception.ID != nil {
+		err = xxdk.StoreReceptionIdentity(identityStorageKey, knownReception, client)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+	}
+
+	// Attempt to load extant xxdk.ReceptionIdentity
+	identity, err := xxdk.LoadReceptionIdentity(identityStorageKey, client)
+	if err != nil {
+		// If no extant xxdk.ReceptionIdentity, generate and store a new one
+		identity, err = xxdk.MakeReceptionIdentity(client)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+		err = xxdk.StoreReceptionIdentity(identityStorageKey, identity, client)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+	}
+	return client, identity
 }
 
 func initParams() xxdk.Params {
@@ -654,8 +686,9 @@ func initParams() xxdk.Params {
 	return p
 }
 
-func initClient() *xxdk.E2e {
-	createClient()
+// initE2e returns a fully-formed xxdk.E2e object
+func initE2e() *xxdk.E2e {
+	_, receptionIdentity := initCmix()
 
 	pass := parsePassword(viper.GetString("password"))
 	storeDir := viper.GetString("session")
@@ -664,22 +697,30 @@ func initClient() *xxdk.E2e {
 	params := initParams()
 
 	// load the client
-	baseclient, err := xxdk.LoadCmix(storeDir, pass, params)
-
+	baseClient, err := xxdk.LoadCmix(storeDir, pass, params)
 	if err != nil {
 		jww.FATAL.Panicf("%+v", err)
 	}
 
-	authCbs = makeAuthCallbacks(nil,
+	authCbs = makeAuthCallbacks(
 		viper.GetBool("unsafe-channel-creation"))
 
-	client, err := xxdk.LoginLegacy(baseclient, authCbs)
-	if err != nil {
-		jww.FATAL.Panicf("%+v", err)
+	// Force LoginLegacy for precanned senderID
+	var client *xxdk.E2e
+	if isPrecanID(receptionIdentity.ID) {
+		jww.INFO.Printf("Using LoginLegacy for precan sender")
+		client, err = xxdk.LoginLegacy(baseClient, authCbs)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+	} else {
+		jww.INFO.Printf("Using Login for non-precan sender")
+		client, err = xxdk.Login(baseClient, authCbs, receptionIdentity)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
 	}
 
-	authCbs.client = client
-
 	if protoUser := viper.GetString("protoUserOut"); protoUser != "" {
 
 		jsonBytes, err := client.ConstructProtoUserFile()
@@ -785,7 +826,7 @@ func addAuthenticatedChannel(client *xxdk.E2e, recipientID *id.ID,
 	recipientContact := recipient
 
 	if recipientContact.ID != nil && recipientContact.DhPubKey != nil {
-		me := client.GetUser().GetContact()
+		me := client.GetReceptionIdentity().GetContact()
 		jww.INFO.Printf("Requesting auth channel from: %s",
 			recipientID)
 
@@ -1011,20 +1052,18 @@ func parsePassword(pwStr string) []byte {
 	}
 }
 
-func parseRecipient(idStr string) (*id.ID, bool) {
+func parseRecipient(idStr string) *id.ID {
 	if idStr == "0" {
-		return nil, false
+		jww.FATAL.Panicf("No recipient specified")
 	}
 
-	var recipientID *id.ID
 	if strings.HasPrefix(idStr, "0x") {
-		recipientID = getUIDFromHexString(idStr[2:])
+		return getUIDFromHexString(idStr[2:])
 	} else if strings.HasPrefix(idStr, "b64:") {
-		recipientID = getUIDFromb64String(idStr[4:])
+		return getUIDFromb64String(idStr[4:])
 	} else {
-		recipientID = getUIDFromString(idStr)
+		return getUIDFromString(idStr)
 	}
-	return recipientID, isPrecanID(recipientID)
 }
 
 func getUIDFromHexString(idStr string) *id.ID {
@@ -1291,7 +1330,7 @@ func init() {
 			"for confirmation")
 	viper.BindPFlag("send-auth-request",
 		rootCmd.Flags().Lookup("send-auth-request"))
-	rootCmd.Flags().UintP("auth-timeout", "", 120,
+	rootCmd.Flags().UintP("auth-timeout", "", 60,
 		"The number of seconds to wait for an authentication channel"+
 			"to confirm")
 	viper.BindPFlag("auth-timeout",
diff --git a/cmd/single.go b/cmd/single.go
index 2ce03e5c35162a909d3c502410ed35bede436c13..ed6939618864287668739abd0e6e258c02ae41f4 100644
--- a/cmd/single.go
+++ b/cmd/single.go
@@ -33,12 +33,11 @@ var singleCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 
-		client := initClient()
+		client := initE2e()
 
 		// Write user contact to file
-		user := client.GetUser()
-		jww.INFO.Printf("User: %s", user.ReceptionID)
-		jww.INFO.Printf("User Transmission: %s", user.TransmissionID)
+		user := client.GetReceptionIdentity()
+		jww.INFO.Printf("User: %s", user.ID)
 		writeContact(user.GetContact())
 
 		err := client.StartNetworkFollower(5 * time.Second)
@@ -66,9 +65,14 @@ var singleCmd = &cobra.Command{
 			}),
 		}
 
-		myID := client.GetUser().ReceptionID
+		dhKeyPriv, err := user.GetDHKeyPrivate()
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+
+		myID := user.ID
 		listener := single.Listen(tag, myID,
-			client.GetUser().E2eDhPrivateKey,
+			dhKeyPriv,
 			client.GetCmix(),
 			client.GetStorage().GetE2EGroup(),
 			receiver)
diff --git a/cmd/ud.go b/cmd/ud.go
index 4666cba3fec19f620b0903e191c3955b2b42439a..44d159684a40756dcc724c88b1a17b94c7ed9b65 100644
--- a/cmd/ud.go
+++ b/cmd/ud.go
@@ -33,11 +33,11 @@ var udCmd = &cobra.Command{
 	Short: "Register for and search users using the xx network user discovery service.",
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
-		client := initClient()
+		client := initE2e()
 
 		// get user and save contact to file
-		user := client.GetUser()
-		jww.INFO.Printf("User: %s", user.ReceptionID)
+		user := client.GetReceptionIdentity()
+		jww.INFO.Printf("User: %s", user.ID)
 		writeContact(user.GetContact())
 
 		// // Set up reception handler
@@ -78,21 +78,21 @@ var udCmd = &cobra.Command{
 		waitUntilConnected(connected)
 
 		// Make user discovery manager
-		stream := client.GetRng().GetStream()
-		defer stream.Close()
+		rng := client.GetRng()
 		userToRegister := viper.GetString("register")
 		userDiscoveryMgr, err := ud.NewManager(client.GetCmix(),
 			client.GetE2E(), client.NetworkFollowerStatus,
 			client.GetEventReporter(),
 			client.GetComms(), client.GetStorage(),
-			stream,
+			rng,
 			userToRegister, client.GetStorage().GetKV())
 		if err != nil {
 			if strings.Contains(err.Error(), ud.IsRegisteredErr) {
 				userDiscoveryMgr, err = ud.LoadManager(client.GetCmix(),
 					client.GetE2E(), client.GetEventReporter(),
 					client.GetComms(),
-					client.GetStorage(), client.GetStorage().GetKV())
+					client.GetStorage(), client.GetRng(),
+					client.GetStorage().GetKV())
 				if err != nil {
 					jww.FATAL.Panicf("Failed to load UD manager: %+v", err)
 				}
@@ -152,7 +152,7 @@ var udCmd = &cobra.Command{
 		// Note: Cryptographic verification occurs above the bindings layer
 		lookupIDStr := viper.GetString("lookup")
 		if lookupIDStr != "" {
-			lookupID, _ := parseRecipient(lookupIDStr)
+			lookupID := parseRecipient(lookupIDStr)
 			//if !ok {
 			//	jww.FATAL.Panicf("Could not parse recipient: %s", lookupIDStr)
 			//}
@@ -163,12 +163,15 @@ var udCmd = &cobra.Command{
 				}
 				printContact(newContact)
 			}
+
+			stream := rng.GetStream()
 			_, _, err = ud.Lookup(client.GetCmix(),
 				stream, client.GetE2E().GetGroup(),
 				udContact, cb, lookupID, single.GetDefaultRequestParams())
 			if err != nil {
 				jww.WARN.Printf("Failed UD lookup: %+v", err)
 			}
+			stream.Close()
 
 			time.Sleep(31 * time.Second)
 		}
@@ -256,6 +259,8 @@ var udCmd = &cobra.Command{
 			}
 		}
 
+		stream := rng.GetStream()
+		defer stream.Close()
 		_, _, err = ud.Search(client.GetCmix(),
 			client.GetEventReporter(),
 			stream, client.GetE2E().GetGroup(),
diff --git a/cmd/utils.go b/cmd/utils.go
index aca4fc016ca31eae6efee3087791510ddef0ea42..12c01738e45ecae13c9a4fef3fdb4140f3284a42 100644
--- a/cmd/utils.go
+++ b/cmd/utils.go
@@ -87,8 +87,7 @@ func writeContact(c contact.Contact) {
 	}
 }
 
-func readContact() contact.Contact {
-	inputFilePath := viper.GetString("destfile")
+func readContact(inputFilePath string) contact.Contact {
 	if inputFilePath == "" {
 		return contact.Contact{}
 	}
diff --git a/cmix/client.go b/cmix/client.go
index a06e21f4d3a1c94e492a81ddd7bed6679a1547f9..996cacdb46016a7675cde906cbcd9c66dc68c766 100644
--- a/cmix/client.go
+++ b/cmix/client.go
@@ -147,6 +147,11 @@ func (c *client) initialize(ndf *ndf.NetworkDefinition) error {
 	// Disable KeepAlive packets
 	poolParams.HostParams.KaClientOpts.Time = time.Duration(math.MaxInt64)
 
+	// Configure the proxy error exponential moving average tracker
+	poolParams.HostParams.ProxyErrorMetricParams.Cutoff = 0.30
+	poolParams.HostParams.ProxyErrorMetricParams.InitialAverage =
+		0.75 * poolParams.HostParams.ProxyErrorMetricParams.Cutoff
+
 	// Enable optimized HostPool initialization
 	poolParams.MaxPings = 50
 	poolParams.ForceConnection = true
diff --git a/cmix/gateway/hostPool.go b/cmix/gateway/hostPool.go
index 33bf7e9837fd51a9fa059cd3ddef7ae11158ced1..ca9e0c7b606cb50bd27e932f8da625fcc39aaae9 100644
--- a/cmix/gateway/hostPool.go
+++ b/cmix/gateway/hostPool.go
@@ -47,6 +47,7 @@ var errorsList = []string{
 	ndf.NO_NDF,
 	"Host is in cool down",
 	grpc.ErrClientConnClosing.Error(),
+	connect.TooManyProxyError,
 }
 
 // HostManager Interface allowing storage and retrieval of Host objects
diff --git a/connect/authCallbacks.go b/connect/authCallbacks.go
new file mode 100644
index 0000000000000000000000000000000000000000..cdf8b849fdf3b4065d3e19b62ec5191b79fbf48a
--- /dev/null
+++ b/connect/authCallbacks.go
@@ -0,0 +1,153 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+package connect
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/auth"
+	"gitlab.com/elixxir/client/cmix/identity/receptionID"
+	"gitlab.com/elixxir/client/cmix/rounds"
+	clientE2e "gitlab.com/elixxir/client/e2e"
+	"gitlab.com/elixxir/client/xxdk"
+	"gitlab.com/elixxir/crypto/contact"
+)
+
+// clientAuthCallback provides callback functionality for interfacing between
+// auth.State and Connection. This is used both for blocking creation of a
+// Connection object until the auth Request is confirmed and for dynamically
+// building new Connection objects when an auth Request is received.
+type clientAuthCallback struct {
+	// Used for signaling confirmation of E2E partnership
+	confirmCallback Callback
+	requestCallback Callback
+
+	// Used for building new Connection objects
+	connectionE2e    clientE2e.Handler
+	connectionParams Params
+	authState        auth.State
+}
+
+// getClientAuthCallback returns a callback interface to be passed into the creation
+// of an auth.State object.
+// it will accept requests only if a request callback is passed in
+func getClientAuthCallback(confirm, request Callback, e2e clientE2e.Handler,
+	auth auth.State, params Params) *clientAuthCallback {
+	return &clientAuthCallback{
+		confirmCallback:  confirm,
+		requestCallback:  request,
+		connectionE2e:    e2e,
+		connectionParams: params,
+		authState:        auth,
+	}
+}
+
+// Confirm will be called when an auth Confirm message is processed.
+func (a clientAuthCallback) Confirm(requestor contact.Contact,
+	_ receptionID.EphemeralIdentity, _ rounds.Round) {
+	jww.DEBUG.Printf("Connection auth request for %s confirmed",
+		requestor.ID.String())
+	defer a.authState.DeletePartnerCallback(requestor.ID)
+
+	// After confirmation, get the new partner
+	newPartner, err := a.connectionE2e.GetPartner(requestor.ID)
+	if err != nil {
+		jww.ERROR.Printf("Unable to build connection with "+
+			"partner %s: %+v", requestor.ID, err)
+		// Send a nil connection to avoid hold-ups down the line
+		if a.confirmCallback != nil {
+			a.confirmCallback(nil)
+		}
+		return
+	}
+
+	// Return the new Connection object
+	if a.confirmCallback != nil {
+		a.confirmCallback(BuildConnection(newPartner, a.connectionE2e,
+			a.authState, a.connectionParams))
+	}
+}
+
+// Request will be called when an auth Request message is processed.
+func (a clientAuthCallback) Request(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round) {
+}
+
+// Reset will be called when an auth Reset operation occurs.
+func (a clientAuthCallback) Reset(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round) {
+}
+
+// serverAuthCallback provides callback functionality for interfacing between
+// auth.State and Connection. This is used both for blocking creation of a
+// Connection object until the auth Request is confirmed and for dynamically
+// building new Connection objects when an auth Request is received.
+type serverAuthCallback struct {
+	// Used for signaling confirmation of E2E partnership
+	confirmCallback Callback
+	requestCallback Callback
+
+	// Used to track stale connections
+	cl *ConnectionList
+
+	// Used for building new Connection objects
+	connectionParams Params
+}
+
+// getServerAuthCallback returns a callback interface to be passed into the creation
+// of a xxdk.E2e object.
+// it will accept requests only if a request callback is passed in
+func getServerAuthCallback(confirm, request Callback, cl *ConnectionList,
+	params Params) *serverAuthCallback {
+	return &serverAuthCallback{
+		confirmCallback:  confirm,
+		requestCallback:  request,
+		cl:               cl,
+		connectionParams: params,
+	}
+}
+
+// Confirm will be called when an auth Confirm message is processed.
+func (a serverAuthCallback) Confirm(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round, *xxdk.E2e) {
+}
+
+// Request will be called when an auth Request message is processed.
+func (a serverAuthCallback) Request(requestor contact.Contact,
+	_ receptionID.EphemeralIdentity, _ rounds.Round, e2e *xxdk.E2e) {
+	if a.requestCallback == nil {
+		jww.ERROR.Printf("Received a request when requests are" +
+			"not enable, will not accept")
+	}
+	_, err := e2e.GetAuth().Confirm(requestor)
+	if err != nil {
+		jww.ERROR.Printf("Unable to build connection with "+
+			"partner %s: %+v", requestor.ID, err)
+		// Send a nil connection to avoid hold-ups down the line
+		a.requestCallback(nil)
+	}
+	// After confirmation, get the new partner
+	newPartner, err := e2e.GetE2E().GetPartner(requestor.ID)
+	if err != nil {
+		jww.ERROR.Printf("Unable to build connection with "+
+			"partner %s: %+v", requestor.ID, err)
+		// Send a nil connection to avoid hold-ups down the line
+		a.requestCallback(nil)
+
+		return
+	}
+
+	// Return the new Connection object
+	c := BuildConnection(
+		newPartner, e2e.GetE2E(), e2e.GetAuth(), a.connectionParams)
+	a.cl.Add(c)
+	a.requestCallback(c)
+}
+
+// Reset will be called when an auth Reset operation occurs.
+func (a serverAuthCallback) Reset(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round, *xxdk.E2e) {
+}
diff --git a/connect/authenticated.go b/connect/authenticated.go
index 89311eff3ee33dd957b790c06a98e3de7bd745b9..926cd15435d61883896117c98a5c370860365c27 100644
--- a/connect/authenticated.go
+++ b/connect/authenticated.go
@@ -66,7 +66,11 @@ func ConnectWithAuthentication(recipient contact.Contact, e2eClient *xxdk.E2e,
 
 	// Build the authenticated connection and return
 	identity := e2eClient.GetReceptionIdentity()
-	return connectWithAuthentication(conn, timeStart, recipient, identity.Salt, identity.RSAPrivatePem,
+	privKey, err := identity.GetRSAPrivatePem()
+	if err != nil {
+		return nil, err
+	}
+	return connectWithAuthentication(conn, timeStart, recipient, identity.Salt, privKey,
 		e2eClient.GetRng(), e2eClient.GetCmix(), p)
 }
 
@@ -170,7 +174,7 @@ func connectWithAuthentication(conn Connection, timeStart time.Time,
 // authenticate themselves. An established AuthenticatedConnection will
 // be passed via the callback.
 func StartAuthenticatedServer(identity xxdk.ReceptionIdentity,
-	cb AuthenticatedCallback, net *xxdk.Cmix, p Params) (*xxdk.E2e, error) {
+	cb AuthenticatedCallback, net *xxdk.Cmix, p Params) (*ConnectionServer, error) {
 
 	// Register the waiter for a connection establishment
 	connCb := Callback(func(connection Connection) {
@@ -178,8 +182,14 @@ func StartAuthenticatedServer(identity xxdk.ReceptionIdentity,
 		// client's identity proof. If an identity authentication
 		// message is received and validated, an authenticated connection will
 		// be passed along via the AuthenticatedCallback
-		connection.RegisterListener(catalog.ConnectionAuthenticationRequest,
+		_, err := connection.RegisterListener(
+			catalog.ConnectionAuthenticationRequest,
 			buildAuthConfirmationHandler(cb, connection))
+		if err != nil {
+			jww.ERROR.Printf(
+				"Failed to register listener on connection with %s: %+v",
+				connection.GetPartner().PartnerId(), err)
+		}
 	})
 	return StartServer(identity, connCb, net, p)
 }
diff --git a/connect/authenticated_test.go b/connect/authenticated_test.go
index 3668a742229ed15a27af3697c8a1ae05ad7deca2..5a8a3112fbe10e7672fae864d75f995dcb0c1c50 100644
--- a/connect/authenticated_test.go
+++ b/connect/authenticated_test.go
@@ -42,7 +42,7 @@ func TestConnectWithAuthentication(t *testing.T) {
 
 	myRsaPrivKey, err := rsa.LoadPrivateKeyFromPem(getPrivKey())
 	if err != nil {
-		t.Fatalf("Faled to load private key: %v", err)
+		t.Fatalf("Failed to load private key: %v", err)
 	}
 
 	// Construct client ID the proper way as server will need to verify it
diff --git a/connect/connect.go b/connect/connect.go
index e7d4d14c5c8b1cdee3da162226ba8f7c59313e31..0a6b9a7fc8b1d95125bf24ac9d0f9cfff245205e 100644
--- a/connect/connect.go
+++ b/connect/connect.go
@@ -8,21 +8,21 @@ package connect
 
 import (
 	"encoding/json"
+	"gitlab.com/elixxir/client/e2e/rekey"
+	"gitlab.com/elixxir/client/event"
 	"gitlab.com/elixxir/client/xxdk"
+	"gitlab.com/xx_network/primitives/netTime"
 	"io"
+	"sync/atomic"
 	"time"
 
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/auth"
 	"gitlab.com/elixxir/client/catalog"
-	"gitlab.com/elixxir/client/cmix/identity/receptionID"
-	"gitlab.com/elixxir/client/cmix/rounds"
 	clientE2e "gitlab.com/elixxir/client/e2e"
 	"gitlab.com/elixxir/client/e2e/ratchet/partner"
 	"gitlab.com/elixxir/client/e2e/receive"
-	"gitlab.com/elixxir/client/e2e/rekey"
-	"gitlab.com/elixxir/client/event"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/xx_network/primitives/id"
@@ -34,6 +34,8 @@ const (
 	connectionTimeout = 15 * time.Second
 )
 
+var alreadyClosedErr = errors.New("connection is closed")
+
 // Connection is a wrapper for the E2E and auth packages.
 // It can be used to automatically establish an E2E partnership
 // with a partner.Manager, or be built from an existing E2E partnership.
@@ -54,7 +56,7 @@ type Connection interface {
 	// RegisterListener is used for E2E reception
 	// and allows for reading data sent from the partner.Manager
 	RegisterListener(messageType catalog.MessageType,
-		newListener receive.Listener) receive.ListenerID
+		newListener receive.Listener) (receive.ListenerID, error)
 	// Unregister listener for E2E reception
 	Unregister(listenerID receive.ListenerID)
 
@@ -73,6 +75,10 @@ type Connection interface {
 	// PayloadSize Returns the max payload size for a partitionable E2E
 	// message
 	PayloadSize() uint
+
+	// LastUse returns the timestamp of the last time the connection was
+	// utilised.
+	LastUse() time.Time
 }
 
 // Callback is the callback format required to retrieve
@@ -84,6 +90,7 @@ type Params struct {
 	Auth    auth.Params
 	Rekey   rekey.Params
 	Event   event.Reporter `json:"-"`
+	List    ConnectionListParams
 	Timeout time.Duration
 }
 
@@ -93,6 +100,7 @@ func GetDefaultParams() Params {
 		Auth:    auth.GetDefaultTemporaryParams(),
 		Rekey:   rekey.GetDefaultEphemeralParams(),
 		Event:   event.NewEventManager(),
+		List:    DefaultConnectionListParams(),
 		Timeout: connectionTimeout,
 	}
 }
@@ -116,13 +124,12 @@ func GetParameters(params string) (Params, error) {
 // partner.Manager is confirmed.
 func Connect(recipient contact.Contact, e2eClient *xxdk.E2e,
 	p Params) (Connection, error) {
-
 	// Build callback for E2E negotiation
 	signalChannel := make(chan Connection, 1)
 	cb := func(connection Connection) {
 		signalChannel <- connection
 	}
-	callback := getAuthCallback(cb, nil, e2eClient.GetE2E(), e2eClient.GetAuth(), p)
+	callback := getClientAuthCallback(cb, nil, e2eClient.GetE2E(), e2eClient.GetAuth(), p)
 	e2eClient.GetAuth().AddPartnerCallback(recipient.ID, callback)
 
 	// Perform the auth request
@@ -161,19 +168,31 @@ func Connect(recipient contact.Contact, e2eClient *xxdk.E2e,
 // This call does an xxDK.ephemeralLogin under the hood and the connection
 // server must be the only listener on auth.
 func StartServer(identity xxdk.ReceptionIdentity, cb Callback, net *xxdk.Cmix,
-	p Params) (*xxdk.E2e, error) {
+	p Params) (*ConnectionServer, error) {
+
+	// Create connection list and start cleanup thread
+	cl := NewConnectionList(p.List)
+	err := net.AddService(cl.CleanupThread)
+	if err != nil {
+		return nil, err
+	}
 
 	// Build callback for E2E negotiation
-	callback := getAuthCallback(nil, cb, nil, nil, p)
+	callback := getServerAuthCallback(nil, cb, cl, p)
 
-	client, err := xxdk.LoginEphemeral(net, callback, identity)
+	e2eClient, err := xxdk.LoginEphemeral(net, callback, identity)
 	if err != nil {
 		return nil, err
 	}
 
-	callback.connectionE2e = client.GetE2E()
-	callback.authState = client.GetAuth()
-	return client, nil
+	// Return an ephemeral E2e object
+	return &ConnectionServer{e2eClient, cl}, nil
+}
+
+// ConnectionServer contains
+type ConnectionServer struct {
+	E2e *xxdk.E2e
+	Cl  *ConnectionList
 }
 
 // handler provides an implementation for the Connection interface.
@@ -181,7 +200,14 @@ type handler struct {
 	auth    auth.State
 	partner partner.Manager
 	e2e     clientE2e.Handler
-	params  Params
+
+	// Timestamp of last time a message was sent or received (Unix nanoseconds)
+	lastUse *int64
+
+	// Indicates if the connection has been closed (0 = open, 1 = closed)
+	closed *uint32
+
+	params Params
 }
 
 // BuildConnection assembles a Connection object
@@ -189,20 +215,42 @@ type handler struct {
 // partner.Manager.
 func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler,
 	auth auth.State, p Params) Connection {
+	lastUse := netTime.Now().UnixNano()
+	closed := uint32(0)
 	return &handler{
 		auth:    auth,
 		partner: partner,
 		params:  p,
 		e2e:     e2eHandler,
+		lastUse: &lastUse,
+		closed:  &closed,
 	}
 }
 
-// Close deletes this Connection's partner.Manager and releases resources.
+// Close deletes this Connection's partner.Manager and releases resources. If
+// the connection is already closed, then nil is returned.
 func (h *handler) Close() error {
-	if err := h.e2e.DeletePartner(h.partner.PartnerId()); err != nil {
+	if h.isClosed() {
+		return nil
+	}
+
+	// Get partner ID once at the top because PartnerId makes a copy
+	partnerID := h.partner.PartnerId()
+
+	// Unregister all listeners
+	h.e2e.UnregisterUserListeners(partnerID)
+
+	// Delete partner from e2e and auth
+	if err := h.e2e.DeletePartner(partnerID); err != nil {
+		return err
+	}
+	if err := h.auth.DeletePartner(partnerID); err != nil {
 		return err
 	}
-	return h.auth.Close()
+
+	atomic.StoreUint32(h.closed, 1)
+
+	return nil
 }
 
 // GetPartner returns the partner.Manager for this Connection.
@@ -213,17 +261,25 @@ func (h *handler) GetPartner() partner.Manager {
 // SendE2E is a wrapper for sending specifically to the Connection's
 // partner.Manager.
 func (h *handler) SendE2E(mt catalog.MessageType, payload []byte,
-	params clientE2e.Params) (
-	[]id.Round, e2e.MessageID, time.Time, error) {
+	params clientE2e.Params) ([]id.Round, e2e.MessageID, time.Time, error) {
+	if h.isClosed() {
+		return nil, e2e.MessageID{}, time.Time{}, alreadyClosedErr
+	}
+
+	h.updateLastUse(netTime.Now())
+
 	return h.e2e.SendE2E(mt, h.partner.PartnerId(), payload, params)
 }
 
 // RegisterListener is used for E2E reception
 // and allows for reading data sent from the partner.Manager.
 func (h *handler) RegisterListener(messageType catalog.MessageType,
-	newListener receive.Listener) receive.ListenerID {
-	return h.e2e.RegisterListener(h.partner.PartnerId(),
-		messageType, newListener)
+	newListener receive.Listener) (receive.ListenerID, error) {
+	if h.isClosed() {
+		return receive.ListenerID{}, alreadyClosedErr
+	}
+	lt := &listenerTracker{h, newListener}
+	return h.e2e.RegisterListener(h.partner.PartnerId(), messageType, lt), nil
 }
 
 // Unregister listener for E2E reception.
@@ -231,96 +287,6 @@ func (h *handler) Unregister(listenerID receive.ListenerID) {
 	h.e2e.Unregister(listenerID)
 }
 
-// authCallback provides callback functionality for interfacing between
-// auth.State and Connection. This is used both for blocking creation of a
-// Connection object until the auth Request is confirmed and for dynamically
-// building new Connection objects when an auth Request is received.
-type authCallback struct {
-	// Used for signaling confirmation of E2E partnership
-	confirmCallback Callback
-	requestCallback Callback
-
-	// Used for building new Connection objects
-	connectionE2e    clientE2e.Handler
-	connectionParams Params
-	authState        auth.State
-}
-
-// getAuthCallback returns a callback interface to be passed into the creation
-// of an auth.State object.
-// it will accept requests only if a request callback is passed in
-func getAuthCallback(confirm, request Callback, e2e clientE2e.Handler,
-	auth auth.State, params Params) *authCallback {
-	return &authCallback{
-		confirmCallback:  confirm,
-		requestCallback:  request,
-		connectionE2e:    e2e,
-		connectionParams: params,
-		authState:        auth,
-	}
-}
-
-// Confirm will be called when an auth Confirm message is processed.
-func (a authCallback) Confirm(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	jww.DEBUG.Printf("Connection auth request for %s confirmed",
-		requestor.ID.String())
-	defer a.authState.DeletePartnerCallback(requestor.ID)
-
-	// After confirmation, get the new partner
-	newPartner, err := a.connectionE2e.GetPartner(requestor.ID)
-	if err != nil {
-		jww.ERROR.Printf("Unable to build connection with "+
-			"partner %s: %+v", requestor.ID, err)
-		// Send a nil connection to avoid hold-ups down the line
-		if a.confirmCallback != nil {
-			a.confirmCallback(nil)
-		}
-		return
-	}
-
-	// Return the new Connection object
-	if a.confirmCallback != nil {
-		a.confirmCallback(BuildConnection(newPartner, a.connectionE2e,
-			a.authState, a.connectionParams))
-	}
-}
-
-// Request will be called when an auth Request message is processed.
-func (a authCallback) Request(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-	if a.requestCallback == nil {
-		jww.ERROR.Printf("Received a request when requests are" +
-			"not enable, will not accept")
-	}
-	_, err := a.authState.Confirm(requestor)
-	if err != nil {
-		jww.ERROR.Printf("Unable to build connection with "+
-			"partner %s: %+v", requestor.ID, err)
-		// Send a nil connection to avoid hold-ups down the line
-		a.requestCallback(nil)
-	}
-	// After confirmation, get the new partner
-	newPartner, err := a.connectionE2e.GetPartner(requestor.ID)
-	if err != nil {
-		jww.ERROR.Printf("Unable to build connection with "+
-			"partner %s: %+v", requestor.ID, err)
-		// Send a nil connection to avoid hold-ups down the line
-		a.requestCallback(nil)
-
-		return
-	}
-
-	// Return the new Connection object
-	a.requestCallback(BuildConnection(newPartner, a.connectionE2e,
-		a.authState, a.connectionParams))
-}
-
-// Reset will be called when an auth Reset operation occurs.
-func (a authCallback) Reset(requestor contact.Contact,
-	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
-}
-
 // FirstPartitionSize returns the max partition payload size for the
 // first payload
 func (h *handler) FirstPartitionSize() uint {
@@ -344,3 +310,18 @@ func (h *handler) PartitionSize(payloadIndex uint) uint {
 func (h *handler) PayloadSize() uint {
 	return h.e2e.PayloadSize()
 }
+
+// LastUse returns the timestamp of the last time the connection was utilised.
+func (h *handler) LastUse() time.Time {
+	return time.Unix(0, atomic.LoadInt64(h.lastUse))
+}
+
+// updateLastUse updates the last use time stamp to the given time.
+func (h *handler) updateLastUse(t time.Time) {
+	atomic.StoreInt64(h.lastUse, t.UnixNano())
+}
+
+// isClosed returns true if the connection is closed.
+func (h *handler) isClosed() bool {
+	return atomic.LoadUint32(h.closed) == 1
+}
diff --git a/connect/connectionList.go b/connect/connectionList.go
new file mode 100644
index 0000000000000000000000000000000000000000..fab3404126334ce4c3e0b29f46f9e307a751894e
--- /dev/null
+++ b/connect/connectionList.go
@@ -0,0 +1,119 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package connect
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
+	"sync"
+	"time"
+)
+
+// ConnectionList is a list of all connections.
+type ConnectionList struct {
+	list map[id.ID]Connection
+	p    ConnectionListParams
+	mux  sync.Mutex
+}
+
+// NewConnectionList initialises an empty ConnectionList.
+func NewConnectionList(p ConnectionListParams) *ConnectionList {
+	return &ConnectionList{
+		list: make(map[id.ID]Connection),
+		p:    p,
+	}
+}
+
+// Add adds the connection to the list.
+func (cl *ConnectionList) Add(c Connection) {
+	cl.mux.Lock()
+	defer cl.mux.Unlock()
+
+	cl.list[*c.GetPartner().PartnerId()] = c
+}
+
+// CleanupThread runs the loop that runs the cleanup processes periodically.
+func (cl *ConnectionList) CleanupThread() (stoppable.Stoppable, error) {
+	stop := stoppable.NewSingle("StaleConnectionCleanup")
+
+	go func() {
+		jww.INFO.Printf("Starting stale connection cleanup thread to delete "+
+			"connections older than %s. Running every %s.",
+			cl.p.MaxAge, cl.p.CleanupPeriod)
+		ticker := time.NewTicker(cl.p.CleanupPeriod)
+		for {
+			select {
+			case <-stop.Quit():
+				jww.INFO.Print(
+					"Stopping connection cleanup thread: stoppable triggered")
+				ticker.Stop()
+				stop.ToStopped()
+			case <-ticker.C:
+				jww.DEBUG.Print("Starting connection cleanup.")
+				cl.Cleanup()
+			}
+		}
+	}()
+
+	return stop, nil
+}
+
+// Cleanup disconnects all connections that have been stale for longer than the
+// max allowed time.
+func (cl *ConnectionList) Cleanup() {
+	cl.mux.Lock()
+	defer cl.mux.Unlock()
+
+	for partnerID, c := range cl.list {
+		lastUse := c.LastUse()
+		timeSinceLastUse := netTime.Since(lastUse)
+		if timeSinceLastUse > cl.p.MaxAge {
+			err := c.Close()
+			if err != nil {
+				jww.ERROR.Printf(
+					"Could not close connection with partner %s: %+v",
+					partnerID, err)
+			}
+			delete(cl.list, partnerID)
+
+			jww.INFO.Printf("Deleted stale connection for partner %s. "+
+				"Last use was %s ago (%s)",
+				&partnerID, timeSinceLastUse, lastUse.Local())
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Parameters                                                                 //
+////////////////////////////////////////////////////////////////////////////////
+
+// Default values.
+const (
+	cleanupPeriodDefault = 5 * time.Minute
+	maxAgeDefault        = 30 * time.Minute
+)
+
+// ConnectionListParams are the parameters used for the ConnectionList.
+type ConnectionListParams struct {
+	// CleanupPeriod is the duration between when cleanups occur.
+	CleanupPeriod time.Duration
+
+	// MaxAge is the maximum age of an unused connection before it is deleted.
+	MaxAge time.Duration
+}
+
+// DefaultConnectionListParams returns a ConnectionListParams filled with
+// default values.
+func DefaultConnectionListParams() ConnectionListParams {
+	return ConnectionListParams{
+		CleanupPeriod: cleanupPeriodDefault,
+		MaxAge:        maxAgeDefault,
+	}
+}
diff --git a/connect/connectionList_test.go b/connect/connectionList_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b76c6b35876ee6656b099a46b663a75820db08e5
--- /dev/null
+++ b/connect/connectionList_test.go
@@ -0,0 +1,125 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package connect
+
+import (
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests that NewConnectionList returned the expected new ConnectionList.
+func TestNewConnectionList(t *testing.T) {
+	expected := &ConnectionList{
+		list: make(map[id.ID]Connection),
+		p:    DefaultConnectionListParams(),
+	}
+
+	cl := NewConnectionList(expected.p)
+
+	if !reflect.DeepEqual(expected, cl) {
+		t.Errorf("New ConnectionList did not match expected."+
+			"\nexpected: %+v\nreceived: %+v", expected, cl)
+	}
+}
+
+// Tests that ConnectionList.Add adds all the given connections to the list.
+func TestConnectionList_Add(t *testing.T) {
+	cl := NewConnectionList(DefaultConnectionListParams())
+
+	expected := map[id.ID]Connection{
+		*id.NewIdFromString("p1", id.User, t): &handler{
+			partner: &mockPartner{partnerId: id.NewIdFromString("p1", id.User, t)}},
+		*id.NewIdFromString("p2", id.User, t): &handler{
+			partner: &mockPartner{partnerId: id.NewIdFromString("p2", id.User, t)}},
+		*id.NewIdFromString("p3", id.User, t): &handler{
+			partner: &mockPartner{partnerId: id.NewIdFromString("p3", id.User, t)}},
+		*id.NewIdFromString("p4", id.User, t): &handler{
+			partner: &mockPartner{partnerId: id.NewIdFromString("p4", id.User, t)}},
+		*id.NewIdFromString("p5", id.User, t): &handler{
+			partner: &mockPartner{partnerId: id.NewIdFromString("p5", id.User, t)}},
+	}
+
+	for _, c := range expected {
+		cl.Add(c)
+	}
+
+	if !reflect.DeepEqual(expected, cl.list) {
+		t.Errorf("List does not have expected connections."+
+			"\nexpected: %+v\nreceived: %+v", expected, cl.list)
+	}
+
+}
+
+// Tests that ConnectionList.Cleanup deletes only stale connections from the
+// list and that they are closed.
+func TestConnectionList_Cleanup(t *testing.T) {
+	cl := NewConnectionList(DefaultConnectionListParams())
+
+	list := []*mockConnection{
+		{
+			partner: &mockPartner{partnerId: id.NewIdFromString("p0", id.User, t)},
+			lastUse: netTime.Now().Add(-(cl.p.MaxAge * 2)),
+		}, {
+			partner: &mockPartner{partnerId: id.NewIdFromString("p1", id.User, t)},
+			lastUse: netTime.Now().Add(-(cl.p.MaxAge / 2)),
+		}, {
+			partner: &mockPartner{partnerId: id.NewIdFromString("p2", id.User, t)},
+			lastUse: netTime.Now().Add(-(cl.p.MaxAge + 10)),
+		}, {
+			partner: &mockPartner{partnerId: id.NewIdFromString("p3", id.User, t)},
+			lastUse: netTime.Now().Add(-(cl.p.MaxAge - time.Second)),
+		},
+	}
+
+	for _, c := range list {
+		cl.Add(c)
+	}
+
+	cl.Cleanup()
+
+	for i, c := range list {
+		if i%2 == 0 {
+			if _, exists := cl.list[*c.GetPartner().PartnerId()]; exists {
+				t.Errorf("Connection #%d exists while being stale.", i)
+			}
+			if !c.closed {
+				t.Errorf("Connection #%d was not closed.", i)
+			}
+		} else {
+			if _, exists := cl.list[*c.GetPartner().PartnerId()]; !exists {
+				t.Errorf("Connection #%d was removed when it was not stale.", i)
+			}
+			if c.closed {
+				t.Errorf("Connection #%d was closed.", i)
+			}
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Parameters                                                                 //
+////////////////////////////////////////////////////////////////////////////////
+
+// Tests that DefaultConnectionListParams returns a ConnectionListParams with
+// the expected default values.
+func TestDefaultConnectionListParams(t *testing.T) {
+	expected := ConnectionListParams{
+		CleanupPeriod: cleanupPeriodDefault,
+		MaxAge:        maxAgeDefault,
+	}
+
+	p := DefaultConnectionListParams()
+
+	if !reflect.DeepEqual(expected, p) {
+		t.Errorf("Default ConnectionListParams does not match expected."+
+			"\nexpected: %+v\nreceived: %+v", expected, p)
+	}
+}
diff --git a/connect/listenerTracker.go b/connect/listenerTracker.go
new file mode 100644
index 0000000000000000000000000000000000000000..875598dfe53d314cf0052c2ab592ceff425b032c
--- /dev/null
+++ b/connect/listenerTracker.go
@@ -0,0 +1,30 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package connect
+
+import (
+	"gitlab.com/elixxir/client/e2e/receive"
+	"gitlab.com/xx_network/primitives/netTime"
+)
+
+// listenerTracker wraps a listener and updates the last use timestamp on every
+// call to Hear.
+type listenerTracker struct {
+	h *handler
+	l receive.Listener
+}
+
+// Hear updates the last call timestamp and then calls Hear on the wrapped
+// listener.
+func (lt *listenerTracker) Hear(item receive.Message) {
+	lt.h.updateLastUse(netTime.Now())
+	lt.l.Hear(item)
+}
+
+// Name returns a name, used for debugging.
+func (lt *listenerTracker) Name() string { return lt.l.Name() }
diff --git a/connect/listenerTracker_test.go b/connect/listenerTracker_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1cab220ad7955c6b25be5b716fc41197eefc055b
--- /dev/null
+++ b/connect/listenerTracker_test.go
@@ -0,0 +1,74 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package connect
+
+import (
+	"gitlab.com/elixxir/client/e2e/receive"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests that listenerTracker.Hear correctly updates the last use timestamp and
+// calls the wrapped Hear function.
+func Test_listenerTracker_Hear(t *testing.T) {
+	lastUsed, closed := int64(0), uint32(0)
+	itemChan := make(chan receive.Message, 1)
+	lt := &listenerTracker{
+		h: &handler{
+			lastUse: &lastUsed,
+			closed:  &closed,
+		},
+		l: &mockListener{itemChan, "mockListener"},
+	}
+
+	expected := receive.Message{
+		Payload:     []byte("Message payload."),
+		Sender:      id.NewIdFromString("senderID", id.User, t),
+		RecipientID: id.NewIdFromString("RecipientID", id.User, t),
+	}
+
+	lt.Hear(expected)
+
+	select {
+	case r := <-itemChan:
+		if !reflect.DeepEqual(expected, r) {
+			t.Errorf("Did not receive expected receive.Message."+
+				"\nexpected: %+v\nreceived: %+v", expected, r)
+		}
+		if netTime.Since(lt.h.LastUse()) > 300*time.Millisecond {
+			t.Errorf("Last use has incorrect time: %s", lt.h.LastUse())
+		}
+
+	case <-time.After(30 * time.Millisecond):
+		t.Error("Timed out waiting for Hear to be called.")
+	}
+}
+
+// Tests that listenerTracker.Name calls the wrapped listener Name function.
+func Test_listenerTracker_Name(t *testing.T) {
+	expected := "mockListener"
+	lt := &listenerTracker{
+		l: &mockListener{make(chan receive.Message, 1), "mockListener"},
+	}
+
+	if lt.Name() != expected {
+		t.Errorf("Did not get expected name.\nexected: %s\nreceived: %s",
+			expected, lt.Name())
+	}
+}
+
+type mockListener struct {
+	item chan receive.Message
+	name string
+}
+
+func (m *mockListener) Hear(item receive.Message) { m.item <- item }
+func (m *mockListener) Name() string              { return m.name }
diff --git a/connect/utils_test.go b/connect/utils_test.go
index dec043d0433cfe2efe7d6016a45ae61a1c1d1488..6e4be5173d00cd8ba4b671d6abee3a8669e1c77c 100644
--- a/connect/utils_test.go
+++ b/connect/utils_test.go
@@ -22,15 +22,17 @@ import (
 	"gitlab.com/xx_network/crypto/large"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
-	"gitlab.com/xx_network/primitives/ndf"
 	"gitlab.com/xx_network/primitives/netTime"
 	"time"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
-// Mock Partner Interface                                                           //
+// Mock Partner Interface                                                     //
 ////////////////////////////////////////////////////////////////////////////////
 
+// Tests that mockPartner adheres to the partner.Manager interface.
+var _ partner.Manager = (*mockPartner)(nil)
+
 type mockPartner struct {
 	partnerId       *id.ID
 	myID            *id.ID
@@ -48,126 +50,73 @@ func newMockPartner(partnerId, myId *id.ID,
 	}
 }
 
-func (m mockPartner) PartnerId() *id.ID {
-	return m.partnerId
-}
-
-func (m mockPartner) MyId() *id.ID {
-	return m.myID
-}
-
-func (m mockPartner) MyRootPrivateKey() *cyclic.Int {
-	return m.myDhPrivKey
-}
-
-func (m mockPartner) PartnerRootPublicKey() *cyclic.Int {
-	return m.partnerDhPubKey
-}
-
-func (m mockPartner) SendRelationshipFingerprint() []byte {
-	return nil
-}
-
-func (m mockPartner) ReceiveRelationshipFingerprint() []byte {
-	return nil
-}
-
-func (m mockPartner) ConnectionFingerprint() partner.ConnectionFp {
-	return partner.ConnectionFp{}
-}
-
-func (m mockPartner) Contact() contact.Contact {
+func (m *mockPartner) PartnerId() *id.ID                           { return m.partnerId }
+func (m *mockPartner) MyId() *id.ID                                { return m.myID }
+func (m *mockPartner) MyRootPrivateKey() *cyclic.Int               { return m.myDhPrivKey }
+func (m *mockPartner) PartnerRootPublicKey() *cyclic.Int           { return m.partnerDhPubKey }
+func (m *mockPartner) SendRelationshipFingerprint() []byte         { return nil }
+func (m *mockPartner) ReceiveRelationshipFingerprint() []byte      { return nil }
+func (m *mockPartner) ConnectionFingerprint() partner.ConnectionFp { return partner.ConnectionFp{} }
+func (m *mockPartner) Contact() contact.Contact {
 	return contact.Contact{
 		ID:       m.partnerId,
 		DhPubKey: m.partnerDhPubKey,
 	}
 }
-
-func (m mockPartner) PopSendCypher() (session.Cypher, error) {
-	return nil, nil
-}
-
-func (m mockPartner) PopRekeyCypher() (session.Cypher, error) {
-	return nil, nil
-}
-
-func (m mockPartner) NewReceiveSession(partnerPubKey *cyclic.Int, partnerSIDHPubKey *sidh.PublicKey, e2eParams session.Params, source *session.Session) (*session.Session, bool) {
+func (m *mockPartner) PopSendCypher() (session.Cypher, error)  { return nil, nil }
+func (m *mockPartner) PopRekeyCypher() (session.Cypher, error) { return nil, nil }
+func (m *mockPartner) NewReceiveSession(*cyclic.Int, *sidh.PublicKey, session.Params, *session.Session) (*session.Session, bool) {
 	return nil, false
 }
-
-func (m mockPartner) NewSendSession(myDHPrivKey *cyclic.Int, mySIDHPrivateKey *sidh.PrivateKey, e2eParams session.Params, source *session.Session) *session.Session {
-	return nil
-}
-
-func (m mockPartner) GetSendSession(sid session.SessionID) *session.Session {
-	return nil
-}
-
-func (m mockPartner) GetReceiveSession(sid session.SessionID) *session.Session {
-	return nil
-}
-
-func (m mockPartner) Confirm(sid session.SessionID) error {
-	return nil
-}
-
-func (m mockPartner) TriggerNegotiations() []*session.Session {
-	return nil
-}
-
-func (m mockPartner) MakeService(tag string) message.Service {
-	return message.Service{}
-}
-
-func (m mockPartner) Delete() error {
+func (m *mockPartner) NewSendSession(*cyclic.Int, *sidh.PrivateKey, session.Params, *session.Session) *session.Session {
 	return nil
 }
+func (m *mockPartner) GetSendSession(session.SessionID) *session.Session    { return nil }
+func (m *mockPartner) GetReceiveSession(session.SessionID) *session.Session { return nil }
+func (m *mockPartner) Confirm(session.SessionID) error                      { return nil }
+func (m *mockPartner) TriggerNegotiations() []*session.Session              { return nil }
+func (m *mockPartner) MakeService(string) message.Service                   { return message.Service{} }
+func (m *mockPartner) Delete() error                                        { return nil }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Mock Connection Interface                                                           //
+// Mock Connection Interface                                                  //
 ////////////////////////////////////////////////////////////////////////////////
 
+// Tests that mockConnection adheres to the Connection interface.
+var _ Connection = (*mockConnection)(nil)
+
 type mockConnection struct {
 	partner     *mockPartner
 	payloadChan chan []byte
 	listener    server
+	lastUse     time.Time
+	closed      bool
 }
 
-func newMockConnection(partnerId, myId *id.ID,
-	myDhPrivKey, partnerDhPubKey *cyclic.Int) *mockConnection {
+func newMockConnection(partnerId, myId *id.ID, myDhPrivKey,
+	partnerDhPubKey *cyclic.Int) *mockConnection {
 
 	return &mockConnection{
-		partner: newMockPartner(partnerId, myId,
-			myDhPrivKey, partnerDhPubKey),
+		partner:     newMockPartner(partnerId, myId, myDhPrivKey, partnerDhPubKey),
 		payloadChan: make(chan []byte, 1),
 	}
 }
 
-func (m mockConnection) FirstPartitionSize() uint {
-	return 0
-}
-
-func (m mockConnection) SecondPartitionSize() uint {
-	return 0
-}
-
-func (m mockConnection) PartitionSize(payloadIndex uint) uint {
-	return 0
-}
-
-func (m mockConnection) PayloadSize() uint {
-	return 0
-}
+func (m *mockConnection) FirstPartitionSize() uint  { return 0 }
+func (m *mockConnection) SecondPartitionSize() uint { return 0 }
+func (m *mockConnection) PartitionSize(uint) uint   { return 0 }
+func (m *mockConnection) PayloadSize() uint         { return 0 }
 
-func (m mockConnection) Close() error {
+func (m *mockConnection) Close() error {
+	m.closed = true
 	return nil
 }
 
-func (m mockConnection) GetPartner() partner.Manager {
-	return m.partner
-}
+func (m *mockConnection) GetPartner() partner.Manager { return m.partner }
 
-func (m mockConnection) SendE2E(mt catalog.MessageType, payload []byte, params e2e.Params) ([]id.Round, cryptoE2e.MessageID, time.Time, error) {
+func (m *mockConnection) SendE2E(
+	mt catalog.MessageType, payload []byte, _ e2e.Params) (
+	[]id.Round, cryptoE2e.MessageID, time.Time, error) {
 	m.payloadChan <- payload
 	m.listener.Hear(receive.Message{
 		MessageType: mt,
@@ -178,250 +127,86 @@ func (m mockConnection) SendE2E(mt catalog.MessageType, payload []byte, params e
 	return nil, cryptoE2e.MessageID{}, time.Time{}, nil
 }
 
-func (m mockConnection) RegisterListener(messageType catalog.MessageType, newListener receive.Listener) receive.ListenerID {
-	return receive.ListenerID{}
-}
-
-func (m mockConnection) Unregister(listenerID receive.ListenerID) {
-
+func (m *mockConnection) RegisterListener(
+	catalog.MessageType, receive.Listener) (receive.ListenerID, error) {
+	return receive.ListenerID{}, nil
 }
+func (m *mockConnection) Unregister(receive.ListenerID) {}
+func (m *mockConnection) LastUse() time.Time            { return m.lastUse }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Mock cMix                                                           //
+// Mock cMix                                                                  //
 ////////////////////////////////////////////////////////////////////////////////
 
+// Tests that mockCmix adheres to the cmix.Client interface.
+var _ cmix.Client = (*mockCmix)(nil)
+
 type mockCmix struct {
 	instance *network.Instance
 }
 
 func newMockCmix() *mockCmix {
-
 	return &mockCmix{}
 }
 
-func (m *mockCmix) Follow(report cmix.ClientErrorReport) (stoppable.Stoppable, error) {
-	return nil, nil
-}
+func (m *mockCmix) Follow(cmix.ClientErrorReport) (stoppable.Stoppable, error) { return nil, nil }
 
-func (m *mockCmix) GetMaxMessageLength() int {
-	return 4096
-}
-
-func (m *mockCmix) Send(recipient *id.ID, fingerprint format.Fingerprint,
-	service message.Service, payload, mac []byte,
-	cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error) {
+func (m *mockCmix) GetMaxMessageLength() int { return 4096 }
 
+func (m *mockCmix) Send(*id.ID, format.Fingerprint, message.Service, []byte,
+	[]byte, cmix.CMIXParams) (id.Round, ephemeral.Id, error) {
 	return 0, ephemeral.Id{}, nil
 }
-
-func (m *mockCmix) SendMany(messages []cmix.TargetedCmixMessage, p cmix.CMIXParams) (id.Round, []ephemeral.Id, error) {
+func (m *mockCmix) SendMany([]cmix.TargetedCmixMessage, cmix.CMIXParams) (id.Round, []ephemeral.Id, error) {
 	return 0, []ephemeral.Id{}, nil
 }
-
-func (m *mockCmix) AddIdentity(id *id.ID, validUntil time.Time, persistent bool) {
-}
-
-func (m *mockCmix) RemoveIdentity(id *id.ID) {
-}
-
-func (m *mockCmix) GetIdentity(get *id.ID) (identity.TrackedID, error) {
-	return identity.TrackedID{
-		Creation: netTime.Now().Add(-time.Minute),
-	}, nil
-}
-
-func (m *mockCmix) AddFingerprint(identity *id.ID, fp format.Fingerprint, mp message.Processor) error {
-	return nil
-}
-
-func (m *mockCmix) DeleteFingerprint(identity *id.ID, fingerprint format.Fingerprint) {
-	return
-}
-
-func (m *mockCmix) DeleteClientFingerprints(identity *id.ID) {
-	return
-}
-
-func (m *mockCmix) AddService(clientID *id.ID, newService message.Service, response message.Processor) {
-	return
-}
-
-func (m *mockCmix) DeleteService(clientID *id.ID, toDelete message.Service, processor message.Processor) {
-	return
-}
-
-func (m *mockCmix) DeleteClientService(clientID *id.ID) {
-}
-
-func (m *mockCmix) TrackServices(tracker message.ServicesTracker) {
-	return
-}
-
-func (m *mockCmix) CheckInProgressMessages() {
-	return
-}
-
-func (m *mockCmix) IsHealthy() bool {
-	return true
-}
-
-func (m *mockCmix) WasHealthy() bool {
-	return true
-}
-
-func (m *mockCmix) AddHealthCallback(f func(bool)) uint64 {
-	return 0
-}
-
-func (m *mockCmix) RemoveHealthCallback(u uint64) {
-	return
-}
-
-func (m *mockCmix) HasNode(nid *id.ID) bool {
-	return true
-}
-
-func (m *mockCmix) NumRegisteredNodes() int {
-	return 24
-}
-
-func (m *mockCmix) TriggerNodeRegistration(nid *id.ID) {
-	return
-}
-
-func (m *mockCmix) GetRoundResults(timeout time.Duration, roundCallback cmix.RoundEventCallback, roundList ...id.Round) error {
+func (m *mockCmix) AddIdentity(*id.ID, time.Time, bool) {}
+func (m *mockCmix) RemoveIdentity(*id.ID)               {}
+
+func (m *mockCmix) GetIdentity(*id.ID) (identity.TrackedID, error) {
+	return identity.TrackedID{Creation: netTime.Now().Add(-time.Minute)}, nil
+}
+
+func (m *mockCmix) AddFingerprint(*id.ID, format.Fingerprint, message.Processor) error { return nil }
+func (m *mockCmix) DeleteFingerprint(*id.ID, format.Fingerprint)                       {}
+func (m *mockCmix) DeleteClientFingerprints(*id.ID)                                    {}
+func (m *mockCmix) AddService(*id.ID, message.Service, message.Processor)              {}
+func (m *mockCmix) DeleteService(*id.ID, message.Service, message.Processor)           {}
+func (m *mockCmix) DeleteClientService(*id.ID)                                         {}
+func (m *mockCmix) TrackServices(message.ServicesTracker)                              {}
+func (m *mockCmix) CheckInProgressMessages()                                           {}
+func (m *mockCmix) IsHealthy() bool                                                    { return true }
+func (m *mockCmix) WasHealthy() bool                                                   { return true }
+func (m *mockCmix) AddHealthCallback(func(bool)) uint64                                { return 0 }
+func (m *mockCmix) RemoveHealthCallback(uint64)                                        {}
+func (m *mockCmix) HasNode(*id.ID) bool                                                { return true }
+func (m *mockCmix) NumRegisteredNodes() int                                            { return 24 }
+func (m *mockCmix) TriggerNodeRegistration(*id.ID)                                     {}
+
+func (m *mockCmix) GetRoundResults(_ time.Duration, roundCallback cmix.RoundEventCallback, _ ...id.Round) error {
 	roundCallback(true, false, nil)
 	return nil
 }
 
-func (m *mockCmix) LookupHistoricalRound(rid id.Round, callback rounds.RoundResultCallback) error {
-	return nil
-}
-
-func (m *mockCmix) SendToAny(sendFunc func(host *connect.Host) (interface{}, error), stop *stoppable.Single) (interface{}, error) {
+func (m *mockCmix) LookupHistoricalRound(id.Round, rounds.RoundResultCallback) error { return nil }
+func (m *mockCmix) SendToAny(func(host *connect.Host) (interface{}, error), *stoppable.Single) (interface{}, error) {
 	return nil, nil
 }
-
-func (m *mockCmix) SendToPreferred(targets []*id.ID, sendFunc gateway.SendToPreferredFunc, stop *stoppable.Single, timeout time.Duration) (interface{}, error) {
-	return nil, nil
-}
-
-func (m *mockCmix) SetGatewayFilter(f gateway.Filter) {
-	return
-}
-
-func (m *mockCmix) GetHostParams() connect.HostParams {
-	return connect.GetDefaultHostParams()
-}
-
-func (m *mockCmix) GetAddressSpace() uint8 {
-	return 32
-}
-
-func (m *mockCmix) RegisterAddressSpaceNotification(tag string) (chan uint8, error) {
+func (m *mockCmix) SendToPreferred([]*id.ID, gateway.SendToPreferredFunc, *stoppable.Single, time.Duration) (interface{}, error) {
 	return nil, nil
 }
-
-func (m *mockCmix) UnregisterAddressSpaceNotification(tag string) {
-	return
-}
-
-func (m *mockCmix) GetInstance() *network.Instance {
-	return m.instance
-}
-
-func (m *mockCmix) GetVerboseRounds() string {
-	return ""
-}
+func (m *mockCmix) SetGatewayFilter(gateway.Filter)                             {}
+func (m *mockCmix) GetHostParams() connect.HostParams                           { return connect.GetDefaultHostParams() }
+func (m *mockCmix) GetAddressSpace() uint8                                      { return 32 }
+func (m *mockCmix) RegisterAddressSpaceNotification(string) (chan uint8, error) { return nil, nil }
+func (m *mockCmix) UnregisterAddressSpaceNotification(string)                   {}
+func (m *mockCmix) GetInstance() *network.Instance                              { return m.instance }
+func (m *mockCmix) GetVerboseRounds() string                                    { return "" }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Misc set-up utils                                                          //
 ////////////////////////////////////////////////////////////////////////////////
 
-var testCert = `-----BEGIN CERTIFICATE-----
-MIIF4DCCA8igAwIBAgIUegUvihtQooWNIzsNqj6lucXn6g8wDQYJKoZIhvcNAQEL
-BQAwgYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJQ2xhcmVt
-b250MRAwDgYDVQQKDAdFbGl4eGlyMRQwEgYDVQQLDAtEZXZlbG9wbWVudDETMBEG
-A1UEAwwKZWxpeHhpci5pbzEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZWxpeHhpci5p
-bzAeFw0yMTExMzAxODMwMTdaFw0zMTExMjgxODMwMTdaMIGMMQswCQYDVQQGEwJV
-UzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUNsYXJlbW9udDEQMA4GA1UECgwHRWxp
-eHhpcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxEzARBgNVBAMMCmVsaXh4aXIuaW8x
-HzAdBgkqhkiG9w0BCQEWEGFkbWluQGVsaXh4aXIuaW8wggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQCckGabzUitkySleveyD9Yrxrpj50FiGkOvwkmgN1jF
-9r5StN3otiU5tebderkjD82mVqB781czRA9vPqAggbw1ZdAyQPTvDPTj7rmzkByq
-QIkdZBMshV/zX1z8oXoNB9bzZlUFVF4HTY3dEytAJONJRkGGAw4FTa/wCkWsITiT
-mKvkP3ciKgz7s8uMyZzZpj9ElBphK9Nbwt83v/IOgTqDmn5qDBnHtoLw4roKJkC8
-00GF4ZUhlVSQC3oFWOCu6tvSUVCBCTUzVKYJLmCnoilmiE/8nCOU0VOivtsx88f5
-9RSPfePUk8u5CRmgThwOpxb0CAO0gd+sY1YJrn+FaW+dSR8OkM3bFuTq7fz9CEkS
-XFfUwbJL+HzT0ZuSA3FupTIExyDmM/5dF8lC0RB3j4FNQF+H+j5Kso86e83xnXPI
-e+IKKIYa/LVdW24kYRuBDpoONN5KS/F+F/5PzOzH9Swdt07J9b7z1dzWcLnKGtkN
-WVsZ7Ue6cuI2zOEWqF1OEr9FladgORcdVBoF/WlsA63C2c1J0tjXqqcl/27GmqGW
-gvhaA8Jkm20qLCEhxQ2JzrBdk/X/lCZdP/7A5TxnLqSBq8xxMuLJlZZbUG8U/BT9
-sHF5mXZyiucMjTEU7qHMR2UGNFot8TQ7ZXntIApa2NlB/qX2qI5D13PoXI9Hnyxa
-8wIDAQABozgwNjAVBgNVHREEDjAMggplbGl4eGlyLmlvMB0GA1UdDgQWBBQimFud
-gCzDVFD3Xz68zOAebDN6YDANBgkqhkiG9w0BAQsFAAOCAgEAccsH9JIyFZdytGxC
-/6qjSHPgV23ZGmW7alg+GyEATBIAN187Du4Lj6cLbox5nqLdZgYzizVop32JQAHv
-N1QPKjViOOkLaJprSUuRULa5kJ5fe+XfMoyhISI4mtJXXbMwl/PbOaDSdeDjl0ZO
-auQggWslyv8ZOkfcbC6goEtAxljNZ01zY1ofSKUj+fBw9Lmomql6GAt7NuubANs4
-9mSjXwD27EZf3Aqaaju7gX1APW2O03/q4hDqhrGW14sN0gFt751ddPuPr5COGzCS
-c3Xg2HqMpXx//FU4qHrZYzwv8SuGSshlCxGJpWku9LVwci1Kxi4LyZgTm6/xY4kB
-5fsZf6C2yAZnkIJ8bEYr0Up4KzG1lNskU69uMv+d7W2+4Ie3Evf3HdYad/WeUskG
-tc6LKY6B2NX3RMVkQt0ftsDaWsktnR8VBXVZSBVYVEQu318rKvYRdOwZJn339obI
-jyMZC/3D721e5Anj/EqHpc3I9Yn3jRKw1xc8kpNLg/JIAibub8JYyDvT1gO4xjBO
-+6EWOBFgDAsf7bSP2xQn1pQFWcA/sY1MnRsWeENmKNrkLXffP+8l1tEcijN+KCSF
-ek1mr+qBwSaNV9TA+RXVhvqd3DEKPPJ1WhfxP1K81RdUESvHOV/4kdwnSahDyao0
-EnretBzQkeKeBwoB2u6NTiOmUjk=
------END CERTIFICATE-----
-`
-
-func getNDF() *ndf.NetworkDefinition {
-	return &ndf.NetworkDefinition{
-		UDB: ndf.UDB{
-			ID:       id.DummyUser.Bytes(),
-			Cert:     testCert,
-			Address:  "address",
-			DhPubKey: []byte{123, 34, 86, 97, 108, 117, 101, 34, 58, 53, 48, 49, 53, 53, 53, 52, 54, 53, 49, 48, 54, 49, 56, 57, 53, 54, 51, 48, 54, 52, 49, 51, 53, 49, 57, 56, 55, 57, 52, 57, 50, 48, 56, 49, 52, 57, 52, 50, 57, 51, 57, 53, 49, 50, 51, 54, 52, 56, 49, 57, 55, 48, 50, 50, 49, 48, 55, 55, 50, 52, 52, 48, 49, 54, 57, 52, 55, 52, 57, 53, 53, 56, 55, 54, 50, 57, 53, 57, 53, 48, 54, 55, 57, 55, 48, 53, 48, 48, 54, 54, 56, 49, 57, 50, 56, 48, 52, 48, 53, 51, 50, 48, 57, 55, 54, 56, 56, 53, 57, 54, 57, 56, 57, 49, 48, 54, 56, 54, 50, 52, 50, 52, 50, 56, 49, 48, 51, 51, 51, 54, 55, 53, 55, 54, 52, 51, 54, 55, 54, 56, 53, 56, 48, 55, 56, 49, 52, 55, 49, 52, 53, 49, 52, 52, 52, 52, 53, 51, 57, 57, 51, 57, 57, 53, 50, 52, 52, 53, 51, 56, 48, 49, 48, 54, 54, 55, 48, 52, 50, 49, 55, 54, 57, 53, 57, 57, 57, 51, 52, 48, 54, 54, 54, 49, 50, 48, 54, 56, 57, 51, 54, 57, 48, 52, 55, 55, 54, 50, 49, 49, 56, 56, 53, 51, 50, 57, 57, 50, 54, 53, 48, 52, 57, 51, 54, 55, 54, 48, 57, 56, 56, 49, 55, 52, 52, 57, 53, 57, 54, 53, 50, 55, 53, 52, 52, 52, 49, 57, 55, 49, 54, 50, 52, 52, 56, 50, 55, 55, 50, 49, 48, 53, 56, 56, 57, 54, 51, 53, 54, 54, 53, 53, 53, 53, 49, 56, 50, 53, 49, 49, 50, 57, 50, 48, 49, 56, 48, 48, 54, 49, 56, 57, 48, 55, 48, 51, 53, 51, 51, 56, 57, 52, 49, 50, 57, 49, 55, 50, 56, 55, 57, 57, 52, 55, 53, 51, 49, 55, 55, 48, 53, 55, 55, 49, 50, 51, 57, 49, 51, 55, 54, 48, 50, 49, 55, 50, 54, 54, 52, 56, 52, 48, 48, 54, 48, 52, 48, 53, 56, 56, 53, 54, 52, 56, 56, 49, 52, 52, 51, 57, 56, 51, 51, 57, 54, 55, 48, 49, 53, 55, 52, 53, 50, 56, 51, 49, 51, 48, 53, 52, 49, 49, 49, 49, 49, 56, 51, 53, 52, 52, 52, 52, 48, 53, 54, 57, 48, 54, 52, 56, 57, 52, 54, 53, 50, 56, 51, 53, 50, 48, 48, 50, 48, 48, 49, 50, 51, 51, 48, 48, 53, 48, 49, 50, 52, 56, 57, 48, 49, 51, 54, 55, 52, 57, 55, 50, 49, 48, 55, 53, 54, 49, 50, 52, 52, 57, 55, 48, 50, 56, 55, 55, 51, 51, 50, 53, 50, 48, 57, 52, 56, 57, 49, 49, 56, 49, 54, 57, 50, 55, 50, 51, 57, 51, 57, 54, 50, 56, 48, 54, 54, 49, 57, 55, 48, 50, 48, 57, 49, 51, 54, 50, 49, 50, 53, 50, 54, 50, 53, 53, 55, 57, 54, 51, 56, 49, 57, 48, 51, 49, 54, 54, 53, 51, 56, 56, 49, 48, 56, 48, 51, 57, 53, 49, 53, 53, 55, 49, 53, 57, 48, 57, 57, 55, 49, 56, 53, 55, 54, 48, 50, 54, 48, 49, 55, 57, 52, 55, 53, 51, 57, 49, 51, 53, 52, 49, 48, 50, 49, 55, 52, 51, 57, 48, 50, 56, 48, 50, 51, 53, 51, 54, 56, 49, 56, 50, 49, 55, 50, 57, 52, 51, 49, 56, 48, 56, 56, 50, 51, 53, 52, 56, 55, 49, 52, 55, 53, 50, 56, 48, 57, 55, 49, 53, 48, 48, 51, 50, 48, 57, 50, 50, 53, 50, 56, 51, 57, 55, 57, 49, 57, 50, 53, 56, 51, 55, 48, 51, 57, 54, 48, 50, 55, 54, 48, 54, 57, 55, 52, 53, 54, 52, 51, 56, 52, 53, 54, 48, 51, 57, 55, 55, 55, 49, 53, 57, 57, 49, 57, 52, 57, 56, 56, 54, 56, 50, 49, 49, 54, 56, 55, 56, 55, 51, 51, 57, 52, 53, 49, 52, 52, 55, 57, 53, 57, 49, 57, 52, 48, 51, 53, 49, 49, 49, 51, 48, 53, 54, 54, 50, 49, 56, 57, 52, 55, 50, 49, 54, 53, 57, 53, 50, 57, 50, 48, 51, 51, 52, 48, 56, 55, 54, 50, 49, 49, 49, 56, 53, 54, 57, 51, 57, 50, 53, 48, 53, 56, 56, 55, 56, 53, 54, 55, 51, 56, 55, 50, 53, 57, 56, 52, 54, 53, 49, 51, 50, 54, 51, 50, 48, 56, 56, 57, 52, 53, 57, 53, 56, 57, 57, 54, 52, 55, 55, 50, 57, 51, 51, 52, 55, 51, 48, 52, 56, 56, 50, 51, 50, 52, 53, 48, 51, 50, 56, 56, 50, 49, 55, 51, 51, 53, 54, 55, 51, 50, 51, 52, 56, 53, 52, 55, 48, 51, 56, 50, 51, 49, 53, 55, 52, 53, 53, 48, 55, 55, 56, 55, 48, 50, 51, 52, 50, 53, 52, 51, 48, 57, 56, 56, 54, 56, 54, 49, 57, 54, 48, 55, 55, 52, 57, 55, 56, 51, 48, 51, 57, 49, 55, 52, 49, 51, 49, 54, 57, 54, 50, 49, 52, 50, 55, 57, 55, 56, 56, 51, 49, 55, 51, 50, 54, 56, 49, 56, 53, 57, 48, 49, 49, 53, 48, 52, 53, 51, 51, 56, 52, 57, 57, 55, 54, 51, 55, 55, 48, 55, 49, 52, 50, 49, 54, 48, 49, 54, 52, 49, 57, 53, 56, 49, 54, 50, 55, 49, 52, 49, 52, 56, 49, 51, 52, 50, 53, 56, 55, 53, 57, 55, 52, 49, 57, 49, 55, 51, 55, 49, 51, 57, 54, 51, 49, 51, 49, 56, 53, 50, 49, 53, 52, 49, 51, 44, 34, 70, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, 34, 58, 49, 54, 56, 48, 49, 53, 52, 49, 53, 49, 49, 50, 51, 51, 48, 57, 56, 51, 54, 51, 125},
-		},
-		E2E: ndf.Group{
-			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" +
-				"8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" +
-				"D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" +
-				"75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" +
-				"6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" +
-				"4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" +
-				"6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" +
-				"448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" +
-				"198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" +
-				"DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" +
-				"631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" +
-				"3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" +
-				"19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" +
-				"5873847AEF49F66E43873",
-			Generator: "2",
-		},
-		CMIX: ndf.Group{
-			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" +
-				"F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" +
-				"264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" +
-				"9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" +
-				"B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" +
-				"0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" +
-				"92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" +
-				"2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" +
-				"995FAD5AABBCFBE3EDA2741E375404AE25B",
-			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" +
-				"9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" +
-				"1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" +
-				"8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" +
-				"C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" +
-				"5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" +
-				"59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" +
-				"2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" +
-				"B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
-		},
-	}
-}
-
 func getGroup() *cyclic.Group {
 	return cyclic.NewGroup(
 		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D4941"+
diff --git a/e2e/interface.go b/e2e/interface.go
index d9710650594926f528985ddf5f7aa1c41365d74c..c4c6b68af6ccacfc85876ce964120a5868e3e4a2 100644
--- a/e2e/interface.go
+++ b/e2e/interface.go
@@ -103,6 +103,10 @@ type Handler interface {
 	// will no longer get called
 	Unregister(listenerID receive.ListenerID)
 
+	// UnregisterUserListeners removes all the listeners registered with the
+	// specified user.
+	UnregisterUserListeners(userID *id.ID)
+
 	/* === Partners ===================================================== */
 
 	// AddPartner adds a partner. Automatically creates both send
diff --git a/e2e/receive/byID.go b/e2e/receive/byID.go
index 625aa05db6958642b25961e18daf5196d3dd8659..425cd036e6b7c0bccc307621a709b08098b5ba2e 100644
--- a/e2e/receive/byID.go
+++ b/e2e/receive/byID.go
@@ -44,13 +44,13 @@ func (bi *byId) Get(uid *id.ID) *set.Set {
 
 // adds a listener to a set for the given ID. Creates a new set to add it to if
 // the set does not exist
-func (bi *byId) Add(uid *id.ID, l Listener) *set.Set {
-	s, ok := bi.list[*uid]
+func (bi *byId) Add(lid ListenerID) *set.Set {
+	s, ok := bi.list[*lid.userID]
 	if !ok {
-		s = set.New(l)
-		bi.list[*uid] = s
+		s = set.New(lid)
+		bi.list[*lid.userID] = s
 	} else {
-		s.Insert(l)
+		s.Insert(lid)
 	}
 
 	return s
@@ -58,13 +58,20 @@ func (bi *byId) Add(uid *id.ID, l Listener) *set.Set {
 
 // Removes the passed listener from the set for UserID and
 // deletes the set if it is empty if the ID is not a generic one
-func (bi *byId) Remove(uid *id.ID, l Listener) {
-	s, ok := bi.list[*uid]
+func (bi *byId) Remove(lid ListenerID) {
+	s, ok := bi.list[*lid.userID]
 	if ok {
-		s.Remove(l)
+		s.Remove(lid)
 
-		if s.Len() == 0 && !uid.Cmp(AnyUser()) && !uid.Cmp(&id.ID{}) {
-			delete(bi.list, *uid)
+		if s.Len() == 0 && !lid.userID.Cmp(AnyUser()) && !lid.userID.Cmp(&id.ID{}) {
+			delete(bi.list, *lid.userID)
 		}
 	}
 }
+
+// RemoveId removes all listeners registered for the given user ID.
+func (bi *byId) RemoveId(uid *id.ID) {
+	if !uid.Cmp(AnyUser()) && !uid.Cmp(&id.ID{}) {
+		delete(bi.list, *uid)
+	}
+}
diff --git a/e2e/receive/byID_test.go b/e2e/receive/byID_test.go
index 750c1bc6c958d76b56315f0ad10f7bdd954e63bd..e8aebd2777242ff1e910876d0f2d964f493c3bc7 100644
--- a/e2e/receive/byID_test.go
+++ b/e2e/receive/byID_test.go
@@ -120,9 +120,9 @@ func TestById_Add_New(t *testing.T) {
 
 	uid := id.NewIdFromUInt(42, id.User, t)
 
-	l := &funcListener{}
+	lid := ListenerID{uid, 1, &funcListener{}}
 
-	nbi.Add(uid, l)
+	nbi.Add(lid)
 
 	s := nbi.list[*uid]
 
@@ -130,7 +130,7 @@ func TestById_Add_New(t *testing.T) {
 		t.Errorf("Should a set of the wrong size")
 	}
 
-	if !s.Has(l) {
+	if !s.Has(lid) {
 		t.Errorf("Wrong set returned")
 	}
 }
@@ -142,26 +142,26 @@ func TestById_Add_Old(t *testing.T) {
 
 	uid := id.NewIdFromUInt(42, id.User, t)
 
-	l1 := &funcListener{}
-	l2 := &funcListener{}
+	lid1 := ListenerID{uid, 1, &funcListener{}}
+	lid2 := ListenerID{uid, 1, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbi.list[*uid] = set1
 
-	nbi.Add(uid, l2)
+	nbi.Add(lid2)
 
 	s := nbi.list[*uid]
 
 	if s.Len() != 2 {
-		t.Errorf("Should have returned a set")
+		t.Errorf("Incorrect set length.\nexpected: %d\nreceived: %d", 2, s.Len())
 	}
 
-	if !s.Has(l1) {
+	if !s.Has(lid1) {
 		t.Errorf("Set does not include the initial listener")
 	}
 
-	if !s.Has(l2) {
+	if !s.Has(lid2) {
 		t.Errorf("Set does not include the new listener")
 	}
 }
@@ -171,11 +171,11 @@ func TestById_Add_Old(t *testing.T) {
 func TestById_Add_Generic(t *testing.T) {
 	nbi := newById()
 
-	l1 := &funcListener{}
-	l2 := &funcListener{}
+	lid1 := ListenerID{&id.ID{}, 1, &funcListener{}}
+	lid2 := ListenerID{AnyUser(), 1, &funcListener{}}
 
-	nbi.Add(&id.ID{}, l1)
-	nbi.Add(AnyUser(), l2)
+	nbi.Add(lid1)
+	nbi.Add(lid2)
 
 	s := nbi.generic
 
@@ -183,11 +183,11 @@ func TestById_Add_Generic(t *testing.T) {
 		t.Errorf("Should have returned a set of size 2")
 	}
 
-	if !s.Has(l1) {
+	if !s.Has(lid1) {
 		t.Errorf("Set does not include the ZeroUser listener")
 	}
 
-	if !s.Has(l2) {
+	if !s.Has(lid2) {
 		t.Errorf("Set does not include the empty user listener")
 	}
 }
@@ -199,14 +199,14 @@ func TestById_Remove_ManyInSet(t *testing.T) {
 
 	uid := id.NewIdFromUInt(42, id.User, t)
 
-	l1 := &funcListener{}
-	l2 := &funcListener{}
+	lid1 := ListenerID{uid, 1, &funcListener{}}
+	lid2 := ListenerID{uid, 1, &funcListener{}}
 
-	set1 := set.New(l1, l2)
+	set1 := set.New(lid1, lid2)
 
 	nbi.list[*uid] = set1
 
-	nbi.Remove(uid, l1)
+	nbi.Remove(lid1)
 
 	if _, ok := nbi.list[*uid]; !ok {
 		t.Errorf("Set removed when it should not have been")
@@ -217,11 +217,11 @@ func TestById_Remove_ManyInSet(t *testing.T) {
 			set1.Len())
 	}
 
-	if set1.Has(l1) {
+	if set1.Has(lid1) {
 		t.Errorf("Listener 1 still in set, it should not be")
 	}
 
-	if !set1.Has(l2) {
+	if !set1.Has(lid2) {
 		t.Errorf("Listener 2 not still in set, it should be")
 	}
 
@@ -234,13 +234,13 @@ func TestById_Remove_SingleInSet(t *testing.T) {
 
 	uid := id.NewIdFromUInt(42, id.User, t)
 
-	l1 := &funcListener{}
+	lid1 := ListenerID{uid, 1, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbi.list[*uid] = set1
 
-	nbi.Remove(uid, l1)
+	nbi.Remove(lid1)
 
 	if _, ok := nbi.list[*uid]; ok {
 		t.Errorf("Set not removed when it should have been")
@@ -251,7 +251,7 @@ func TestById_Remove_SingleInSet(t *testing.T) {
 			set1.Len())
 	}
 
-	if set1.Has(l1) {
+	if set1.Has(lid1) {
 		t.Errorf("Listener 1 still in set, it should not be")
 	}
 }
@@ -263,13 +263,13 @@ func TestById_Remove_SingleInSet_ZeroUser(t *testing.T) {
 
 	uid := &id.ZeroUser
 
-	l1 := &funcListener{}
+	lid1 := ListenerID{uid, 1, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbi.list[*uid] = set1
 
-	nbi.Remove(uid, l1)
+	nbi.Remove(lid1)
 
 	if _, ok := nbi.list[*uid]; !ok {
 		t.Errorf("Set removed when it should not have been")
@@ -280,7 +280,7 @@ func TestById_Remove_SingleInSet_ZeroUser(t *testing.T) {
 			set1.Len())
 	}
 
-	if set1.Has(l1) {
+	if set1.Has(lid1) {
 		t.Errorf("Listener 1 still in set, it should not be")
 	}
 }
@@ -292,13 +292,13 @@ func TestById_Remove_SingleInSet_EmptyUser(t *testing.T) {
 
 	uid := &id.ID{}
 
-	l1 := &funcListener{}
+	lid1 := ListenerID{uid, 1, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbi.list[*uid] = set1
 
-	nbi.Remove(uid, l1)
+	nbi.Remove(lid1)
 
 	if _, ok := nbi.list[*uid]; !ok {
 		t.Errorf("Set removed when it should not have been")
@@ -309,7 +309,7 @@ func TestById_Remove_SingleInSet_EmptyUser(t *testing.T) {
 			set1.Len())
 	}
 
-	if set1.Has(l1) {
+	if set1.Has(lid1) {
 		t.Errorf("Listener 1 still in set, it should not be")
 	}
 }
diff --git a/e2e/receive/byType.go b/e2e/receive/byType.go
index 0ec3f6b4502a2f8ece67599ea95d0e65dabe8eb0..183e2da360b5c03fceba294bcc578b6c73f6fb86 100644
--- a/e2e/receive/byType.go
+++ b/e2e/receive/byType.go
@@ -45,13 +45,13 @@ func (bt *byType) Get(messageType catalog.MessageType) *set.Set {
 
 // adds a listener to a set for the given messageType. Creates a new set to add
 // it to if the set does not exist
-func (bt *byType) Add(messageType catalog.MessageType, r Listener) *set.Set {
-	s, ok := bt.list[messageType]
+func (bt *byType) Add(lid ListenerID) *set.Set {
+	s, ok := bt.list[lid.messageType]
 	if !ok {
-		s = set.New(r)
-		bt.list[messageType] = s
+		s = set.New(lid)
+		bt.list[lid.messageType] = s
 	} else {
-		s.Insert(r)
+		s.Insert(lid)
 	}
 
 	return s
@@ -59,13 +59,13 @@ func (bt *byType) Add(messageType catalog.MessageType, r Listener) *set.Set {
 
 // Removes the passed listener from the set for messageType and
 // deletes the set if it is empty and the type is not AnyType
-func (bt *byType) Remove(mt catalog.MessageType, l Listener) {
-	s, ok := bt.list[mt]
+func (bt *byType) Remove(lid ListenerID) {
+	s, ok := bt.list[lid.messageType]
 	if ok {
-		s.Remove(l)
+		s.Remove(lid)
 
-		if s.Len() == 0 && mt != AnyType {
-			delete(bt.list, mt)
+		if s.Len() == 0 && lid.messageType != AnyType {
+			delete(bt.list, lid.messageType)
 		}
 	}
 }
diff --git a/e2e/receive/byType_test.go b/e2e/receive/byType_test.go
index 30f3c45bd005d922f44190e20a3b3f48bd31188f..3ca6127d222f2b7b9038c7fbe7d2ac389c833969 100644
--- a/e2e/receive/byType_test.go
+++ b/e2e/receive/byType_test.go
@@ -10,6 +10,7 @@ package receive
 import (
 	"github.com/golang-collections/collections/set"
 	"gitlab.com/elixxir/client/catalog"
+	"gitlab.com/xx_network/primitives/id"
 	"testing"
 )
 
@@ -108,9 +109,9 @@ func TestByType_Add_New(t *testing.T) {
 
 	m := catalog.MessageType(42)
 
-	l := &funcListener{}
+	l := ListenerID{&id.ZeroUser, m, &funcListener{}}
 
-	nbt.Add(m, l)
+	nbt.Add(l)
 
 	s := nbt.list[m]
 
@@ -130,14 +131,14 @@ func TestByType_Add_Old(t *testing.T) {
 
 	m := catalog.MessageType(42)
 
-	l1 := &funcListener{}
-	l2 := &funcListener{}
+	lid1 := ListenerID{&id.ZeroUser, m, &funcListener{}}
+	lid2 := ListenerID{&id.ZeroUser, m, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbt.list[m] = set1
 
-	nbt.Add(m, l2)
+	nbt.Add(lid2)
 
 	s := nbt.list[m]
 
@@ -145,11 +146,11 @@ func TestByType_Add_Old(t *testing.T) {
 		t.Errorf("Should have returned a set")
 	}
 
-	if !s.Has(l1) {
+	if !s.Has(lid1) {
 		t.Errorf("Set does not include the initial listener")
 	}
 
-	if !s.Has(l2) {
+	if !s.Has(lid2) {
 		t.Errorf("Set does not include the new listener")
 	}
 }
@@ -159,9 +160,9 @@ func TestByType_Add_Old(t *testing.T) {
 func TestByType_Add_Generic(t *testing.T) {
 	nbt := newByType()
 
-	l1 := &funcListener{}
+	lid1 := ListenerID{&id.ZeroUser, AnyType, &funcListener{}}
 
-	nbt.Add(AnyType, l1)
+	nbt.Add(lid1)
 
 	s := nbt.generic
 
@@ -169,7 +170,7 @@ func TestByType_Add_Generic(t *testing.T) {
 		t.Errorf("Should have returned a set of size 2")
 	}
 
-	if !s.Has(l1) {
+	if !s.Has(lid1) {
 		t.Errorf("Set does not include the ZeroUser listener")
 	}
 }
@@ -181,13 +182,13 @@ func TestByType_Remove_SingleInSet(t *testing.T) {
 
 	m := catalog.MessageType(42)
 
-	l1 := &funcListener{}
+	lid1 := ListenerID{&id.ZeroUser, m, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbt.list[m] = set1
 
-	nbt.Remove(m, l1)
+	nbt.Remove(lid1)
 
 	if _, ok := nbt.list[m]; ok {
 		t.Errorf("Set not removed when it should have been")
@@ -198,7 +199,7 @@ func TestByType_Remove_SingleInSet(t *testing.T) {
 			set1.Len())
 	}
 
-	if set1.Has(l1) {
+	if set1.Has(lid1) {
 		t.Errorf("Listener 1 still in set, it should not be")
 	}
 }
@@ -210,13 +211,13 @@ func TestByType_Remove_SingleInSet_AnyType(t *testing.T) {
 
 	m := AnyType
 
-	l1 := &funcListener{}
+	lid1 := ListenerID{&id.ZeroUser, m, &funcListener{}}
 
-	set1 := set.New(l1)
+	set1 := set.New(lid1)
 
 	nbt.list[m] = set1
 
-	nbt.Remove(m, l1)
+	nbt.Remove(lid1)
 
 	if _, ok := nbt.list[m]; !ok {
 		t.Errorf("Set removed when it should not have been")
@@ -227,7 +228,7 @@ func TestByType_Remove_SingleInSet_AnyType(t *testing.T) {
 			set1.Len())
 	}
 
-	if set1.Has(l1) {
+	if set1.Has(lid1) {
 		t.Errorf("Listener 1 still in set, it should not be")
 	}
 }
diff --git a/e2e/receive/listener.go b/e2e/receive/listener.go
index 2bef0525c185a291ee861d9447dd9e18824a6109..5e1d34067c5b7709509c18de6000103d73107687 100644
--- a/e2e/receive/listener.go
+++ b/e2e/receive/listener.go
@@ -11,6 +11,8 @@ import (
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/catalog"
 	"gitlab.com/xx_network/primitives/id"
+	"strconv"
+	"strings"
 )
 
 //Listener interface for a listener adhere to
@@ -50,6 +52,19 @@ func (lid ListenerID) GetName() string {
 	return lid.listener.Name()
 }
 
+// String returns the values in the ListenerID in a human-readable format. This
+// functions adheres to the fmt.Stringer interface.
+func (lid ListenerID) String() string {
+	str := []string{
+		"userID:" + lid.userID.String(),
+		"messageType:" + lid.messageType.String() +
+			"(" + strconv.FormatUint(uint64(lid.messageType), 10) + ")",
+		"listener:" + lid.listener.Name(),
+	}
+
+	return "{" + strings.Join(str, " ") + "}"
+}
+
 /*internal listener implementations*/
 
 //listener based off of a function
diff --git a/e2e/receive/switchboard.go b/e2e/receive/switchboard.go
index ad214f53ae524c36fa36ab62a598d76e6e732fa0..3bb5e23cfae466cad43a4a2ca665a079caa48413 100644
--- a/e2e/receive/switchboard.go
+++ b/e2e/receive/switchboard.go
@@ -55,20 +55,23 @@ func (sw *Switchboard) RegisterListener(user *id.ID,
 		jww.FATAL.Panicf("cannot register nil listener")
 	}
 
+	// Create new listener ID
+	lid := ListenerID{
+		userID:      user,
+		messageType: messageType,
+		listener:    newListener,
+	}
+
 	//register the listener by both ID and messageType
 	sw.mux.Lock()
 
-	sw.id.Add(user, newListener)
-	sw.messageType.Add(messageType, newListener)
+	sw.id.Add(lid)
+	sw.messageType.Add(lid)
 
 	sw.mux.Unlock()
 
 	//return a ListenerID so it can be unregistered in the future
-	return ListenerID{
-		userID:      user,
-		messageType: messageType,
-		listener:    newListener,
-	}
+	return lid
 }
 
 // RegisterFunc Registers a new listener built around the passed function.
@@ -141,8 +144,8 @@ func (sw *Switchboard) Speak(item Message) {
 
 	//Execute hear on all matched listeners in a new goroutine
 	matches.Do(func(i interface{}) {
-		r := i.(Listener)
-		go r.Hear(item)
+		lid := i.(ListenerID)
+		go lid.listener.Hear(item)
 	})
 
 	// print to log if nothing was heard
@@ -158,12 +161,32 @@ func (sw *Switchboard) Speak(item Message) {
 func (sw *Switchboard) Unregister(listenerID ListenerID) {
 	sw.mux.Lock()
 
-	sw.id.Remove(listenerID.userID, listenerID.listener)
-	sw.messageType.Remove(listenerID.messageType, listenerID.listener)
+	sw.id.Remove(listenerID)
+	sw.messageType.Remove(listenerID)
 
 	sw.mux.Unlock()
 }
 
+// UnregisterUserListeners removes all the listeners registered with the
+// specified user.
+func (sw *Switchboard) UnregisterUserListeners(userID *id.ID) {
+	sw.mux.Lock()
+	defer sw.mux.Unlock()
+
+	// Get list of all listeners for the specified user
+	idSet := sw.id.Get(userID)
+
+	// Find each listener in the messageType list and delete it
+	idSet.Do(func(i interface{}) {
+		lid := i.(ListenerID)
+		mtSet := sw.messageType.list[lid.messageType]
+		mtSet.Remove(lid)
+	})
+
+	// Remove all listeners for the user from the ID list
+	sw.id.RemoveId(userID)
+}
+
 // finds all listeners who match the items sender or ID, or have those fields
 // as generic
 func (sw *Switchboard) matchListeners(item Message) *set.Set {
diff --git a/e2e/receive/switchboard_test.go b/e2e/receive/switchboard_test.go
index aaee91e2d9d5090175334b503a0ca3a54f929cfe..7d2f8c4ceff37b2be6a6abe09675d0e50a6822dc 100644
--- a/e2e/receive/switchboard_test.go
+++ b/e2e/receive/switchboard_test.go
@@ -85,13 +85,13 @@ func TestSwitchboard_RegisterListener(t *testing.T) {
 	//check that the listener is registered in the appropriate location
 	setID := sw.id.Get(uid)
 
-	if !setID.Has(l) {
+	if !setID.Has(lid) {
 		t.Errorf("Listener is not registered by ID")
 	}
 
 	setType := sw.messageType.Get(mt)
 
-	if !setType.Has(l) {
+	if !setType.Has(lid) {
 		t.Errorf("Listener is not registered by Message Tag")
 	}
 
@@ -152,13 +152,13 @@ func TestSwitchboard_RegisterFunc(t *testing.T) {
 	//check that the listener is registered in the appropriate location
 	setID := sw.id.Get(uid)
 
-	if !setID.Has(lid.listener) {
+	if !setID.Has(lid) {
 		t.Errorf("Listener is not registered by ID")
 	}
 
 	setType := sw.messageType.Get(mt)
 
-	if !setType.Has(lid.listener) {
+	if !setType.Has(lid) {
 		t.Errorf("Listener is not registered by Message Tag")
 	}
 
@@ -221,13 +221,13 @@ func TestSwitchboard_RegisterChan(t *testing.T) {
 	//check that the listener is registered in the appropriate location
 	setID := sw.id.Get(uid)
 
-	if !setID.Has(lid.listener) {
+	if !setID.Has(lid) {
 		t.Errorf("Listener is not registered by ID")
 	}
 
 	setType := sw.messageType.Get(mt)
 
-	if !setType.Has(lid.listener) {
+	if !setType.Has(lid) {
 		t.Errorf("Listener is not registered by Message Tag")
 	}
 
@@ -330,21 +330,67 @@ func TestSwitchboard_Unregister(t *testing.T) {
 	setType := sw.messageType.Get(mt)
 
 	//check that the removed listener is not registered
-	if setID.Has(lid1.listener) {
+	if setID.Has(lid1) {
 		t.Errorf("Removed Listener is registered by ID, should not be")
 	}
 
-	if setType.Has(lid1.listener) {
+	if setType.Has(lid1) {
 		t.Errorf("Removed Listener not registered by Message Tag, " +
 			"should not be")
 	}
 
 	//check that the not removed listener is still registered
-	if !setID.Has(lid2.listener) {
+	if !setID.Has(lid2) {
 		t.Errorf("Remaining Listener is not registered by ID")
 	}
 
-	if !setType.Has(lid2.listener) {
+	if !setType.Has(lid2) {
 		t.Errorf("Remaining Listener is not registered by Message Tag")
 	}
 }
+
+// Tests that three listeners with three different message types but the same
+// user are deleted with Switchboard.UnregisterUserListeners and that three
+// other listeners with the same message types but different users are not
+// delete.
+func TestSwitchboard_UnregisterUserListeners(t *testing.T) {
+	sw := New()
+
+	uid1 := id.NewIdFromUInt(42, id.User, t)
+	uid2 := id.NewIdFromUInt(11, id.User, t)
+
+	l := func(receive Message) {}
+
+	lid1 := sw.RegisterFunc("a", uid1, catalog.NoType, l)
+	lid2 := sw.RegisterFunc("b", uid1, catalog.XxMessage, l)
+	lid3 := sw.RegisterFunc("c", uid1, catalog.KeyExchangeTrigger, l)
+	lid4 := sw.RegisterFunc("d", uid2, catalog.NoType, l)
+	lid5 := sw.RegisterFunc("e", uid2, catalog.XxMessage, l)
+	lid6 := sw.RegisterFunc("f", uid2, catalog.KeyExchangeTrigger, l)
+
+	sw.UnregisterUserListeners(uid1)
+
+	if s, exists := sw.id.list[*uid1]; exists {
+		t.Errorf("Set for ID %s still exists: %+v", uid1, s)
+	}
+
+	if sw.messageType.Get(lid1.messageType).Has(lid1) {
+		t.Errorf("Listener %+v still exists", lid1)
+	}
+	if sw.messageType.Get(lid2.messageType).Has(lid2) {
+		t.Errorf("Listener %+v still exists", lid2)
+	}
+	if sw.messageType.Get(lid3.messageType).Has(lid3) {
+		t.Errorf("Listener %+v still exists", lid3)
+	}
+
+	if !sw.messageType.Get(lid4.messageType).Has(lid4) {
+		t.Errorf("Listener %+v does not exist", lid4)
+	}
+	if !sw.messageType.Get(lid5.messageType).Has(lid5) {
+		t.Errorf("Listener %+v does not exist", lid5)
+	}
+	if !sw.messageType.Get(lid6.messageType).Has(lid6) {
+		t.Errorf("Listener %+v does not exist", lid6)
+	}
+}
diff --git a/fileTransfer/connect/utils_test.go b/fileTransfer/connect/utils_test.go
index bd155a4825642ec6cf629241b10e3a4e1e561523..e5c5c6d902c9d22578d847b95d4dc866e315e935 100644
--- a/fileTransfer/connect/utils_test.go
+++ b/fileTransfer/connect/utils_test.go
@@ -32,7 +32,7 @@ import (
 )
 
 ////////////////////////////////////////////////////////////////////////////////
-// Mock cMix                                                           //
+// Mock cMix                                                                  //
 ////////////////////////////////////////////////////////////////////////////////
 
 type mockCmixHandler struct {
@@ -137,6 +137,7 @@ func (m *mockCmix) GetRoundResults(_ time.Duration,
 ////////////////////////////////////////////////////////////////////////////////
 // Mock Connection Handler                                                    //
 ////////////////////////////////////////////////////////////////////////////////
+
 func newMockListener(hearChan chan receive.Message) *mockListener {
 	return &mockListener{hearChan: hearChan}
 }
@@ -162,6 +163,9 @@ func newMockConnectionHandler() *mockConnectionHandler {
 	}
 }
 
+// Tests that mockConnection adheres to the Connection interface.
+var _ Connection = (*mockConnection)(nil)
+
 type mockConnection struct {
 	myID    *id.ID
 	handler *mockConnectionHandler
@@ -193,12 +197,12 @@ func (m *mockConnection) SendE2E(mt catalog.MessageType, payload []byte,
 	return []id.Round{42}, e2eCrypto.MessageID{}, netTime.Now(), nil
 }
 
-func (m *mockConnection) RegisterListener(
-	mt catalog.MessageType, listener receive.Listener) receive.ListenerID {
+func (m *mockConnection) RegisterListener(mt catalog.MessageType,
+	listener receive.Listener) (receive.ListenerID, error) {
 	m.handler.Lock()
 	defer m.handler.Unlock()
 	m.handler.listeners[mt] = listener
-	return receive.ListenerID{}
+	return receive.ListenerID{}, nil
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/fileTransfer/connect/wrapper.go b/fileTransfer/connect/wrapper.go
index fa78ee94b75879898f0dcfcefc67b1038bd11991..0bfde770e1fbf236858485ce4d6b2ae6e267406e 100644
--- a/fileTransfer/connect/wrapper.go
+++ b/fileTransfer/connect/wrapper.go
@@ -41,7 +41,7 @@ type Connection interface {
 	SendE2E(mt catalog.MessageType, payload []byte, params e2e.Params) (
 		[]id.Round, e2eCrypto.MessageID, time.Time, error)
 	RegisterListener(messageType catalog.MessageType,
-		newListener receive.Listener) receive.ListenerID
+		newListener receive.Listener) (receive.ListenerID, error)
 }
 
 // NewWrapper generates a new file transfer manager using Connection E2E.
@@ -56,7 +56,10 @@ func NewWrapper(receiveCB ft.ReceiveCallback, p Params, ft ft.FileTransfer,
 	}
 
 	// Register listener to receive new file transfers
-	w.conn.RegisterListener(catalog.NewFileTransfer, &listener{w})
+	_, err := w.conn.RegisterListener(catalog.NewFileTransfer, &listener{w})
+	if err != nil {
+		return nil, err
+	}
 
 	return w, nil
 }
@@ -109,7 +112,7 @@ func (w *Wrapper) RegisterSentProgressCallback(tid *ftCrypto.TransferID,
 	return w.ft.RegisterSentProgressCallback(tid, modifiedProgressCB, period)
 }
 
-// addEndMessageToCallback adds the sending of an Connection E2E message when
+// addEndMessageToCallback adds the sending of a Connection E2E message when
 // the transfer completed to the callback. If NotifyUponCompletion is not set,
 // then the message is not sent.
 func (w *Wrapper) addEndMessageToCallback(progressCB ft.SentProgressCallback) ft.SentProgressCallback {
diff --git a/go.mod b/go.mod
index 3e9394e23cb1ce9237a5562d3e59322c85d6c2e9..f1594495c9516fd0d6a4586c377e8810f976a8bb 100644
--- a/go.mod
+++ b/go.mod
@@ -16,9 +16,9 @@ require (
 	gitlab.com/elixxir/crypto v0.0.7-0.20220606201132-c370d5039cea
 	gitlab.com/elixxir/ekv v0.1.7
 	gitlab.com/elixxir/primitives v0.0.3-0.20220606195757-40f7a589347f
-	gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac
+	gitlab.com/xx_network/comms v0.0.4-0.20220630163702-f3d372ef6acd
 	gitlab.com/xx_network/crypto v0.0.5-0.20220606200528-3f886fe49e81
-	gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e
+	gitlab.com/xx_network/primitives v0.0.4-0.20220630163313-7890038258c6
 	go.uber.org/ratelimit v0.2.0
 	golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
diff --git a/go.sum b/go.sum
index 88d420c80a70e484ea4ed24885df87c44823b307..8d1e3fc2d22efad52720f2a922b18426b5ced73f 100644
--- a/go.sum
+++ b/go.sum
@@ -292,8 +292,9 @@ gitlab.com/elixxir/primitives v0.0.3-0.20220323183834-b98f255361b8/go.mod h1:MtF
 gitlab.com/elixxir/primitives v0.0.3-0.20220606195757-40f7a589347f h1:CTf2+ewHWYrzp5Ar3RwNvHePfTHyFniJTVjFW4zqoaE=
 gitlab.com/elixxir/primitives v0.0.3-0.20220606195757-40f7a589347f/go.mod h1:9Bb2+u+CDSwsEU5Droo6saDAXuBDvLRjexpBhPAYxhA=
 gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
-gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac h1:+ykw0JqLH/qMprPEKazGHNH8gUoHGA78EIr4ienxnw4=
 gitlab.com/xx_network/comms v0.0.4-0.20220315161313-76acb14429ac/go.mod h1:isHnwem0v4rTcwwHP455FhVlFyPcHkHiVz+N3s/uCSI=
+gitlab.com/xx_network/comms v0.0.4-0.20220630163702-f3d372ef6acd h1:qYR2V/8KliGyJ2clWpVYhpgwpnfS1MXGpAbrFoOkSWk=
+gitlab.com/xx_network/comms v0.0.4-0.20220630163702-f3d372ef6acd/go.mod h1:TraR4sW+YxK/2CV+IQUNaAV61+ElrBV0kXd5HLEjM7M=
 gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE=
 gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk=
 gitlab.com/xx_network/crypto v0.0.5-0.20220222212031-750f7e8a01f4/go.mod h1:6apvsoHCQJDjO0J4E3uhR3yO9tTz/Mq5be5rjB3tQPU=
@@ -305,8 +306,9 @@ gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK
 gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
 gitlab.com/xx_network/primitives v0.0.4-0.20220222211843-901fa4a2d72b/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
 gitlab.com/xx_network/primitives v0.0.4-0.20220317172007-4d2a53e6e669/go.mod h1:AXVVFt7dDAeIUpOGPiStCcUIKsBXLWbmV/BgZ4T+tOo=
-gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e h1:F651DdbU9n5qOLN8rdyAIluXWtm5Wl4jwH5D6ED3bSI=
 gitlab.com/xx_network/primitives v0.0.4-0.20220324193139-b292d1ae6e7e/go.mod h1:AXVVFt7dDAeIUpOGPiStCcUIKsBXLWbmV/BgZ4T+tOo=
+gitlab.com/xx_network/primitives v0.0.4-0.20220630163313-7890038258c6 h1:3It6ILDHn/9J/Oi7MfMjkidKPe7vbFCy5JQtXx8EfYM=
+gitlab.com/xx_network/primitives v0.0.4-0.20220630163313-7890038258c6/go.mod h1:AXVVFt7dDAeIUpOGPiStCcUIKsBXLWbmV/BgZ4T+tOo=
 gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93 h1:eJZrXqHsMmmejEPWw8gNAt0I8CGAMNO/7C339Zco3TM=
 gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
diff --git a/groupChat/groupStore/store_test.go b/groupChat/groupStore/store_test.go
index a41439b730f46285f39f9a960e6fe6bd94bbce77..e5549f06b1afadeceb44dd3a8bb3d2ac3c0ce7d1 100644
--- a/groupChat/groupStore/store_test.go
+++ b/groupChat/groupStore/store_test.go
@@ -558,7 +558,7 @@ func TestStore_GetUser(t *testing.T) {
 	}
 
 	if !user.Equal(store.GetUser()) {
-		t.Errorf("GetUser() failed to return the expected member."+
+		t.Errorf("GetTransmissionIdentity() failed to return the expected member."+
 			"\nexpected: %#v\nreceived: %#v", user, store.GetUser())
 	}
 }
diff --git a/groupChat/manager_test.go b/groupChat/manager_test.go
index 374062ea83700854b222aaea55d70483304c8d30..de32491e49f2b7555783abc49d26d131c81a877e 100644
--- a/groupChat/manager_test.go
+++ b/groupChat/manager_test.go
@@ -170,26 +170,26 @@ func TestNewManager_LoadError(t *testing.T) {
 // 	m2, _ := newTestManagerWithStore(prng, 10, 0, requestFunc2, receiveFunc2, t)
 // 	m3, _ := newTestManagerWithStore(prng, 10, 0, requestFunc3, receiveFunc3, t)
 //
-// 	membership, err := group.NewMembership(m1.store.GetUser().Contact(),
-// 		m2.store.GetUser().Contact(), m3.store.GetUser().Contact())
+// 	membership, err := group.NewMembership(m1.store.GetTransmissionIdentity().Contact(),
+// 		m2.store.GetTransmissionIdentity().Contact(), m3.store.GetTransmissionIdentity().Contact())
 // 	if err != nil {
 // 		t.Errorf("Failed to generate new membership: %+v", err)
 // 	}
 //
-// 	dhKeys := gs.GenerateDhKeyList(m1.gs.GetUser().ID,
-// 		m1.store.GetUser().E2eDhPrivateKey, membership, m1.store.E2e().GetGroup())
+// 	dhKeys := gs.GenerateDhKeyList(m1.gs.GetTransmissionIdentity().ID,
+// 		m1.store.GetTransmissionIdentity().E2eDhPrivateKey, membership, m1.store.E2e().GetGroup())
 //
-// 	grp1 := newTestGroup(m1.store.E2e().GetGroup(), m1.store.GetUser().E2eDhPrivateKey, prng, t)
+// 	grp1 := newTestGroup(m1.store.E2e().GetGroup(), m1.store.GetTransmissionIdentity().E2eDhPrivateKey, prng, t)
 // 	grp1.Members = membership
 // 	grp1.DhKeys = dhKeys
 // 	grp1.ID = group.NewID(grp1.IdPreimage, grp1.Members)
 // 	grp1.Key = group.NewKey(grp1.KeyPreimage, grp1.Members)
 // 	grp2 := grp1.DeepCopy()
-// 	grp2.DhKeys = gs.GenerateDhKeyList(m2.gs.GetUser().ID,
-// 		m2.store.GetUser().E2eDhPrivateKey, membership, m2.store.E2e().GetGroup())
+// 	grp2.DhKeys = gs.GenerateDhKeyList(m2.gs.GetTransmissionIdentity().ID,
+// 		m2.store.GetTransmissionIdentity().E2eDhPrivateKey, membership, m2.store.E2e().GetGroup())
 // 	grp3 := grp1.DeepCopy()
-// 	grp3.DhKeys = gs.GenerateDhKeyList(m3.gs.GetUser().ID,
-// 		m3.store.GetUser().E2eDhPrivateKey, membership, m3.store.E2e().GetGroup())
+// 	grp3.DhKeys = gs.GenerateDhKeyList(m3.gs.GetTransmissionIdentity().ID,
+// 		m3.store.GetTransmissionIdentity().E2eDhPrivateKey, membership, m3.store.E2e().GetGroup())
 //
 // 	err = m1.gs.Add(grp1)
 // 	if err != nil {
@@ -222,7 +222,7 @@ func TestNewManager_LoadError(t *testing.T) {
 // 	msg := message.Receive{
 // 		Payload:     requestMarshaled,
 // 		MessageType: message.GroupCreationRequest,
-// 		Sender:      m1.gs.GetUser().ID,
+// 		Sender:      m1.gs.GetTransmissionIdentity().ID,
 // 	}
 //
 // 	m2.swb.(*switchboard.Switchboard).Speak(msg)
@@ -252,14 +252,14 @@ func TestNewManager_LoadError(t *testing.T) {
 // 	timestamp := netTime.Now()
 //
 // 	// Create cMix message and get public message
-// 	cMixMsg, err := m1.newCmixMsg(grp1, contents, timestamp, m2.gs.GetUser(), prng)
+// 	cMixMsg, err := m1.newCmixMsg(grp1, contents, timestamp, m2.gs.GetTransmissionIdentity(), prng)
 // 	if err != nil {
 // 		t.Errorf("Failed to create new cMix message: %+v", err)
 // 	}
 //
 // 	internalMsg, _ := newInternalMsg(cMixMsg.ContentsSize() - publicMinLen)
 // 	internalMsg.SetTimestamp(timestamp)
-// 	internalMsg.SetSenderID(m1.gs.GetUser().ID)
+// 	internalMsg.SetSenderID(m1.gs.GetTransmissionIdentity().ID)
 // 	internalMsg.SetPayload(contents)
 // 	expectedMsgID := group.NewMessageID(grp1.ID, internalMsg.Marshal())
 //
@@ -267,14 +267,14 @@ func TestNewManager_LoadError(t *testing.T) {
 // 		GroupID:        grp1.ID,
 // 		ID:             expectedMsgID,
 // 		Payload:        contents,
-// 		SenderID:       m1.gs.GetUser().ID,
+// 		SenderID:       m1.gs.GetTransmissionIdentity().ID,
 // 		RoundTimestamp: timestamp.Local(),
 // 	}
 //
 // 	msg = message.Receive{
 // 		Payload:        cMixMsg.Marshal(),
 // 		MessageType:    message.Raw,
-// 		Sender:         m1.gs.GetUser().ID,
+// 		Sender:         m1.gs.GetTransmissionIdentity().ID,
 // 		RoundTimestamp: timestamp.Local(),
 // 	}
 // 	m2.swb.(*switchboard.Switchboard).Speak(msg)
diff --git a/storage/user/info.go b/storage/user/info.go
index 62603c2225f0921f54684299f140fb33bfb6d182..3da77bb13935d863e382e26ad55aa529f1b63b6b 100644
--- a/storage/user/info.go
+++ b/storage/user/info.go
@@ -9,9 +9,7 @@ package user
 
 import (
 	"gitlab.com/elixxir/crypto/backup"
-	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/crypto/signature/rsa"
 	"gitlab.com/xx_network/primitives/id"
 )
@@ -55,14 +53,6 @@ type Info struct {
 	E2eDhPublicKey  *cyclic.Int
 }
 
-func (u Info) GetContact() contact.Contact {
-	return contact.Contact{
-		ID:       u.ReceptionID.DeepCopy(),
-		DhPubKey: u.E2eDhPublicKey,
-		Facts:    make([]fact.Fact, 0),
-	}
-}
-
 func NewUserFromProto(proto *Proto) Info {
 	return Info{
 		TransmissionID:        proto.TransmissionID,
diff --git a/ud/addFact.go b/ud/addFact.go
index 6412e94b308871e9aa7266b2176e39cb8f169376..3c2b0af5428d71c57fd5f80cc3a0b5657bc9cccc 100644
--- a/ud/addFact.go
+++ b/ud/addFact.go
@@ -1,7 +1,6 @@
 package ud
 
 import (
-	"crypto/rand"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
@@ -47,7 +46,9 @@ func (m *Manager) addFact(inFact fact.Fact, myId *id.ID,
 
 	// Sign our inFact for putting into the request
 	privKey := m.user.PortableUserInfo().ReceptionRSA
-	fSig, err := rsa.Sign(rand.Reader, privKey, hash.CMixHash, fHash, nil)
+	stream := m.rng.GetStream()
+	defer stream.Close()
+	fSig, err := rsa.Sign(stream, privKey, hash.CMixHash, fHash, nil)
 	if err != nil {
 		return "", err
 	}
diff --git a/ud/manager.go b/ud/manager.go
index 1ce22eac8d6c24812c381fdc60753e07ba5e5e66..77dbc01f44def0d37e0d84ff208fb8cffe39befe 100644
--- a/ud/manager.go
+++ b/ud/manager.go
@@ -2,6 +2,7 @@ package ud
 
 import (
 	"fmt"
+	"gitlab.com/elixxir/crypto/fastRNG"
 	"sync"
 	"time"
 
@@ -14,7 +15,6 @@ import (
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/comms/connect"
-	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/primitives/id"
 )
 
@@ -65,6 +65,10 @@ type Manager struct {
 	// alternativeUd is an alternate User discovery service to circumvent
 	// production. This is for testing with a separately deployed UD service.
 	alternativeUd *alternateUd
+
+	// rng is a fastRNG.StreamGenerator which is used to generate random
+	// data. This is used for signatures for adding/removing facts.
+	rng *fastRNG.StreamGenerator
 }
 
 // NewManager builds a new user discovery manager.
@@ -73,7 +77,7 @@ type Manager struct {
 func NewManager(services CMix, e2e E2E,
 	follower NetworkStatus,
 	events event.Reporter, comms Comms, userStore UserInfo,
-	rng csprng.Source, username string,
+	rng *fastRNG.StreamGenerator, username string,
 	kv *versioned.KV) (*Manager, error) {
 	jww.INFO.Println("ud.NewManager()")
 
@@ -90,6 +94,7 @@ func NewManager(services CMix, e2e E2E,
 		comms:   comms,
 		user:    userStore,
 		kv:      kv,
+		rng:     rng,
 	}
 
 	if m.isRegistered() {
@@ -111,7 +116,9 @@ func NewManager(services CMix, e2e E2E,
 	}
 
 	// Register with user discovery
-	err = m.register(username, rng, comms, udHost)
+	stream := rng.GetStream()
+	defer stream.Close()
+	err = m.register(username, stream, comms, udHost)
 	if err != nil {
 		return nil, errors.Errorf("Failed to register: %v", err)
 	}
@@ -132,6 +139,7 @@ func NewManager(services CMix, e2e E2E,
 func NewManagerFromBackup(services CMix,
 	e2e E2E, follower NetworkStatus,
 	events event.Reporter, comms Comms, userStore UserInfo,
+	rng *fastRNG.StreamGenerator,
 	email, phone fact.Fact, kv *versioned.KV) (*Manager, error) {
 	jww.INFO.Println("ud.NewManagerFromBackup()")
 	if follower() != xxdk.Running {
@@ -148,6 +156,7 @@ func NewManagerFromBackup(services CMix,
 		comms:   comms,
 		user:    userStore,
 		kv:      kv,
+		rng:     rng,
 	}
 
 	// Initialize our store
@@ -212,6 +221,7 @@ func InitStoreFromBackup(kv *versioned.KV,
 // instantiation of the manager by NewUserDiscovery.
 func LoadManager(services CMix, e2e E2E,
 	events event.Reporter, comms Comms, userStore UserInfo,
+	rng *fastRNG.StreamGenerator,
 	kv *versioned.KV) (*Manager, error) {
 
 	m := &Manager{
@@ -220,8 +230,8 @@ func LoadManager(services CMix, e2e E2E,
 		events:  events,
 		comms:   comms,
 		user:    userStore,
-
-		kv: kv,
+		rng:     rng,
+		kv:      kv,
 	}
 
 	if !m.isRegistered() {
diff --git a/ud/remove.go b/ud/remove.go
index 61d130ec240ad1bda693af3c39c9660f7858f597..b64a218e6d54f120de999478ef8b9fd5fb10dd0c 100644
--- a/ud/remove.go
+++ b/ud/remove.go
@@ -1,7 +1,6 @@
 package ud
 
 import (
-	"crypto/rand"
 	"fmt"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
@@ -47,7 +46,9 @@ func (m *Manager) removeFact(f fact.Fact,
 
 	// Sign our inFact for putting into the request
 	privKey := m.user.PortableUserInfo().ReceptionRSA
-	fSig, err := rsa.Sign(rand.Reader, privKey, hash.CMixHash, fHash, nil)
+	stream := m.rng.GetStream()
+	defer stream.Close()
+	fSig, err := rsa.Sign(stream, privKey, hash.CMixHash, fHash, nil)
 	if err != nil {
 		return err
 	}
@@ -103,7 +104,9 @@ func (m *Manager) permanentDeleteAccount(f fact.Fact, myId *id.ID, privateKey *r
 	fHash := factID.Fingerprint(f)
 
 	// Sign our inFact for putting into the request
-	fsig, err := rsa.Sign(rand.Reader, privateKey, hash.CMixHash, fHash, nil)
+	stream := m.rng.GetStream()
+	defer stream.Close()
+	fsig, err := rsa.Sign(stream, privateKey, hash.CMixHash, fHash, nil)
 	if err != nil {
 		return err
 	}
diff --git a/ud/utils_test.go b/ud/utils_test.go
index 640c62ec048b356c66319a15e3e08f3ae187ff61..5124db19cd73f3b153afde487c4649a2276780ff 100644
--- a/ud/utils_test.go
+++ b/ud/utils_test.go
@@ -24,6 +24,7 @@ import (
 	"gitlab.com/elixxir/comms/testkeys"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/comms/messages"
@@ -69,6 +70,8 @@ func newTestManager(t *testing.T) (*Manager, *testNetworkManager) {
 		t.Fatalf("Failed to initialize store %v", err)
 	}
 
+	rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG)
+
 	// Create our Manager object
 	m := &Manager{
 		e2e:    mockE2e{grp: getGroup()},
@@ -76,6 +79,7 @@ func newTestManager(t *testing.T) (*Manager, *testNetworkManager) {
 		user:   mockUser{testing: t, key: key},
 		store:  udStore,
 		comms:  &mockComms{},
+		rng:    rngGen,
 		kv:     kv,
 	}
 	tnm := newTestNetworkManager(t)
diff --git a/xxdk/cmix.go b/xxdk/cmix.go
index 059c9d061ffa8b83db87cf30db140f05d5311f7f..54aa051cbd8c22b0e7205e7e3c4d06822d5c5c39 100644
--- a/xxdk/cmix.go
+++ b/xxdk/cmix.go
@@ -392,7 +392,6 @@ func (c *Cmix) StopNetworkFollower() error {
 
 // NetworkFollowerStatus Gets the state of the network follower. Returns:
 // Stopped 	- 0
-// Starting - 1000
 // Running	- 2000
 // Stopping	- 3000
 func (c *Cmix) NetworkFollowerStatus() Status {
@@ -421,12 +420,11 @@ func (c *Cmix) AddService(sp Service) error {
 	return c.followerServices.add(sp)
 }
 
-// GetUser returns the current user Identity for this client. This
-// can be serialized into a byte stream for out-of-band sharing.
-func (c *Cmix) GetUser() user.Info {
-	jww.INFO.Printf("GetUser()")
+// GetTransmissionIdentity returns the current TransmissionIdentity for this client
+func (c *Cmix) GetTransmissionIdentity() TransmissionIdentity {
+	jww.INFO.Printf("GetTransmissionIdentity()")
 	cMixUser := c.storage.PortableUserInfo()
-	return cMixUser
+	return buildTransmissionIdentity(cMixUser)
 }
 
 // GetComms returns the client comms object
diff --git a/xxdk/e2e.go b/xxdk/e2e.go
index ce139c2941c26eb86ccd917403864718b61c2946..8811994fd3e1b5032d6b5821c4d34e798cb41fdd 100644
--- a/xxdk/e2e.go
+++ b/xxdk/e2e.go
@@ -12,10 +12,13 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/auth"
+	"gitlab.com/elixxir/client/cmix/identity/receptionID"
+	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/e2e"
 	"gitlab.com/elixxir/client/e2e/rekey"
 	"gitlab.com/elixxir/client/storage/user"
 	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
 	"gitlab.com/elixxir/ekv"
@@ -34,16 +37,27 @@ type E2e struct {
 	e2eIdentity ReceptionIdentity
 }
 
+// AuthCallbacks is an adapter for the auth.Callbacks interface
+// that allows for initializing an E2e object without an E2e-dependant auth.Callbacks
+type AuthCallbacks interface {
+	Request(partner contact.Contact, receptionID receptionID.EphemeralIdentity,
+		round rounds.Round, e2e *E2e)
+	Confirm(partner contact.Contact, receptionID receptionID.EphemeralIdentity,
+		round rounds.Round, e2e *E2e)
+	Reset(partner contact.Contact, receptionID receptionID.EphemeralIdentity,
+		round rounds.Round, e2e *E2e)
+}
+
 // Login creates a new E2e backed by the xxdk.Cmix persistent versioned.KV
-// If identity == nil, a new ReceptionIdentity will be generated automagically
-func Login(client *Cmix, callbacks auth.Callbacks,
+// It bundles a Cmix object with a ReceptionIdentity object
+// and initializes the auth.State and e2e.Handler objects
+func Login(client *Cmix, callbacks AuthCallbacks,
 	identity ReceptionIdentity) (m *E2e, err error) {
 	return login(client, callbacks, identity, client.GetStorage().GetKV())
 }
 
 // LoginEphemeral creates a new E2e backed by a totally ephemeral versioned.KV
-// If identity == nil, a new ReceptionIdentity will be generated automagically
-func LoginEphemeral(client *Cmix, callbacks auth.Callbacks,
+func LoginEphemeral(client *Cmix, callbacks AuthCallbacks,
 	identity ReceptionIdentity) (m *E2e, err error) {
 	return login(client, callbacks, identity, versioned.NewKV(ekv.MakeMemstore()))
 }
@@ -52,17 +66,19 @@ func LoginEphemeral(client *Cmix, callbacks auth.Callbacks,
 // Uses the pre-generated transmission ID used by xxdk.Cmix.
 // This function is designed to maintain backwards compatibility with previous
 // xx messenger designs and should not be used for other purposes.
-func LoginLegacy(client *Cmix, callbacks auth.Callbacks) (m *E2e, err error) {
+func LoginLegacy(client *Cmix, callbacks AuthCallbacks) (m *E2e, err error) {
 	m = &E2e{
 		Cmix:   client,
 		backup: &Container{},
 	}
 
-	m.e2e, err = LoadOrInitE2e(client)
+	m.e2e, err = loadOrInitE2eLegacy(client)
 	if err != nil {
 		return nil, err
 	}
-	client.GetCmix().AddIdentity(client.GetUser().ReceptionID, time.Time{}, true)
+
+	userInfo := client.GetStorage().PortableUserInfo()
+	client.GetCmix().AddIdentity(userInfo.ReceptionID, time.Time{}, true)
 
 	err = client.AddService(m.e2e.StartProcesses)
 	if err != nil {
@@ -72,19 +88,12 @@ func LoginLegacy(client *Cmix, callbacks auth.Callbacks) (m *E2e, err error) {
 
 	m.auth, err = auth.NewState(client.GetStorage().GetKV(), client.GetCmix(),
 		m.e2e, client.GetRng(), client.GetEventReporter(),
-		auth.GetDefaultParams(), callbacks, m.backup.TriggerBackup)
+		auth.GetDefaultParams(), MakeAuthCallbacksAdapter(callbacks, m), m.backup.TriggerBackup)
 	if err != nil {
 		return nil, err
 	}
 
-	u := m.Cmix.GetUser()
-	m.e2eIdentity = ReceptionIdentity{
-		ID:            u.TransmissionID,
-		RSAPrivatePem: u.TransmissionRSA,
-		Salt:          u.TransmissionSalt,
-		DHKeyPrivate:  u.E2eDhPrivateKey,
-	}
-
+	m.e2eIdentity, err = buildReceptionIdentity(userInfo, m.e2e.GetGroup(), m.e2e.GetHistoricalDHPrivkey())
 	return m, err
 }
 
@@ -131,7 +140,7 @@ func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte,
 // JSON containing the cryptographic primitives. This is designed for
 // some specific deployment procedures and is generally unsafe.
 func LoginWithProtoClient(storageDir string, password []byte,
-	protoClientJSON []byte, newBaseNdf string,
+	protoClientJSON []byte, newBaseNdf string, callbacks AuthCallbacks,
 	params Params) (*E2e, error) {
 	jww.INFO.Printf("LoginWithProtoClient()")
 
@@ -164,33 +173,26 @@ func LoginWithProtoClient(storageDir string, password []byte,
 		return nil, err
 	}
 
-	c.network.AddIdentity(c.GetUser().ReceptionID, time.Time{}, true)
-
-	// FIXME: The callbacks need to be set, so I suppose we would need to
-	//        either set them via a special type or add them
-	//        to the login call?
-	if err != nil {
-		return nil, err
-	}
 	err = c.registerFollower()
 	if err != nil {
 		return nil, err
 	}
 
-	return Login(c, nil, ReceptionIdentity{
-		ID:            protoUser.ReceptionID,
-		RSAPrivatePem: protoUser.ReceptionRSA,
-		Salt:          protoUser.ReceptionSalt,
-		DHKeyPrivate:  protoUser.E2eDhPrivateKey,
-	})
+	userInfo := c.GetStorage().PortableUserInfo()
+	receptionIdentity, err := buildReceptionIdentity(userInfo, c.GetStorage().GetE2EGroup(), protoUser.E2eDhPrivateKey)
+	return Login(c, callbacks, receptionIdentity)
 }
 
 // login creates a new xxdk.E2e backed by the given versioned.KV
-func login(client *Cmix, callbacks auth.Callbacks,
+func login(client *Cmix, callbacks AuthCallbacks,
 	identity ReceptionIdentity, kv *versioned.KV) (m *E2e, err error) {
 
 	// Verify the passed-in ReceptionIdentity matches its properties
-	generatedId, err := xx.NewID(identity.RSAPrivatePem.GetPublic(), identity.Salt, id.User)
+	privatePem, err := identity.GetRSAPrivatePem()
+	if err != nil {
+		return nil, err
+	}
+	generatedId, err := xx.NewID(privatePem.GetPublic(), identity.Salt, id.User)
 	if err != nil {
 		return nil, err
 	}
@@ -199,27 +201,38 @@ func login(client *Cmix, callbacks auth.Callbacks,
 			identity.ID.String())
 	}
 
-	e2eGrp := client.GetStorage().GetE2EGroup()
 	m = &E2e{
 		Cmix:        client,
 		backup:      &Container{},
 		e2eIdentity: identity,
 	}
-
-	//initialize the e2e storage
-	err = e2e.Init(kv, identity.ID, identity.DHKeyPrivate, e2eGrp,
-		rekey.GetDefaultEphemeralParams())
+	dhPrivKey, err := identity.GetDHKeyPrivate()
 	if err != nil {
 		return nil, err
 	}
 
-	//load the new e2e storage
+	// load or init the new e2e storage
+	e2eGrp := client.GetStorage().GetE2EGroup()
 	m.e2e, err = e2e.Load(kv,
 		client.GetCmix(), identity.ID, e2eGrp, client.GetRng(),
 		client.GetEventReporter())
 	if err != nil {
-		return nil, errors.WithMessage(err, "Failed to load a "+
-			"newly created e2e store")
+		//initialize the e2e storage
+		jww.INFO.Printf("Initializing new e2e.Handler for %s", identity.ID.String())
+		err = e2e.Init(kv, identity.ID, dhPrivKey, e2eGrp,
+			rekey.GetDefaultParams())
+		if err != nil {
+			return nil, err
+		}
+
+		//load the new e2e storage
+		m.e2e, err = e2e.Load(kv,
+			client.GetCmix(), identity.ID, e2eGrp, client.GetRng(),
+			client.GetEventReporter())
+		if err != nil {
+			return nil, errors.WithMessage(err, "Failed to load a "+
+				"newly created e2e store")
+		}
 	}
 
 	err = client.AddService(m.e2e.StartProcesses)
@@ -230,19 +243,20 @@ func login(client *Cmix, callbacks auth.Callbacks,
 
 	m.auth, err = auth.NewState(kv, client.GetCmix(),
 		m.e2e, client.GetRng(), client.GetEventReporter(),
-		auth.GetDefaultTemporaryParams(), callbacks, m.backup.TriggerBackup)
+		auth.GetDefaultTemporaryParams(), MakeAuthCallbacksAdapter(callbacks, m), m.backup.TriggerBackup)
 	if err != nil {
 		return nil, err
 	}
 
+	client.network.AddIdentity(identity.ID, time.Time{}, true)
 	return m, err
 }
 
-// LoadOrInitE2e loads the e2e handler or makes a new one, generating a new
+// loadOrInitE2eLegacy loads the e2e handler or makes a new one, generating a new
 // e2e private key. It attempts to load via a legacy construction, then tries
 // to load the modern one, creating a new modern ID if neither can be found
-func LoadOrInitE2e(client *Cmix) (e2e.Handler, error) {
-	usr := client.GetUser()
+func loadOrInitE2eLegacy(client *Cmix) (e2e.Handler, error) {
+	usr := client.GetStorage().PortableUserInfo()
 	e2eGrp := client.GetStorage().GetE2EGroup()
 	kv := client.GetStorage().GetKV()
 
@@ -306,15 +320,6 @@ func LoadOrInitE2e(client *Cmix) (e2e.Handler, error) {
 	return e2eHandler, nil
 }
 
-// GetUser replaces xxdk.Cmix's GetUser with one which includes the e2e dh
-// private keys
-func (m *E2e) GetUser() user.Info {
-	u := m.Cmix.GetUser()
-	u.E2eDhPrivateKey = m.e2e.GetHistoricalDHPrivkey()
-	u.E2eDhPublicKey = m.e2e.GetHistoricalDHPubkey()
-	return u
-}
-
 // GetReceptionIdentity returns a safe copy of the E2e ReceptionIdentity
 func (m *E2e) GetReceptionIdentity() ReceptionIdentity {
 	return m.e2eIdentity.DeepCopy()
@@ -331,15 +336,22 @@ func (m *E2e) ConstructProtoUserFile() ([]byte, error) {
 			"permissioning")
 	}
 
+	transIdentity := m.Cmix.GetTransmissionIdentity()
+	receptionIdentity := m.GetReceptionIdentity()
+	privatePem, err := receptionIdentity.GetRSAPrivatePem()
+	if err != nil {
+		return nil, err
+	}
+
 	Usr := user.Proto{
-		TransmissionID:        m.GetUser().TransmissionID,
-		TransmissionSalt:      m.GetUser().TransmissionSalt,
-		TransmissionRSA:       m.GetUser().TransmissionRSA,
-		ReceptionID:           m.GetUser().ReceptionID,
-		ReceptionSalt:         m.GetUser().ReceptionSalt,
-		ReceptionRSA:          m.GetUser().ReceptionRSA,
-		Precanned:             m.GetUser().Precanned,
-		RegistrationTimestamp: m.GetUser().RegistrationTimestamp,
+		TransmissionID:        transIdentity.ID,
+		TransmissionSalt:      transIdentity.Salt,
+		TransmissionRSA:       transIdentity.RSAPrivatePem,
+		ReceptionID:           receptionIdentity.ID,
+		ReceptionSalt:         receptionIdentity.Salt,
+		ReceptionRSA:          privatePem,
+		Precanned:             m.GetStorage().IsPrecanned(),
+		RegistrationTimestamp: transIdentity.RegistrationTimestamp,
 		RegCode:               regCode,
 		TransmissionRegValidationSig: m.GetStorage().
 			GetTransmissionRegistrationValidationSignature(),
@@ -396,3 +408,55 @@ func (m *E2e) DeleteContact(partnerId *id.ID) error {
 
 	return nil
 }
+
+// MakeAuthCallbacksAdapter creates an authCallbacksAdapter
+func MakeAuthCallbacksAdapter(ac AuthCallbacks, e2e *E2e) *authCallbacksAdapter {
+	return &authCallbacksAdapter{
+		ac:  ac,
+		e2e: e2e,
+	}
+}
+
+// authCallbacksAdapter is an adapter type to make the AuthCallbacks type
+// compatible with the auth.Callbacks type
+type authCallbacksAdapter struct {
+	ac  AuthCallbacks
+	e2e *E2e
+}
+
+func (aca *authCallbacksAdapter) Request(partner contact.Contact,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
+	aca.ac.Request(partner, receptionID, round, aca.e2e)
+}
+
+func (aca *authCallbacksAdapter) Confirm(partner contact.Contact,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
+	aca.ac.Confirm(partner, receptionID, round, aca.e2e)
+}
+
+func (aca *authCallbacksAdapter) Reset(partner contact.Contact,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
+	aca.ac.Reset(partner, receptionID, round, aca.e2e)
+}
+
+// DefaultAuthCallbacks is a simple structure for providing a default Callbacks implementation
+// It should generally not be used.
+type DefaultAuthCallbacks struct{}
+
+// Confirm will be called when an auth Confirm message is processed.
+func (a DefaultAuthCallbacks) Confirm(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round, *E2e) {
+	jww.ERROR.Printf("No valid auth callback assigned!")
+}
+
+// Request will be called when an auth Request message is processed.
+func (a DefaultAuthCallbacks) Request(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round, *E2e) {
+	jww.ERROR.Printf("No valid auth callback assigned!")
+}
+
+// Reset will be called when an auth Reset operation occurs.
+func (a DefaultAuthCallbacks) Reset(contact.Contact,
+	receptionID.EphemeralIdentity, rounds.Round, *E2e) {
+	jww.ERROR.Printf("No valid auth callback assigned!")
+}
diff --git a/xxdk/identity.go b/xxdk/identity.go
index 923bf3d5c6704284360d319c2d7e2483df77acd2..46caef778129618a8a07a2ebc5ba036fc5500718 100644
--- a/xxdk/identity.go
+++ b/xxdk/identity.go
@@ -7,26 +7,87 @@
 package xxdk
 
 import (
+	"encoding/json"
+	"gitlab.com/elixxir/client/storage/user"
+	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
-	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/crypto/signature/rsa"
 	"gitlab.com/xx_network/crypto/xx"
 	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
 )
 
+const idVersion = 0
+
+// ReceptionIdentity is used by the E2e object for managing
+// identities used for message pickup
 type ReceptionIdentity struct {
 	ID            *id.ID
-	RSAPrivatePem *rsa.PrivateKey
+	RSAPrivatePem []byte
 	Salt          []byte
-	DHKeyPrivate  *cyclic.Int
+	DHKeyPrivate  []byte
+	E2eGrp        []byte
+}
+
+// StoreReceptionIdentity stores the given identity in Cmix storage with the given key
+// This is the ideal way to securely store identities, as the caller of this function
+// is only required to store the given key separately rather than the keying material
+func StoreReceptionIdentity(key string, identity ReceptionIdentity, client *Cmix) error {
+	marshalledIdentity, err := identity.Marshal()
+	if err != nil {
+		return err
+	}
+
+	return client.GetStorage().Set(key, &versioned.Object{
+		Version:   idVersion,
+		Timestamp: netTime.Now(),
+		Data:      marshalledIdentity,
+	})
+}
+
+// LoadReceptionIdentity loads the given identity in Cmix storage with the given key
+func LoadReceptionIdentity(key string, client *Cmix) (ReceptionIdentity, error) {
+	storageObj, err := client.GetStorage().Get(key)
+	if err != nil {
+		return ReceptionIdentity{}, err
+	}
+
+	return UnmarshalReceptionIdentity(storageObj.Data)
+}
+
+// Marshal returns the JSON representation of a ReceptionIdentity
+func (r ReceptionIdentity) Marshal() ([]byte, error) {
+	return json.Marshal(&r)
+}
+
+// UnmarshalReceptionIdentity takes in a marshalled ReceptionIdentity
+// and converts it to an object
+func UnmarshalReceptionIdentity(marshaled []byte) (ReceptionIdentity, error) {
+	newIdentity := ReceptionIdentity{}
+	return newIdentity, json.Unmarshal(marshaled, &newIdentity)
+}
+
+// GetDHKeyPrivate returns the DHKeyPrivate in go format
+func (r ReceptionIdentity) GetDHKeyPrivate() (*cyclic.Int, error) {
+	dhKeyPriv := &cyclic.Int{}
+	err := dhKeyPriv.UnmarshalJSON(r.DHKeyPrivate)
+	return dhKeyPriv, err
+}
+
+// GetRSAPrivatePem returns the RSAPrivatePem in go format
+func (r ReceptionIdentity) GetRSAPrivatePem() (*rsa.PrivateKey, error) {
+	return rsa.LoadPrivateKeyFromPem(r.RSAPrivatePem)
 }
 
 // MakeReceptionIdentity generates a new cryptographic identity
 // for receiving messages.
-func MakeReceptionIdentity(rng csprng.Source,
-	grp *cyclic.Group) (ReceptionIdentity, error) {
+func MakeReceptionIdentity(client *Cmix) (ReceptionIdentity, error) {
+	rng := client.GetRng().GetStream()
+	defer rng.Close()
+	grp := client.GetStorage().GetE2EGroup()
+
 	//make RSA Key
 	rsaKey, err := rsa.GenerateKey(rng,
 		rsa.DefaultRSABitLen)
@@ -50,12 +111,24 @@ func MakeReceptionIdentity(rng csprng.Source,
 		return ReceptionIdentity{}, err
 	}
 
+	privKeyBytes, err := privKey.MarshalJSON()
+	if err != nil {
+		return ReceptionIdentity{}, err
+	}
+
+	grpBytes, err := grp.MarshalJSON()
+	if err != nil {
+		return ReceptionIdentity{}, err
+	}
+
 	//create the identity object
+	rsaPem := rsa.CreatePrivateKeyPem(rsaKey)
 	I := ReceptionIdentity{
 		ID:            newId,
-		RSAPrivatePem: rsaKey,
+		RSAPrivatePem: rsaPem,
 		Salt:          salt,
-		DHKeyPrivate:  privKey,
+		DHKeyPrivate:  privKeyBytes,
+		E2eGrp:        grpBytes,
 	}
 
 	return I, nil
@@ -65,18 +138,28 @@ func MakeReceptionIdentity(rng csprng.Source,
 func (r ReceptionIdentity) DeepCopy() ReceptionIdentity {
 	saltCopy := make([]byte, len(r.Salt))
 	copy(saltCopy, r.Salt)
+
+	dhKeyCopy := make([]byte, len(r.DHKeyPrivate))
+	copy(dhKeyCopy, r.DHKeyPrivate)
+
+	grpCopy := make([]byte, len(r.E2eGrp))
+	copy(grpCopy, r.E2eGrp)
 	return ReceptionIdentity{
 		ID:            r.ID.DeepCopy(),
 		RSAPrivatePem: r.RSAPrivatePem,
 		Salt:          saltCopy,
-		DHKeyPrivate:  r.DHKeyPrivate.DeepCopy(),
+		DHKeyPrivate:  dhKeyCopy,
+		E2eGrp:        grpCopy,
 	}
 }
 
 // GetContact accepts a xxdk.ReceptionIdentity object and returns a contact.Contact object
-func (r ReceptionIdentity) GetContact(grp *cyclic.Group) contact.Contact {
-	dhPub := grp.ExpG(r.DHKeyPrivate, grp.NewInt(1))
+func (r ReceptionIdentity) GetContact() contact.Contact {
+	grp := &cyclic.Group{}
+	_ = grp.UnmarshalJSON(r.E2eGrp)
+	dhKeyPriv, _ := r.GetDHKeyPrivate()
 
+	dhPub := grp.ExpG(dhKeyPriv, grp.NewInt(1))
 	ct := contact.Contact{
 		ID:             r.ID,
 		DhPubKey:       dhPub,
@@ -85,3 +168,62 @@ func (r ReceptionIdentity) GetContact(grp *cyclic.Group) contact.Contact {
 	}
 	return ct
 }
+
+// buildReceptionIdentity creates a new ReceptionIdentity
+// from the given user.Info
+func buildReceptionIdentity(userInfo user.Info, e2eGrp *cyclic.Group, dHPrivkey *cyclic.Int) (ReceptionIdentity, error) {
+	saltCopy := make([]byte, len(userInfo.TransmissionSalt))
+	copy(saltCopy, userInfo.TransmissionSalt)
+
+	grp, err := e2eGrp.MarshalJSON()
+	if err != nil {
+		return ReceptionIdentity{}, err
+	}
+	privKey, err := dHPrivkey.MarshalJSON()
+	if err != nil {
+		return ReceptionIdentity{}, err
+	}
+
+	return ReceptionIdentity{
+		ID:            userInfo.ReceptionID.DeepCopy(),
+		RSAPrivatePem: rsa.CreatePrivateKeyPem(userInfo.ReceptionRSA),
+		Salt:          saltCopy,
+		DHKeyPrivate:  privKey,
+		E2eGrp:        grp,
+	}, nil
+}
+
+// TransmissionIdentity represents the identity
+// used to transmit over the network via a specific Cmix object
+type TransmissionIdentity struct {
+	ID            *id.ID
+	RSAPrivatePem *rsa.PrivateKey
+	Salt          []byte
+	// Timestamp in which user has registered with the network
+	RegistrationTimestamp int64
+}
+
+// DeepCopy produces a safe copy of a TransmissionIdentity
+func (t TransmissionIdentity) DeepCopy() TransmissionIdentity {
+	saltCopy := make([]byte, len(t.Salt))
+	copy(saltCopy, t.Salt)
+	return TransmissionIdentity{
+		ID:                    t.ID.DeepCopy(),
+		RSAPrivatePem:         t.RSAPrivatePem,
+		Salt:                  saltCopy,
+		RegistrationTimestamp: t.RegistrationTimestamp,
+	}
+}
+
+// buildTransmissionIdentity creates a new TransmissionIdentity
+// from the given user.Info
+func buildTransmissionIdentity(userInfo user.Info) TransmissionIdentity {
+	saltCopy := make([]byte, len(userInfo.TransmissionSalt))
+	copy(saltCopy, userInfo.TransmissionSalt)
+	return TransmissionIdentity{
+		ID:                    userInfo.TransmissionID.DeepCopy(),
+		RSAPrivatePem:         userInfo.TransmissionRSA,
+		Salt:                  saltCopy,
+		RegistrationTimestamp: userInfo.RegistrationTimestamp,
+	}
+}
diff --git a/xxdk/permissioning.go b/xxdk/permissioning.go
index 2e1d735e1236c410bfb473e7d02bad87b6f73536..e3327d8a875c33885c3194013434e0b8d7037252 100644
--- a/xxdk/permissioning.go
+++ b/xxdk/permissioning.go
@@ -63,15 +63,16 @@ func (c *Cmix) ConstructProtoUserFile() ([]byte, error) {
 			"permissioning")
 	}
 
+	userInfo := c.GetStorage().PortableUserInfo()
 	Usr := user.Proto{
-		TransmissionID:               c.GetUser().TransmissionID,
-		TransmissionSalt:             c.GetUser().TransmissionSalt,
-		TransmissionRSA:              c.GetUser().TransmissionRSA,
-		ReceptionID:                  c.GetUser().ReceptionID,
-		ReceptionSalt:                c.GetUser().ReceptionSalt,
-		ReceptionRSA:                 c.GetUser().ReceptionRSA,
-		Precanned:                    c.GetUser().Precanned,
-		RegistrationTimestamp:        c.GetUser().RegistrationTimestamp,
+		TransmissionID:               userInfo.TransmissionID,
+		TransmissionSalt:             userInfo.TransmissionSalt,
+		TransmissionRSA:              userInfo.TransmissionRSA,
+		ReceptionID:                  userInfo.ReceptionID,
+		ReceptionSalt:                userInfo.ReceptionSalt,
+		ReceptionRSA:                 userInfo.ReceptionRSA,
+		Precanned:                    userInfo.Precanned,
+		RegistrationTimestamp:        userInfo.RegistrationTimestamp,
 		RegCode:                      regCode,
 		TransmissionRegValidationSig: c.storage.GetTransmissionRegistrationValidationSignature(),
 		ReceptionRegValidationSig:    c.storage.GetReceptionRegistrationValidationSignature(),
diff --git a/xxdk/precan.go b/xxdk/precan.go
index b710954cc27f6901d5f4b0226a3b5aa920bb8f4a..51ae566f33d7983bf2009c3b5e3107551653f1ab 100644
--- a/xxdk/precan.go
+++ b/xxdk/precan.go
@@ -62,7 +62,7 @@ func CreatePrecannedUser(precannedID uint, rng csprng.Source) user.Info {
 // username/identity, but merely creates a new cryptographic identity
 // for adding such information at a later date.
 func NewPrecannedClient(precannedID uint, defJSON, storageDir string,
-	password []byte) error {
+	password []byte) (ReceptionIdentity, error) {
 	jww.INFO.Printf("NewPrecannedClient()")
 	rngStreamGen := fastRNG.NewStreamGenerator(12, 1024,
 		csprng.NewSystemRNG)
@@ -70,26 +70,32 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string,
 
 	def, err := ParseNDF(defJSON)
 	if err != nil {
-		return err
+		return ReceptionIdentity{}, err
 	}
 	cmixGrp, e2eGrp := DecodeGroups(def)
 
+	dhPrivKey := generatePrecanDHKeypair(precannedID, e2eGrp)
+
 	protoUser := CreatePrecannedUser(precannedID, rngStream)
+	identity, err := buildReceptionIdentity(protoUser, e2eGrp, dhPrivKey)
+	if err != nil {
+		return ReceptionIdentity{}, err
+	}
 
 	store, err := CheckVersionAndSetupStorage(def, storageDir, password,
 		protoUser, cmixGrp, e2eGrp, "")
 	if err != nil {
-		return err
+		return ReceptionIdentity{}, err
 	}
 
 	// Mark the precanned user as finished with permissioning and registered
 	// with the network.
 	err = store.ForwardRegistrationStatus(storage.PermissioningComplete)
 	if err != nil {
-		return err
+		return ReceptionIdentity{}, err
 	}
 
-	return nil
+	return identity, err
 }
 
 func generatePrecanDHKeypair(precannedID uint, e2eGrp *cyclic.Group) *cyclic.Int {