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/state.go b/auth/state.go
index f8511ac70ed5ff25048fa44129f4c20d13737f33..f77da163641d59a5be7004c8f52b498e3c2cb6bb 100644
--- a/auth/state.go
+++ b/auth/state.go
@@ -151,6 +151,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 ec848b30b8eecfd9c649dd26fa2e68891d6df169..6c9e034867cf71c2c67cb147869d4ed215191f4b 100644
--- a/bindings/connect.go
+++ b/bindings/connect.go
@@ -91,6 +91,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 0f9b86f02daf6f0d42986cda2cf37b217cb1cd46..5d33a8441910457b55cea6cfef130eec8aa5bb58 100644
--- a/bindings/e2e.go
+++ b/bindings/e2e.go
@@ -7,16 +7,10 @@
 package bindings
 
 import (
-	"encoding/json"
-
-	jww "github.com/spf13/jwalterweatherman"
 	"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
@@ -47,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 xxdk.AuthCallbacks
 	if callbacks == nil {
-		authCallbacks = defaultAuthCallbacks{}
+		authCallbacks = xxdk.DefaultAuthCallbacks{}
 	} else {
 		authCallbacks = &authCallback{bindingsCbs: callbacks}
 	}
@@ -78,14 +72,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 xxdk.AuthCallbacks
 	if callbacks == nil {
-		authCallbacks = defaultAuthCallbacks{}
+		authCallbacks = xxdk.DefaultAuthCallbacks{}
 	} else {
 		authCallbacks = &authCallback{bindingsCbs: callbacks}
 	}
@@ -112,7 +106,7 @@ func LoginE2eLegacy(cmixId int, callbacks AuthCallbacks) (*E2e, error) {
 
 	var authCallbacks xxdk.AuthCallbacks
 	if callbacks == nil {
-		authCallbacks = defaultAuthCallbacks{}
+		authCallbacks = xxdk.DefaultAuthCallbacks{}
 	} else {
 		authCallbacks = &authCallback{bindingsCbs: callbacks}
 	}
@@ -128,38 +122,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.
@@ -204,25 +167,3 @@ func (a *authCallback) Reset(partner contact.Contact,
 	receptionID receptionID.EphemeralIdentity, round rounds.Round, _ *xxdk.E2e) {
 	a.bindingsCbs.Reset(convertAuthCallbacks(partner, receptionID, round))
 }
-
-// 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, *xxdk.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, *xxdk.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, *xxdk.E2e) {
-	jww.ERROR.Printf("No valid auth callback assigned!")
-}
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 262a5b2ab278839e91ae349905f737bc6da2bb26..5d98893bd533444b459563bc22c7ed4790a67784 100644
--- a/cmd/broadcast.go
+++ b/cmd/broadcast.go
@@ -25,12 +25,11 @@ var broadcastCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 		cmixParams, e2eParams := initParams()
-		client := initClient(cmixParams, e2eParams)
+		client := initE2e(cmixParams, e2eParams)
 
 		// 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)
@@ -41,8 +40,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)
 
@@ -71,10 +70,10 @@ var broadcastCmd = &cobra.Command{
 				jww.FATAL.Panicf("description cannot be empty")
 			}
 
-			var channel *crypto.Channel
+			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)
 				}
@@ -101,7 +100,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,
@@ -128,7 +127,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/fileTransfer.go b/cmd/fileTransfer.go
index fe5ac973d5c133c66c2935824bcbfb470f57fa44..eb9b7f34e441b078db012e114b3abc45f00154a3 100644
--- a/cmd/fileTransfer.go
+++ b/cmd/fileTransfer.go
@@ -36,11 +36,11 @@ var ftCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 		cmixParams, e2eParams := initParams()
-		client := initClient(cmixParams, e2eParams)
+		client := initE2e(cmixParams, e2eParams)
 
 		// 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 8c537daf44e57c64db3f49652babfbd54c53052e..3045e71b328bc51dc71fd30364398e761c7170b3 100644
--- a/cmd/group.go
+++ b/cmd/group.go
@@ -35,11 +35,11 @@ var groupCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 		cmixParams, e2eParams := initParams()
-		client := initClient(cmixParams, e2eParams)
+		client := initE2e(cmixParams, e2eParams)
 
 		// 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 {
@@ -156,7 +156,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)
 	}
 
@@ -175,7 +175,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",
@@ -213,7 +213,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)
@@ -227,7 +227,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)
 
@@ -273,7 +273,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 2d98ebbee742521b414ffadd90d28e896390fa33..049b9fb3a2eb1cfce0be2225119a55c04a5fb415 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -166,6 +166,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
@@ -195,35 +198,33 @@ var rootCmd = &cobra.Command{
 
 		cmixParams, e2eParams := initParams()
 
-		client := initClient(cmixParams, e2eParams)
+		client := initE2e(cmixParams, e2eParams)
 
 		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()
@@ -241,8 +242,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)
 
@@ -293,6 +294,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)
@@ -318,19 +320,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)
@@ -344,6 +349,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
@@ -538,7 +545,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())
@@ -552,6 +560,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
@@ -561,7 +572,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)
@@ -635,7 +646,29 @@ func createClient() *xxdk.Cmix {
 	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.CMIXParams, xxdk.E2EParams) {
@@ -665,16 +698,16 @@ func initParams() (xxdk.CMIXParams, xxdk.E2EParams) {
 	return cmixParams, e2eParams
 }
 
-func initClient(cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e {
-	createClient()
+// initE2e returns a fully-formed xxdk.E2e object
+func initE2e(cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e {
+	_, receptionIdentity := initCmix()
 
 	pass := parsePassword(viper.GetString("password"))
 	storeDir := viper.GetString("session")
 	jww.DEBUG.Printf("sessionDur: %v", storeDir)
 
 	// load the client
-	baseclient, err := xxdk.LoadCmix(storeDir, pass, cmixParams)
-
+	baseClient, err := xxdk.LoadCmix(storeDir, pass, cmixParams)
 	if err != nil {
 		jww.FATAL.Panicf("%+v", err)
 	}
@@ -682,9 +715,21 @@ func initClient(cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams) *xxdk.E2e
 	authCbs = makeAuthCallbacks(
 		viper.GetBool("unsafe-channel-creation"), e2eParams)
 
-	client, err := xxdk.LoginLegacy(baseclient, e2eParams, 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, e2eParams, 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,
+			e2eParams)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
 	}
 
 	if protoUser := viper.GetString("protoUserOut"); protoUser != "" {
@@ -792,7 +837,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)
 
@@ -1019,20 +1064,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 {
@@ -1299,7 +1342,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 6b8bb772322d8509ace9c9d731074882a4e769f6..406374c939efacb4af6e0e5b55f5cf91581da5c0 100644
--- a/cmd/single.go
+++ b/cmd/single.go
@@ -34,12 +34,11 @@ var singleCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 
 		cmixParams, e2eParams := initParams()
-		client := initClient(cmixParams, e2eParams)
+		client := initE2e(cmixParams, e2eParams)
 
 		// 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)
@@ -67,9 +66,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 e6fd573e8d8d2b301a03fd7338fb1d78e3f67169..fec4e372fcf9d205197da1b46efdd3ad19946b9a 100644
--- a/cmd/ud.go
+++ b/cmd/ud.go
@@ -35,11 +35,11 @@ var udCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 		cmixParams, e2eParams := initParams()
-		client := initClient(cmixParams, e2eParams)
+		client := initE2e(cmixParams, e2eParams)
 
 		// 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
@@ -154,7 +154,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)
 			//}
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/cmix/message/handler_test.go b/cmix/message/handler_test.go
index b842ce1e4aa367e8c3fbafddd0e0a24da1cbca5b..99dfb9f199d0ea3ced7341e0ae7282e73930feaf 100644
--- a/cmix/message/handler_test.go
+++ b/cmix/message/handler_test.go
@@ -7,6 +7,9 @@
 package message
 
 import (
+	"testing"
+	"time"
+
 	"gitlab.com/elixxir/client/cmix/identity/receptionID"
 	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/event"
@@ -17,8 +20,6 @@ import (
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
-	"testing"
-	"time"
 )
 
 type testProcessor struct {
@@ -150,9 +151,16 @@ func Test_handler_handleMessageHelper_Service(t *testing.T) {
 
 	contents := []byte{4, 4}
 	lazyPreimage := sih.MakePreimage(contents, "test")
+
+	s := Service{
+		Identifier:   nil,
+		Tag:          "test",
+		lazyPreimage: &lazyPreimage,
+	}
+
 	ecrMsg := format.NewMessage(2056)
 	ecrMsg.SetContents(contents)
-	ecrMsg.SetSIH(sih.Hash(lazyPreimage, ecrMsg.GetContents()))
+	ecrMsg.SetSIH(s.Hash(ecrMsg.GetContents()))
 
 	testRound := rounds.Round{
 		Timestamps:       make(map[states.Round]time.Time),
@@ -166,12 +174,9 @@ func Test_handler_handleMessageHelper_Service(t *testing.T) {
 		RoundInfo: testRound,
 	}
 
-	s := Service{
-		Identifier:   nil,
-		Tag:          "test",
-		lazyPreimage: &lazyPreimage,
-	}
-	m.AddService(testId, s, nil)
+	processor := &testProcessor{}
+
+	m.AddService(testId, s, processor)
 	result := m.handleMessageHelper(ecrMsg, bundle)
 	if !result {
 		t.Errorf("Expected handleMessage success!")
diff --git a/connect/authCallbacks.go b/connect/authCallbacks.go
index 0a2cded398b9c29a9a6a199277bfe919189eaa31..227c47500e166155655ce96d0bdc0b9bfbfa2a94 100644
--- a/connect/authCallbacks.go
+++ b/connect/authCallbacks.go
@@ -90,6 +90,9 @@ type serverAuthCallback struct {
 	confirmCallback Callback
 	requestCallback Callback
 
+	// Used to track stale connections
+	cl *ConnectionList
+
 	// Used for building new Connection objects
 	connectionParams xxdk.E2EParams
 }
@@ -97,11 +100,12 @@ type serverAuthCallback struct {
 // 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,
+func getServerAuthCallback(confirm, request Callback, cl *ConnectionList,
 	params xxdk.E2EParams) *serverAuthCallback {
 	return &serverAuthCallback{
 		confirmCallback:  confirm,
 		requestCallback:  request,
+		cl:               cl,
 		connectionParams: params,
 	}
 }
@@ -137,8 +141,10 @@ func (a serverAuthCallback) Request(requestor contact.Contact,
 	}
 
 	// Return the new Connection object
-	a.requestCallback(BuildConnection(newPartner, e2e.GetE2E(),
-		e2e.GetAuth(), a.connectionParams))
+	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.
diff --git a/connect/authenticated.go b/connect/authenticated.go
index 6ae9342d92d2c235e0e4bc9411305541b7b65352..4376a2ceec13c2588e2dd637ce742cff02a4b033 100644
--- a/connect/authenticated.go
+++ b/connect/authenticated.go
@@ -67,7 +67,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)
 }
 
@@ -171,8 +175,9 @@ 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 xxdk.E2EParams) (*xxdk.E2e,
-	error) {
+	cb AuthenticatedCallback, net *xxdk.Cmix, p xxdk.E2EParams,
+	clParams ConnectionListParams) (
+	*ConnectionServer, error) {
 
 	// Register the waiter for a connection establishment
 	connCb := Callback(func(connection Connection) {
@@ -180,10 +185,16 @@ 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)
+	return StartServer(identity, connCb, net, p, clParams)
 }
 
 // authenticatedHandler provides an implementation for the
diff --git a/connect/authenticated_test.go b/connect/authenticated_test.go
index f375b17f58ce63de8618f3dd8612e50b853920d0..d4f5be3d2875ff0841ce91a6f81c4f5ef846c204 100644
--- a/connect/authenticated_test.go
+++ b/connect/authenticated_test.go
@@ -44,7 +44,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 458d2ade32e8c9514396f3df5e61d05d62eb2bfc..5e77868c43fb00970483a59b00350a6b5c082999 100644
--- a/connect/connect.go
+++ b/connect/connect.go
@@ -8,9 +8,11 @@ package connect
 
 import (
 	"io"
+	"sync/atomic"
 	"time"
 
 	"gitlab.com/elixxir/client/xxdk"
+	"gitlab.com/xx_network/primitives/netTime"
 
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
@@ -30,6 +32,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.
@@ -50,7 +54,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)
 
@@ -69,6 +73,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
@@ -126,13 +134,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 xxdk.E2EParams) (*xxdk.E2e, error) {
+	p xxdk.E2EParams, clParams ConnectionListParams) (*ConnectionServer, error) {
+
+	// Create connection list and start cleanup thread
+	cl := NewConnectionList(clParams)
+	err := net.AddService(cl.CleanupThread)
+	if err != nil {
+		return nil, err
+	}
 
 	// Build callback for E2E negotiation
-	callback := getServerAuthCallback(nil, cb, p)
+	callback := getServerAuthCallback(nil, cb, cl, p)
+
+	e2eClient, err := xxdk.LoginEphemeral(net, callback, identity, p)
+	if err != nil {
+		return nil, err
+	}
 
 	// Return an ephemeral E2e object
-	return xxdk.LoginEphemeral(net, callback, identity, p)
+	return &ConnectionServer{e2eClient, cl}, nil
+}
+
+// ConnectionServer contains
+type ConnectionServer struct {
+	E2e *xxdk.E2e
+	Cl  *ConnectionList
 }
 
 // handler provides an implementation for the Connection interface.
@@ -141,6 +167,12 @@ type handler struct {
 	partner partner.Manager
 	e2e     clientE2e.Handler
 	params  xxdk.E2EParams
+
+	// 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
 }
 
 // BuildConnection assembles a Connection object
@@ -148,20 +180,42 @@ type handler struct {
 // partner.Manager.
 func BuildConnection(partner partner.Manager, e2eHandler clientE2e.Handler,
 	auth auth.State, p xxdk.E2EParams) 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.
@@ -172,17 +226,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.
@@ -213,3 +275,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 2b2160fd516fb84e59d82723392f4fb17751ed1c..3f6122df73beeafd49129e1ab0d859e32e3a8ea5 100644
--- a/e2e/receive/switchboard.go
+++ b/e2e/receive/switchboard.go
@@ -56,20 +56,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.
@@ -145,8 +148,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
@@ -162,12 +165,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/restlike/connect/server.go b/restlike/connect/server.go
index 9ba7544d07ef219442f67e373e3efcdfac95a1da..9320d5e5c924f82107567908b62e0babf5959e08 100644
--- a/restlike/connect/server.go
+++ b/restlike/connect/server.go
@@ -23,7 +23,7 @@ type Server struct {
 // NewServer builds a RestServer with connect.Connection and
 // the provided arguments, then registers necessary external services
 func NewServer(identity xxdk.ReceptionIdentity, net *xxdk.Cmix,
-	p xxdk.E2EParams) (*Server, error) {
+	p xxdk.E2EParams, clParams connect.ConnectionListParams) (*Server, error) {
 	newServer := &Server{
 		receptionId: identity.ID,
 		endpoints:   restlike.NewEndpoints(),
@@ -36,7 +36,7 @@ func NewServer(identity xxdk.ReceptionIdentity, net *xxdk.Cmix,
 	}
 
 	// Build the connection listener
-	_, err := connect.StartServer(identity, cb, net, p)
+	_, err := connect.StartServer(identity, cb, net, p, clParams)
 	if err != nil {
 		return nil, err
 	}
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/xxdk/cmix.go b/xxdk/cmix.go
index 6e2999850bddb2f4762478d5f3115b510831897f..fbc08044b3cdb27bc8224cdff2805dff8efd09eb 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 8e387c078047de544f126bbdadafe4dff43ec15b..78e3c5d1daee69650eb84ac45fedf105221b2297 100644
--- a/xxdk/e2e.go
+++ b/xxdk/e2e.go
@@ -76,11 +76,13 @@ func LoginLegacy(client *Cmix, params E2EParams, callbacks AuthCallbacks) (
 		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 {
@@ -96,14 +98,8 @@ func LoginLegacy(client *Cmix, params E2EParams, callbacks AuthCallbacks) (
 		return nil, err
 	}
 
-	u := m.Cmix.GetUser()
-	m.e2eIdentity = ReceptionIdentity{
-		ID:            u.ReceptionID,
-		RSAPrivatePem: u.ReceptionRSA,
-		Salt:          u.ReceptionSalt,
-		DHKeyPrivate:  u.E2eDhPrivateKey,
-	}
-
+	m.e2eIdentity, err = buildReceptionIdentity(userInfo, m.e2e.GetGroup(),
+		m.e2e.GetHistoricalDHPrivkey())
 	return m, err
 }
 
@@ -189,12 +185,10 @@ func LoginWithProtoClient(storageDir string, password []byte,
 		return nil, err
 	}
 
-	return Login(c, callbacks, ReceptionIdentity{
-		ID:            protoUser.ReceptionID,
-		RSAPrivatePem: protoUser.ReceptionRSA,
-		Salt:          protoUser.ReceptionSalt,
-		DHKeyPrivate:  protoUser.E2eDhPrivateKey,
-	}, e2eParams)
+	userInfo := c.GetStorage().PortableUserInfo()
+	receptionIdentity, err := buildReceptionIdentity(userInfo,
+		c.GetStorage().GetE2EGroup(), protoUser.E2eDhPrivateKey)
+	return Login(c, callbacks, receptionIdentity, e2eParams)
 }
 
 // login creates a new xxdk.E2e backed by the given versioned.KV
@@ -202,7 +196,11 @@ func login(client *Cmix, callbacks AuthCallbacks, identity ReceptionIdentity,
 	kv *versioned.KV, params E2EParams) (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
 	}
@@ -211,35 +209,31 @@ func login(client *Cmix, callbacks AuthCallbacks, identity ReceptionIdentity,
 			identity.ID.String())
 	}
 
-	e2eGrp := client.GetStorage().GetE2EGroup()
 	m = &E2e{
 		Cmix:        client,
 		backup:      &Container{},
 		e2eIdentity: identity,
 	}
-
-	client.network.AddIdentity(identity.ID, time.Time{}, true)
-
-	//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 {
-		jww.WARN.Printf("Cannot load e2e store (will init instead): %s",
-			err.Error())
 		//initialize the e2e storage
-		err = e2e.Init(kv, identity.ID, identity.DHKeyPrivate, e2eGrp,
-			rekey.GetDefaultEphemeralParams())
+		jww.INFO.Printf("Initializing new e2e.Handler for %s", identity.ID.String())
+		err = e2e.Init(kv, identity.ID, dhPrivKey, e2eGrp,
+			params.Rekey)
 		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())
@@ -263,14 +257,15 @@ func login(client *Cmix, callbacks AuthCallbacks, identity ReceptionIdentity,
 		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()
 
@@ -334,15 +329,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()
@@ -359,15 +345,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(),
@@ -461,3 +454,25 @@ 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 {