diff --git a/README.md b/README.md
index 0d0938ebbe404dc1cefd87833031017d3d9c6564..8c29768b63c9fd9e3b119e6832f1931bdbf71b53 100644
--- a/README.md
+++ b/README.md
@@ -163,6 +163,9 @@ Flags:
       --accept-channel            Accept the channel request for the corresponding recipient ID
       --auth-timeout uint         The number of seconds to wait for an authentication channelto confirm (default 120)
       --delete-all-requests       Delete the all contact requests, both sent and received.
+      --backupIn string           Path to load backup client from
+      --backupOut string          Path to output backup client.
+      --backupPass string         Passphrase to encrypt/decrypt backup
       --delete-channel            Delete the channel information for the corresponding recipient ID
       --delete-receive-requests   Delete the all received contact requests.
       --delete-sent-requests      Delete the all sent contact requests.
diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go
index 93446db2b9bee15a5755f49d62ce42bcedf15794..47228cff42516de89903c7090d6fd33eb8e74054 100644
--- a/api/authenticatedChannel.go
+++ b/api/authenticatedChannel.go
@@ -9,6 +9,8 @@ package api
 
 import (
 	"encoding/binary"
+	"math/rand"
+
 	"github.com/cloudflare/circl/dh/sidh"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
@@ -20,7 +22,6 @@ import (
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/primitives/id"
-	"math/rand"
 )
 
 // RequestAuthenticatedChannel sends a request to another party to establish an
@@ -44,6 +45,20 @@ func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact,
 		c.storage, c.network)
 }
 
+// ResetSession resets an authenticate channel that already exists
+func (c *Client) ResetSession(recipient, me contact.Contact,
+	message string) (id.Round, error) {
+	jww.INFO.Printf("ResetSession(%s)", recipient.ID)
+
+	if !c.network.GetHealthTracker().IsHealthy() {
+		return 0, errors.New("Cannot request authenticated channel " +
+			"creation when the network is not healthy")
+	}
+
+	return auth.ResetSession(recipient, me, c.rng.GetStream(),
+		c.storage, c.network)
+}
+
 // GetAuthRegistrar gets the object which allows the registration of auth
 // callbacks
 func (c *Client) GetAuthRegistrar() interfaces.Auth {
@@ -76,8 +91,7 @@ func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) (id.Roun
 			"creation when the network is not healthy")
 	}
 
-	return auth.ConfirmRequestAuth(recipient, c.rng.GetStream(),
-		c.storage, c.network)
+	return c.auth.ConfirmRequestAuth(recipient)
 }
 
 // VerifyOwnership checks if the ownership proof on a passed contact matches the
@@ -144,7 +158,7 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co
 		Source: precan.ID[:],
 	}, me)
 
-	//slient (rekey)
+	// slient (rekey)
 	c.storage.GetEdge().Add(edge.Preimage{
 		Data:   sessionPartner.GetSilentPreimage(),
 		Type:   preimage.Silent,
@@ -165,7 +179,6 @@ func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Co
 		Source: precan.ID[:],
 	}, me)
 
-
 	return precan, err
 }
 
diff --git a/api/client.go b/api/client.go
index a289fea1e030118346df4011ef5601e49438fe6d..c3ff5cd85229da9bcc54e0d06195b1f353dc3636 100644
--- a/api/client.go
+++ b/api/client.go
@@ -24,6 +24,7 @@ import (
 	"gitlab.com/elixxir/client/storage/edge"
 	"gitlab.com/elixxir/client/switchboard"
 	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/elixxir/crypto/backup"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/primitives/version"
@@ -69,6 +70,9 @@ type Client struct {
 
 	// Event reporting in event.go
 	events *eventManager
+
+	// Handles the triggering and delivery of backups
+	backup *interfaces.BackupContainer
 }
 
 // NewClient creates client storage, generates keys, connects, and registers
@@ -163,6 +167,51 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte,
 	return nil
 }
 
+// NewClientFromBackup constructs a new Client from an encrypted backup. The backup
+// is decrypted using the backupPassphrase. On success a successful client creation,
+//// the function will return a JSON encoded list of the E2E partners
+//// contained in the backup.
+func NewClientFromBackup(ndfJSON, storageDir, sessionPassword,
+	backupPassphrase string, backupFileContents []byte) ([]*id.ID, error) {
+
+	backUp := &backup.Backup{}
+	err := backUp.Decrypt(backupPassphrase, backupFileContents)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to unmarshal decrypted client contents.")
+	}
+
+	usr := user.NewUserFromBackup(backUp)
+
+	// Parse the NDF
+	def, err := parseNDF(ndfJSON)
+	if err != nil {
+		return nil, err
+	}
+
+	cmixGrp, e2eGrp := decodeGroups(def)
+
+	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+
+	// Create storage object.
+	// Note we do not need registration
+	storageSess, err := checkVersionAndSetupStorage(def, storageDir, []byte(sessionPassword), usr,
+		cmixGrp, e2eGrp, rngStreamGen, false, backUp.RegistrationCode)
+
+	// Set registration values in storage
+	storageSess.User().SetReceptionRegistrationValidationSignature(backUp.ReceptionIdentity.RegistrarSignature)
+	storageSess.User().SetTransmissionRegistrationValidationSignature(backUp.TransmissionIdentity.RegistrarSignature)
+	storageSess.User().SetRegistrationTimestamp(backUp.RegistrationTimestamp)
+
+	//move the registration state to indicate registered with registration on proto client
+	err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete)
+	if err != nil {
+		return nil, err
+	}
+
+	return backUp.Contacts.Identities, nil
+}
+
 // OpenClient session, but don't connect to the network or log in
 func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) {
 	jww.INFO.Printf("OpenClient()")
@@ -194,6 +243,7 @@ func OpenClient(storageDir string, password []byte, parameters params.Network) (
 		parameters:         parameters,
 		clientErrorChannel: make(chan interfaces.ClientError, 1000),
 		events:             newEventManager(),
+		backup:             &interfaces.BackupContainer{},
 	}
 
 	return c, nil
@@ -304,7 +354,8 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie
 	}
 
 	// initialize the auth tracker
-	c.auth = auth.NewManager(c.switchboard, c.storage, c.network, parameters.ReplayRequests)
+	c.auth = auth.NewManager(c.switchboard, c.storage, c.network, c.rng,
+		c.backup.TriggerBackup, parameters.ReplayRequests)
 
 	// Add all processes to the followerServices
 	err = c.registerFollower()
@@ -363,7 +414,8 @@ func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte,
 	}
 
 	// initialize the auth tracker
-	c.auth = auth.NewManager(c.switchboard, c.storage, c.network, parameters.ReplayRequests)
+	c.auth = auth.NewManager(c.switchboard, c.storage, c.network, c.rng,
+		c.backup.TriggerBackup, parameters.ReplayRequests)
 
 	err = c.registerFollower()
 	if err != nil {
@@ -420,7 +472,8 @@ func LoginWithProtoClient(storageDir string, password []byte, protoClientJSON []
 	}
 
 	// initialize the auth tracker
-	c.auth = auth.NewManager(c.switchboard, c.storage, c.network, parameters.ReplayRequests)
+	c.auth = auth.NewManager(c.switchboard, c.storage, c.network, c.rng,
+		c.backup.TriggerBackup, parameters.ReplayRequests)
 
 	err = c.registerFollower()
 	if err != nil {
@@ -641,6 +694,12 @@ func (c *Client) GetNetworkInterface() interfaces.NetworkManager {
 	return c.network
 }
 
+// GetBackup returns a pointer to the backup container so that the backup can be
+// set and triggered.
+func (c *Client) GetBackup() *interfaces.BackupContainer {
+	return c.backup
+}
+
 // GetRateLimitParams retrieves the rate limiting parameters.
 func (c *Client) GetRateLimitParams() (uint32, uint32, int64) {
 	rateLimitParams := c.storage.GetBucketParams().Get()
@@ -705,7 +764,7 @@ func (c *Client) DeleteReceiveRequests() error {
 // DeleteContact is a function which removes a partner from Client's storage
 func (c *Client) DeleteContact(partnerId *id.ID) error {
 	jww.DEBUG.Printf("Deleting contact with ID %s", partnerId)
-	//get the partner so they can be removed from preiamge store
+	// get the partner so that they can be removed from preimage store
 	partner, err := c.storage.E2e().GetPartner(partnerId)
 	if err != nil {
 		return errors.WithMessagef(err, "Could not delete %s because "+
@@ -720,6 +779,10 @@ func (c *Client) DeleteContact(partnerId *id.ID) error {
 	if err = c.storage.E2e().DeletePartner(partnerId); err != nil {
 		return err
 	}
+
+	// Trigger backup
+	c.backup.TriggerBackup("contact deleted")
+
 	//delete the preimages
 	if err = c.storage.GetEdge().Remove(edge.Preimage{
 		Data:   e2ePreimage,
diff --git a/api/permissioning.go b/api/permissioning.go
index 85aac34b93f333f4994c49b1dac1ec868965807a..2dd62ec87dd8a95f43bce9f93df31db105edc4ca 100644
--- a/api/permissioning.go
+++ b/api/permissioning.go
@@ -73,8 +73,6 @@ func (c *Client) ConstructProtoUerFile() ([]byte, error) {
 		RegCode:                      regCode,
 		TransmissionRegValidationSig: c.storage.User().GetTransmissionRegistrationValidationSignature(),
 		ReceptionRegValidationSig:    c.storage.User().GetReceptionRegistrationValidationSignature(),
-		CmixDhPrivateKey:             c.GetStorage().Cmix().GetDHPrivateKey(),
-		CmixDhPublicKey:              c.GetStorage().Cmix().GetDHPublicKey(),
 		E2eDhPrivateKey:              c.GetStorage().E2e().GetDHPrivateKey(),
 		E2eDhPublicKey:               c.GetStorage().E2e().GetDHPublicKey(),
 	}
diff --git a/api/user.go b/api/user.go
index 1b22a0877235636c55bbd2be922cddfcf4fe2b39..4377f1b0333cb41ca3766cafa4c86185240836d7 100644
--- a/api/user.go
+++ b/api/user.go
@@ -34,10 +34,10 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U
 	// CMIX Keygen
 	var transmissionRsaKey, receptionRsaKey *rsa.PrivateKey
 
-	var cMixKeyBytes, e2eKeyBytes, transmissionSalt, receptionSalt []byte
+	var e2eKeyBytes, transmissionSalt, receptionSalt []byte
 
-	cMixKeyBytes, e2eKeyBytes, transmissionSalt, receptionSalt,
-		transmissionRsaKey, receptionRsaKey = createDhKeys(rng, cmix, e2e)
+	e2eKeyBytes, transmissionSalt, receptionSalt,
+		transmissionRsaKey, receptionRsaKey = createDhKeys(rng, e2e)
 
 	// Salt, UID, etc gen
 	stream := rng.GetStream()
@@ -83,32 +83,17 @@ func createNewUser(rng *fastRNG.StreamGenerator, cmix, e2e *cyclic.Group) user.U
 		ReceptionSalt:    receptionSalt,
 		ReceptionRSA:     receptionRsaKey,
 		Precanned:        false,
-		CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes),
 		E2eDhPrivateKey:  e2e.NewIntFromBytes(e2eKeyBytes),
 	}
 }
 
 func createDhKeys(rng *fastRNG.StreamGenerator,
-	cmix, e2e *cyclic.Group) (cMixKeyBytes, e2eKeyBytes,
+	e2e *cyclic.Group) (e2eKeyBytes,
 	transmissionSalt, receptionSalt []byte,
 	transmissionRsaKey, receptionRsaKey *rsa.PrivateKey) {
 	wg := sync.WaitGroup{}
 
-	wg.Add(4)
-
-	go func() {
-		defer wg.Done()
-		var err error
-		// FIXME: Why 256 bits? -- this is spec but not explained, it has
-		// to do with optimizing operations on one side and still preserves
-		// decent security -- cite this.
-		stream := rng.GetStream()
-		cMixKeyBytes, err = csprng.GenerateInGroup(cmix.GetPBytes(), 256, stream)
-		stream.Close()
-		if err != nil {
-			jww.FATAL.Panicf(err.Error())
-		}
-	}()
+	wg.Add(3)
 
 	go func() {
 		defer wg.Done()
@@ -186,8 +171,6 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.
 		ReceptionSalt:    salt,
 		Precanned:        true,
 		E2eDhPrivateKey:  e2e.NewIntFromBytes(e2eKeyBytes),
-		// NOTE: These are dummy/not used
-		CmixDhPrivateKey: cmix.NewInt(1),
 		TransmissionRSA:  rsaKey,
 		ReceptionRSA:     rsaKey,
 	}
@@ -196,15 +179,6 @@ func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.
 // createNewVanityUser generates an identity for cMix
 // The identity's ReceptionID is not random but starts with the supplied prefix
 func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix string) user.User {
-	// CMIX Keygen
-	// FIXME: Why 256 bits? -- this is spec but not explained, it has
-	// to do with optimizing operations on one side and still preserves
-	// decent security -- cite this.
-	cMixKeyBytes, err := csprng.GenerateInGroup(cmix.GetPBytes(), 256, rng)
-	if err != nil {
-		jww.FATAL.Panicf(err.Error())
-	}
-
 	// DH Keygen
 	// FIXME: Why 256 bits? -- this is spec but not explained, it has
 	// to do with optimizing operations on one side and still preserves
@@ -311,7 +285,6 @@ func createNewVanityUser(rng csprng.Source, cmix, e2e *cyclic.Group, prefix stri
 		ReceptionSalt:    receptionSalt,
 		ReceptionRSA:     receptionRsaKey,
 		Precanned:        false,
-		CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes),
 		E2eDhPrivateKey:  e2e.NewIntFromBytes(e2eKeyBytes),
 	}
 }
diff --git a/auth/callback.go b/auth/callback.go
index 7ae57d144bfb2a3c6e0b7b8cd02cb610d4694553..a437d834ccc7240cdcc5ee18ee85528c6fc99d8a 100644
--- a/auth/callback.go
+++ b/auth/callback.go
@@ -24,10 +24,9 @@ import (
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
 	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
-	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
 )
 
 func (m *Manager) StartProcesses() (stoppable.Stoppable, error) {
@@ -153,6 +152,68 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
 	events.Report(1, "Auth", "RequestReceived", em)
 
 	/*do state edge checks*/
+	// Check if this is a reset, which are valid as of version 1
+	// Resets happen when our fingerprint is new AND we are
+	// the latest fingerprint to be added to the list and we already have
+	// a negotiation or authenticated channel in progress
+	fp := cAuth.CreateNegotiationFingerprint(partnerPubKey,
+		partnerSIDHPubKey)
+	newFP, latest := m.storage.Auth().AddIfNew(partnerID, fp)
+	resetSession := false
+	autoConfirm := false
+	if baseFmt.GetVersion() >= 1 && newFP && latest {
+		// If we had an existing session and it's new, then yes, we
+		// want to reset
+		if _, err := m.storage.E2e().GetPartner(partnerID); err == nil {
+			jww.INFO.Printf("Resetting session for %s", partnerID)
+			resetSession = true
+			// Most likely, we got 2 reset sessions at once, so this
+			// is a non-fatal error but we will record a warning
+			// just in case.
+			err = m.storage.E2e().DeletePartner(partnerID)
+			if err != nil {
+				jww.WARN.Printf("Unable to delete channel: %+v",
+					err)
+			}
+			// Also delete any existing request, sent or received
+			m.storage.Auth().Delete(partnerID)
+		}
+		// If we had an existing negotiation open, then it depends
+
+		// If we've only received, then user has not confirmed, treat as
+		// a non-duplicate request, so delete the old one (to cause new
+		// callback to be called)
+		rType, _, _, err := m.storage.Auth().GetRequest(partnerID)
+		if err != nil && rType == auth.Receive {
+			m.storage.Auth().Delete(partnerID)
+		}
+
+		// If we've already Sent and are now receiving,
+		// then we attempt auto-confirm as below
+		// This poses a potential problem if it is truly a session
+		// reset by the other user, because we may not actually
+		// autoconfirm based on our public key compared to theirs.
+		// This could result in a permanently broken association, as
+		// the other side has attempted to reset it's session and
+		// can no longer detect a sent request collision, so this side
+		// cannot ever successfully resend.
+		// We prevent this by stopping session resets if they
+		// are called when the other side is in the "Sent" state.
+		// If the other side is in the "received" state we also block,
+		// but we could autoconfirm.
+		// Note that you can still get into this state by one side
+		// deleting requests. In that case, both sides need to clear
+		// out all requests and retry negotiation from scratch.
+		// NOTE: This protocol part could use an overhaul/second look,
+		//       there's got to be a way to do this with far less state
+		//       but this is the spec so we're sticking with it for now.
+
+		// If not an existing request, we do nothing.
+	} else {
+		jww.WARN.Printf("Version: %d, newFP: %v, latest: %v", baseFmt.GetVersion(),
+			newFP, latest)
+	}
+
 	// check if a relationship already exists.
 	// if it does and the keys used are the same as we have, send a
 	// confirmation in case there are state issues.
@@ -232,57 +293,13 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
 				// If I do, delete my request on disk
 				m.storage.Auth().Delete(partnerID)
 
-				//process the inner payload
-				facts, _, err := fact.UnstringifyFactList(
-					string(requestFmt.msgPayload))
-				if err != nil {
-					em := fmt.Sprintf("failed to parse facts and message "+
-						"from Auth Request: %s", err)
-					jww.WARN.Print(em)
-					events.Report(10, "Auth", "RequestError", em)
-					return
-				}
-
+				// Do the normal, fall out of this if block and
 				// create the contact, note that we use the data
 				// sent in the request and not any data we had
 				// already
-				partnerContact := contact.Contact{
-					ID:             partnerID,
-					DhPubKey:       partnerPubKey,
-					OwnershipProof: copySlice(ownership),
-					Facts:          facts,
-				}
 
-				// add a confirmation to disk
-				if err = m.storage.Auth().AddReceived(partnerContact,
-					partnerSIDHPubKey); err != nil {
-					em := fmt.Sprintf("failed to store contact Auth "+
-						"Request: %s", err)
-					jww.WARN.Print(em)
-					events.Report(10, "Auth", "RequestError", em)
-				}
+				autoConfirm = true
 
-				// Call ConfirmRequestAuth to send confirmation
-				rngGen := fastRNG.NewStreamGenerator(1, 1,
-					csprng.NewSystemRNG)
-				rng := rngGen.GetStream()
-				rndNum, err := ConfirmRequestAuth(partnerContact,
-					rng, m.storage, m.net)
-				if err != nil {
-					jww.ERROR.Printf("Could not ConfirmRequestAuth: %+v",
-						err)
-					return
-				}
-
-				jww.INFO.Printf("ConfirmRequestAuth to %s on round %d",
-					partnerID, rndNum)
-				c := partnerContact
-				cbList := m.confirmCallbacks.Get(c.ID)
-				for _, cb := range cbList {
-					ccb := cb.(interfaces.ConfirmCallback)
-					go ccb(c)
-				}
-				return
 			}
 		}
 	}
@@ -317,12 +334,44 @@ func (m *Manager) handleRequest(cmixMsg format.Message,
 		return
 	}
 
-	//  fixme: if a crash occurs before or during the calls, the notification
-	//  will never be sent.
-	cbList := m.requestCallbacks.Get(c.ID)
-	for _, cb := range cbList {
-		rcb := cb.(interfaces.RequestCallback)
-		go rcb(c)
+	// We autoconfirm anytime we had already sent a request OR we are
+	// resetting an existing session
+	var rndNum id.Round
+	if autoConfirm || resetSession {
+		// Call ConfirmRequestAuth to send confirmation
+		rndNum, err = m.ConfirmRequestAuth(c)
+		if err != nil {
+			jww.ERROR.Printf("Could not ConfirmRequestAuth: %+v",
+				err)
+			return
+		}
+
+		if autoConfirm {
+			jww.INFO.Printf("ConfirmRequestAuth to %s on round %d",
+				partnerID, rndNum)
+			cbList := m.confirmCallbacks.Get(c.ID)
+			for _, cb := range cbList {
+				ccb := cb.(interfaces.ConfirmCallback)
+				go ccb(c)
+			}
+		}
+		if resetSession {
+			jww.INFO.Printf("Reset Auth %s on round %d",
+				partnerID, rndNum)
+			cbList := m.resetCallbacks.Get(c.ID)
+			for _, cb := range cbList {
+				ccb := cb.(interfaces.ResetCallback)
+				go ccb(c)
+			}
+		}
+	} else {
+		//  fixme: if a crash occurs before or during the calls, the notification
+		//  will never be sent.
+		cbList := m.requestCallbacks.Get(c.ID)
+		for _, cb := range cbList {
+			rcb := cb.(interfaces.RequestCallback)
+			go rcb(c)
+		}
 	}
 	return
 }
@@ -427,6 +476,8 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group,
 			sr.GetPartner(), err)
 	}
 
+	m.backupTrigger("received confirmation from request")
+
 	//remove the confirm fingerprint
 	fp := sr.GetFingerprint()
 	if err := m.storage.GetEdge().Remove(edge.Preimage{
@@ -438,40 +489,7 @@ func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group,
 			sr.GetPartner(), err)
 	}
 
-	//add the e2e and rekey firngeprints
-	//e2e
-	sessionPartner, err := m.storage.E2e().GetPartner(sr.GetPartner())
-	if err != nil {
-		jww.FATAL.Panicf("Cannot find %s right after creating: %+v", sr.GetPartner(), err)
-	}
-	me := m.storage.GetUser().ReceptionID
-
-	m.storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetE2EPreimage(),
-		Type:   preimage.E2e,
-		Source: sr.GetPartner()[:],
-	}, me)
-
-	//silent (rekey)
-	m.storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetSilentPreimage(),
-		Type:   preimage.Silent,
-		Source: sr.GetPartner()[:],
-	}, me)
-
-	// File transfer end
-	m.storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetFileTransferPreimage(),
-		Type:   preimage.EndFT,
-		Source: sr.GetPartner()[:],
-	}, me)
-
-	//group Request
-	m.storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetGroupRequestPreimage(),
-		Type:   preimage.GroupRq,
-		Source: sr.GetPartner()[:],
-	}, me)
+	addPreimages(sr.GetPartner(), m.storage)
 
 	// delete the in progress negotiation
 	// this undoes the request lock
diff --git a/auth/cmix.go b/auth/cmix.go
new file mode 100644
index 0000000000000000000000000000000000000000..0f76dd7efc1f757613c0cd43897afb851f4bd93a
--- /dev/null
+++ b/auth/cmix.go
@@ -0,0 +1,62 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// cmix.go cMix functions for the auth module
+
+package auth
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/preimage"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// getMixPayloadSize calculates the payload size of a cMix Message based on the
+// total message size.
+// TODO: Maybe move this to primitives and export it?
+// FIXME: This can only vary per cMix network target, and it could be scoped
+//        to a Client instance.
+func getMixPayloadSize(primeSize int) int {
+	return 2*primeSize - format.AssociatedDataSize - 1
+}
+
+// sendAuthRequest is a helper to send the cMix Message after the request
+// is created.
+func sendAuthRequest(recipient *id.ID, contents, mac []byte, primeSize int,
+	fingerprint format.Fingerprint, net interfaces.NetworkManager,
+	cMixParams params.CMIX) (id.Round, error) {
+	cmixMsg := format.NewMessage(primeSize)
+	cmixMsg.SetKeyFP(fingerprint)
+	cmixMsg.SetMac(mac)
+	cmixMsg.SetContents(contents)
+
+	jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s",
+		recipient, cmixMsg.Digest())
+
+	cMixParams.IdentityPreimage = preimage.GenerateRequest(recipient)
+	cMixParams.DebugTag = "auth.Request"
+	round, _, err := net.SendCMIX(cmixMsg, recipient, cMixParams)
+	if err != nil {
+		// if the send fails just set it to failed, it will
+		// but automatically retried
+		return 0, errors.WithMessagef(err, "Auth Request with %s "+
+			"(msgDigest: %s) failed to transmit: %+v", recipient,
+			cmixMsg.Digest(), err)
+	}
+
+	em := fmt.Sprintf("Auth Request with %s (msgDigest: %s) sent"+
+		" on round %d", recipient, cmixMsg.Digest(), round)
+	jww.INFO.Print(em)
+	net.GetEventManager().Report(1, "Auth", "RequestSent", em)
+	return round, nil
+}
diff --git a/auth/confirm.go b/auth/confirm.go
index f0f7ba84975ae9f8e261c68014fabe8f347bc2d7..ecc32ac304282ad123125b761916e3ea9af9e697 100644
--- a/auth/confirm.go
+++ b/auth/confirm.go
@@ -9,108 +9,125 @@ package auth
 
 import (
 	"fmt"
+
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/client/interfaces"
 	"gitlab.com/elixxir/client/interfaces/params"
 	"gitlab.com/elixxir/client/interfaces/preimage"
 	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/client/storage/edge"
 	util "gitlab.com/elixxir/client/storage/utility"
 	"gitlab.com/elixxir/crypto/contact"
-	"gitlab.com/elixxir/crypto/diffieHellman"
 	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/primitives/id"
-	"io"
 )
 
-func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
-	storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
+func (m *Manager) ConfirmRequestAuth(partner contact.Contact) (id.Round, error) {
 
 	/*edge checking*/
 
 	// check that messages can be sent over the network
-	if !net.GetHealthTracker().IsHealthy() {
+	if !m.net.GetHealthTracker().IsHealthy() {
 		return 0, errors.New("Cannot confirm authenticated message " +
 			"when the network is not healthy")
 	}
 
+	// Cannot confirm already established channels
+	if _, err := m.storage.E2e().GetPartner(partner.ID); err == nil {
+		em := fmt.Sprintf("Cannot FonritmRequestAuth for %s, "+
+			"channel already exists. Ignoring", partner.ID)
+		jww.WARN.Print(em)
+		m.net.GetEventManager().Report(5, "Auth",
+			"ConfirmRequestAuthIgnored", em)
+		//exit
+		return 0, errors.New(em)
+	}
+
 	// check if the partner has an auth in progress
 	// this takes the lock, from this point forward any errors need to
 	// release the lock
-	storedContact, theirSidhKey, err := storage.Auth().GetReceivedRequest(
+	storedContact, theirSidhKey, err := m.storage.Auth().GetReceivedRequest(
 		partner.ID)
 	if err != nil {
 		return 0, errors.Errorf(
 			"failed to find a pending Auth Request: %s",
 			err)
 	}
-	defer storage.Auth().Done(partner.ID)
+	defer m.storage.Auth().Done(partner.ID)
 
 	// verify the passed contact matches what is stored
 	if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 {
-		storage.Auth().Done(partner.ID)
 		return 0, errors.WithMessage(err,
 			"Pending Auth Request has different pubkey than stored")
 	}
 
-	grp := storage.E2e().GetGroup()
+	grp := m.storage.E2e().GetGroup()
 
 	/*cryptographic generation*/
 
-	//generate ownership proof
-	ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(),
-		partner.DhPubKey, storage.E2e().GetGroup())
+	// generate ownership proof
+	ownership := cAuth.MakeOwnershipProof(m.storage.E2e().GetDHPrivateKey(),
+		partner.DhPubKey, m.storage.E2e().GetGroup())
 
-	//generate new keypair
-	newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng)
-	newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp)
+	rng := m.rng.GetStream()
 
+	// generate new keypair
+	dhGrp := grp
+	dhPriv, dhPub := genDHKeys(dhGrp, rng)
 	sidhVariant := util.GetCompatibleSIDHVariant(theirSidhKey.Variant())
-	newSIDHPrivKey := util.NewSIDHPrivateKey(sidhVariant)
-	newSIDHPubKey := util.NewSIDHPublicKey(sidhVariant)
-	newSIDHPrivKey.Generate(rng)
-	newSIDHPrivKey.GeneratePublicKey(newSIDHPubKey)
+	sidhPriv, sidhPub := util.GenerateSIDHKeyPair(sidhVariant, rng)
+
+	rng.Close()
 
 	/*construct message*/
 	// we build the payload before we save because it is technically fallible
 	// which can get into a bricked state if it fails
-	cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen())
+	cmixMsg := format.NewMessage(m.storage.Cmix().GetGroup().GetP().ByteLen())
 	baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen())
 	ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
 
 	// setup the encrypted payload
 	ecrFmt.SetOwnership(ownership)
-	ecrFmt.SetSidHPubKey(newSIDHPubKey)
+	ecrFmt.SetSidHPubKey(sidhPub)
 	// confirmation has no custom payload
 
-	//encrypt the payload
-	ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey,
+	// encrypt the payload
+	ecrPayload, mac := cAuth.Encrypt(dhPriv, partner.DhPubKey,
 		ecrFmt.data, grp)
 
-	//get the fingerprint from the old ownership proof
+	// get the fingerprint from the old ownership proof
 	fp := cAuth.MakeOwnershipProofFP(storedContact.OwnershipProof)
 	preimg := preimage.Generate(fp[:], preimage.Confirm)
 
-	//final construction
+	// final construction
 	baseFmt.SetEcrPayload(ecrPayload)
-	baseFmt.SetPubKey(newPubKey)
+	baseFmt.SetPubKey(dhPub)
 
 	cmixMsg.SetKeyFP(fp)
 	cmixMsg.SetMac(mac)
 	cmixMsg.SetContents(baseFmt.Marshal())
 
+	jww.TRACE.Printf("SendConfirm cMixMsg contents: %v",
+		cmixMsg.GetContents())
+
+	jww.TRACE.Printf("SendConfirm PARTNERPUBKEY: %v",
+		partner.DhPubKey.Bytes())
+	jww.TRACE.Printf("SendConfirm MYPUBKEY: %v", dhPub.Bytes())
+
+	jww.TRACE.Printf("SendConfirm ECRPAYLOAD: %v", baseFmt.GetEcrPayload())
+	jww.TRACE.Printf("SendConfirm MAC: %v", mac)
+
 	// fixme: channel can get into a bricked state if the first save occurs and
 	// the second does not or the two occur and the storage into critical
 	// messages does not occur
 
-	events := net.GetEventManager()
+	events := m.net.GetEventManager()
 
-	//create local relationship
-	p := storage.E2e().GetE2ESessionParams()
-	if err := storage.E2e().AddPartner(partner.ID, partner.DhPubKey,
-		newPrivKey, theirSidhKey, newSIDHPrivKey,
+	// create local relationship
+	p := m.storage.E2e().GetE2ESessionParams()
+	if err := m.storage.E2e().AddPartner(partner.ID, partner.DhPubKey,
+		dhPriv, theirSidhKey, sidhPriv,
 		p, p); err != nil {
 		em := fmt.Sprintf("Failed to create channel with partner (%s) "+
 			"on confirmation, this is likley a replay: %s",
@@ -119,44 +136,13 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
 		events.Report(10, "Auth", "SendConfirmError", em)
 	}
 
-	//add the preimages
-	sessionPartner, err := storage.E2e().GetPartner(partner.ID)
-	if err != nil {
-		jww.FATAL.Panicf("Cannot find %s right after creating: %+v", partner.ID, err)
-	}
-	me := storage.GetUser().ReceptionID
+	m.backupTrigger("confirmed authenticated channel")
 
-	//e2e
-	storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetE2EPreimage(),
-		Type:   preimage.E2e,
-		Source: partner.ID[:],
-	}, me)
-
-	//slient (rekey)
-	storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetSilentPreimage(),
-		Type:   preimage.Silent,
-		Source: partner.ID[:],
-	}, me)
-
-	// File transfer end
-	storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetFileTransferPreimage(),
-		Type:   preimage.EndFT,
-		Source: partner.ID[:],
-	}, me)
-
-	//group Request
-	storage.GetEdge().Add(edge.Preimage{
-		Data:   sessionPartner.GetGroupRequestPreimage(),
-		Type:   preimage.GroupRq,
-		Source: partner.ID[:],
-	}, me)
+	addPreimages(partner.ID, m.storage)
 
 	// delete the in progress negotiation
 	// this unlocks the request lock
-	//fixme - do these deletes at a later date
+	// fixme - do these deletes at a later date
 	/*if err := storage.Auth().Delete(partner.ID); err != nil {
 		return 0, errors.Errorf("UNRECOVERABLE! Failed to delete in "+
 			"progress negotiation with partner (%s) after creating confirmation: %+v",
@@ -170,7 +156,7 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
 	param.IdentityPreimage = preimg
 	param.DebugTag = "auth.Confirm"
 	/*send message*/
-	round, _, err := net.SendCMIX(cmixMsg, partner.ID, param)
+	round, _, err := m.net.SendCMIX(cmixMsg, partner.ID, param)
 	if err != nil {
 		// if the send fails just set it to failed, it will but automatically
 		// retried
@@ -186,3 +172,65 @@ func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
 
 	return round, nil
 }
+
+func addPreimages(partner *id.ID, store *storage.Session) {
+	// add the preimages
+	sessionPartner, err := store.E2e().GetPartner(partner)
+	if err != nil {
+		jww.FATAL.Panicf("Cannot find %s right after creating: %+v",
+			partner, err)
+	}
+
+	// Delete any known pre-existing edges for this partner
+	existingEdges, _ := store.GetEdge().Get(partner)
+	for i := range existingEdges {
+		delete := true
+		switch existingEdges[i].Type {
+		case preimage.E2e:
+		case preimage.Silent:
+		case preimage.EndFT:
+		case preimage.GroupRq:
+		default:
+			delete = false
+		}
+
+		if delete {
+			err = store.GetEdge().Remove(existingEdges[i], partner)
+			if err != nil {
+				jww.ERROR.Printf(
+					"Unable to delete %s edge for %s: %v",
+					existingEdges[i].Type, partner, err)
+			}
+		}
+	}
+
+	me := store.GetUser().ReceptionID
+
+	// e2e
+	store.GetEdge().Add(edge.Preimage{
+		Data:   sessionPartner.GetE2EPreimage(),
+		Type:   preimage.E2e,
+		Source: partner[:],
+	}, me)
+
+	// silent (rekey)
+	store.GetEdge().Add(edge.Preimage{
+		Data:   sessionPartner.GetSilentPreimage(),
+		Type:   preimage.Silent,
+		Source: partner[:],
+	}, me)
+
+	// File transfer end
+	store.GetEdge().Add(edge.Preimage{
+		Data:   sessionPartner.GetFileTransferPreimage(),
+		Type:   preimage.EndFT,
+		Source: partner[:],
+	}, me)
+
+	// group Request
+	store.GetEdge().Add(edge.Preimage{
+		Data:   sessionPartner.GetGroupRequestPreimage(),
+		Type:   preimage.GroupRq,
+		Source: partner[:],
+	}, me)
+}
diff --git a/auth/fmt.go b/auth/fmt.go
index 335867f41044eb65a5b5c3db27af669c72e60cb8..f267945be7934705f7046b948efc409c42f51d6d 100644
--- a/auth/fmt.go
+++ b/auth/fmt.go
@@ -17,26 +17,33 @@ import (
 	"gitlab.com/xx_network/primitives/id"
 )
 
+const requestFmtVersion = 1
+
 //Basic Format//////////////////////////////////////////////////////////////////
 type baseFormat struct {
 	data       []byte
 	pubkey     []byte
 	ecrPayload []byte
+	version    []byte
 }
 
 func newBaseFormat(payloadSize, pubkeySize int) baseFormat {
-	total := pubkeySize + sidhinterface.PubKeyByteSize + 1
+	total := pubkeySize
+	// Size of sidh pubkey
+	total += sidhinterface.PubKeyByteSize + 1
+	// Size of version
+	total += 1
 	if payloadSize < total {
 		jww.FATAL.Panicf("Size of baseFormat is too small (%d), must be big "+
 			"enough to contain public key (%d) and sidh key (%d)"+
-			"which totals to %d", payloadSize, pubkeySize,
-			sidhinterface.PubKeyByteSize+1, total)
+			"and version which totals to %d", payloadSize,
+			pubkeySize, sidhinterface.PubKeyByteSize+1, total)
 	}
 
 	jww.INFO.Printf("Empty Space RequestAuth: %d", payloadSize-total)
 
 	f := buildBaseFormat(make([]byte, payloadSize), pubkeySize)
-
+	f.version[0] = requestFmtVersion
 	return f
 }
 
@@ -47,10 +54,14 @@ func buildBaseFormat(data []byte, pubkeySize int) baseFormat {
 
 	start := 0
 	end := pubkeySize
-	f.pubkey = f.data[:end]
+	f.pubkey = f.data[start:end]
 
 	start = end
-	f.ecrPayload = f.data[start:]
+	end = len(f.data) - 1
+	f.ecrPayload = f.data[start:end]
+
+	f.version = f.data[end:]
+
 	return f
 }
 
@@ -58,14 +69,24 @@ func unmarshalBaseFormat(b []byte, pubkeySize int) (baseFormat, error) {
 	if len(b) < pubkeySize {
 		return baseFormat{}, errors.New("Received baseFormat too small")
 	}
+	bfmt := buildBaseFormat(b, pubkeySize)
+	version := bfmt.GetVersion()
+	if version != requestFmtVersion {
+		return baseFormat{}, errors.Errorf(
+			"Unknown baseFormat version: %d", version)
+	}
 
-	return buildBaseFormat(b, pubkeySize), nil
+	return bfmt, nil
 }
 
 func (f baseFormat) Marshal() []byte {
 	return f.data
 }
 
+func (f baseFormat) GetVersion() byte {
+	return f.version[0]
+}
+
 func (f baseFormat) GetPubKey(grp *cyclic.Group) *cyclic.Int {
 	return grp.NewIntFromBytes(f.pubkey)
 }
diff --git a/auth/fmt_test.go b/auth/fmt_test.go
index 1178fc0b705b3c6dc3ee57bacfe6ada38de54933..64785b399abc261400a346fceb68bc4ee0fc59c0 100644
--- a/auth/fmt_test.go
+++ b/auth/fmt_test.go
@@ -9,20 +9,26 @@ package auth
 
 import (
 	"bytes"
-	sidhinterface "gitlab.com/elixxir/client/interfaces/sidh"
-	"gitlab.com/xx_network/primitives/id"
 	"math/rand"
 	"reflect"
 	"testing"
+
+	sidhinterface "gitlab.com/elixxir/client/interfaces/sidh"
+	"gitlab.com/xx_network/primitives/id"
 )
 
 // Tests newBaseFormat
 func TestNewBaseFormat(t *testing.T) {
 	// Construct message
 	pubKeySize := 256
-	payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 1
+	payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 2
 	baseMsg := newBaseFormat(payloadSize, pubKeySize)
 
+	if baseMsg.GetVersion() != requestFmtVersion {
+		t.Errorf("Incorrect version: %d, expect %d",
+			baseMsg.GetVersion(), requestFmtVersion)
+	}
+
 	// Check that the base format was constructed properly
 	if !bytes.Equal(baseMsg.pubkey, make([]byte, pubKeySize)) {
 		t.Errorf("NewBaseFormat error: "+
@@ -31,7 +37,7 @@ func TestNewBaseFormat(t *testing.T) {
 			"\n\tReceived: %v", make([]byte, pubKeySize), baseMsg.pubkey)
 	}
 
-	expectedEcrPayloadSize := payloadSize - (pubKeySize)
+	expectedEcrPayloadSize := payloadSize - (pubKeySize) - 1
 	if !bytes.Equal(baseMsg.ecrPayload, make([]byte, expectedEcrPayloadSize)) {
 		t.Errorf("NewBaseFormat error: "+
 			"Unexpected payload field in base format."+
@@ -56,7 +62,7 @@ func TestNewBaseFormat(t *testing.T) {
 func TestBaseFormat_SetGetPubKey(t *testing.T) {
 	// Construct message
 	pubKeySize := 256
-	payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 1
+	payloadSize := pubKeySize + sidhinterface.PubKeyByteSize + 2
 	baseMsg := newBaseFormat(payloadSize, pubKeySize)
 
 	// Test setter
@@ -88,7 +94,7 @@ func TestBaseFormat_SetGetEcrPayload(t *testing.T) {
 	baseMsg := newBaseFormat(payloadSize, pubKeySize)
 
 	// Test setter
-	ecrPayloadSize := payloadSize - (pubKeySize)
+	ecrPayloadSize := payloadSize - (pubKeySize) - 1
 	ecrPayload := newPayload(ecrPayloadSize, "ecrPayload")
 	baseMsg.SetEcrPayload(ecrPayload)
 	if !bytes.Equal(ecrPayload, baseMsg.ecrPayload) {
@@ -123,7 +129,7 @@ func TestBaseFormat_MarshalUnmarshal(t *testing.T) {
 	pubKeySize := 256
 	payloadSize := (pubKeySize + sidhinterface.PubKeyByteSize) * 2
 	baseMsg := newBaseFormat(payloadSize, pubKeySize)
-	ecrPayloadSize := payloadSize - (pubKeySize)
+	ecrPayloadSize := payloadSize - (pubKeySize) - 1
 	ecrPayload := newPayload(ecrPayloadSize, "ecrPayload")
 	baseMsg.SetEcrPayload(ecrPayload)
 	grp := getGroup()
diff --git a/auth/manager.go b/auth/manager.go
index 20b341c3891212edf308ec9c7afcf43a5f1588f5..6be1c8bd1695af1ff5c5609ee6c58e4effea2b62 100644
--- a/auth/manager.go
+++ b/auth/manager.go
@@ -12,29 +12,37 @@ import (
 	"gitlab.com/elixxir/client/interfaces/message"
 	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/xx_network/primitives/id"
 )
 
 type Manager struct {
 	requestCallbacks *callbackMap
 	confirmCallbacks *callbackMap
+	resetCallbacks   *callbackMap
 
 	rawMessages chan message.Receive
 
-	storage *storage.Session
-	net     interfaces.NetworkManager
+	storage       *storage.Session
+	net           interfaces.NetworkManager
+	rng           *fastRNG.StreamGenerator
+	backupTrigger interfaces.TriggerBackup
 
 	replayRequests bool
 }
 
 func NewManager(sw interfaces.Switchboard, storage *storage.Session,
-	net interfaces.NetworkManager, replayRequests bool) *Manager {
+	net interfaces.NetworkManager, rng *fastRNG.StreamGenerator,
+	backupTrigger interfaces.TriggerBackup, replayRequests bool) *Manager {
 	m := &Manager{
 		requestCallbacks: newCallbackMap(),
 		confirmCallbacks: newCallbackMap(),
+		resetCallbacks:   newCallbackMap(),
 		rawMessages:      make(chan message.Receive, 1000),
 		storage:          storage,
 		net:              net,
+		rng:              rng,
+		backupTrigger:    backupTrigger,
 		replayRequests:   replayRequests,
 	}
 
@@ -93,6 +101,11 @@ func (m *Manager) RemoveSpecificConfirmCallback(id *id.ID) {
 	m.confirmCallbacks.RemoveSpecific(id)
 }
 
+// Adds a general callback to be used on auth session renegotiations.
+func (m *Manager) AddResetCallback(cb interfaces.ResetCallback) {
+	m.resetCallbacks.AddOverride(cb)
+}
+
 // ReplayRequests will iterate through all pending contact requests and resend them
 // to the desired contact.
 func (m *Manager) ReplayRequests() {
diff --git a/auth/request.go b/auth/request.go
index 7148cf5a4b0dc1d171da6eb62178cd08c60620b7..75459786839b8fd6730b9bc58bb44cca67cbb7c5 100644
--- a/auth/request.go
+++ b/auth/request.go
@@ -8,7 +8,9 @@
 package auth
 
 import (
-	"fmt"
+	"io"
+	"strings"
+
 	"github.com/cloudflare/circl/dh/sidh"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
@@ -24,25 +26,50 @@ import (
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
 	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
-	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/primitives/id"
-	"io"
-	"strings"
 )
 
 const terminator = ";"
 
 func RequestAuth(partner, me contact.Contact, rng io.Reader,
 	storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
-	/*edge checks generation*/
-
-	// check that an authenticated channel does not already exists
+	// check that an authenticated channel does not already exist
 	if _, err := storage.E2e().GetPartner(partner.ID); err == nil ||
 		!strings.Contains(err.Error(), e2e.NoPartnerErrorStr) {
 		return 0, errors.Errorf("Authenticated channel already " +
 			"established with partner")
 	}
 
+	return requestAuth(partner, me, rng, false, storage, net)
+}
+
+func ResetSession(partner, me contact.Contact, rng io.Reader,
+	storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
+
+	// Delete authenticated channel if it exists.
+	if err := storage.E2e().DeletePartner(partner.ID); err != nil {
+		jww.WARN.Printf("Unable to delete partner when "+
+			"resetting session: %+v", err)
+	} else {
+		// Delete any stored sent/received requests
+		storage.Auth().Delete(partner.ID)
+	}
+
+	rqType, _, _, err := storage.Auth().GetRequest(partner.ID)
+	if err == nil && rqType == auth.Sent {
+		return 0, errors.New("Cannot reset a session after " +
+			"sending request, caller must resend request instead")
+	}
+
+	// Try to initiate a clean session request
+	return requestAuth(partner, me, rng, true, storage, net)
+}
+
+// requestAuth internal helper
+func requestAuth(partner, me contact.Contact, rng io.Reader, reset bool,
+	storage *storage.Session, net interfaces.NetworkManager) (id.Round, error) {
+
+	/*edge checks generation*/
 	// check that the request is being sent from the proper ID
 	if !me.ID.Cmp(storage.GetUser().ReceptionID) {
 		return 0, errors.Errorf("Authenticated channel request " +
@@ -54,98 +81,79 @@ func RequestAuth(partner, me contact.Contact, rng io.Reader,
 
 	//lookup if an ongoing request is occurring
 	rqType, sr, _, err := storage.Auth().GetRequest(partner.ID)
-
-	if err == nil {
-		if rqType == auth.Receive {
+	if err != nil && !strings.Contains(err.Error(), auth.NoRequest) {
+		return 0, errors.WithMessage(err,
+			"Cannot send a request after receiving unknown error "+
+				"on requesting contact status")
+	} else if err == nil {
+		switch rqType {
+		case auth.Receive:
+			// TODO: We've already received a request, so send a
+			//       confirmation instead?
 			return 0, errors.Errorf("Cannot send a request after " +
 				"receiving a request")
-		} else if rqType == auth.Sent {
+		case auth.Sent:
 			resend = true
-		} else {
+		default:
 			return 0, errors.Errorf("Cannot send a request after "+
-				" a stored request with unknown rqType: %d", rqType)
+				"a stored request with unknown rqType: %d",
+				rqType)
 		}
-	} else if !strings.Contains(err.Error(), auth.NoRequest) {
-		return 0, errors.WithMessage(err,
-			"Cannot send a request after receiving unknown error "+
-				"on requesting contact status")
-	}
-
-	grp := storage.E2e().GetGroup()
-
-	/*generate embedded message structures and check payload*/
-	cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen())
-	baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen())
-	ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
-	requestFmt, err := newRequestFormat(ecrFmt)
-	if err != nil {
-		return 0, errors.Errorf("failed to make request format: %+v", err)
 	}
 
-	//check the payload fits
-	facts := me.Facts.Stringify()
-	msgPayload := facts + terminator
-	msgPayloadBytes := []byte(msgPayload)
-
 	/*cryptographic generation*/
-	var newPrivKey, newPubKey *cyclic.Int
-	var sidHPrivKeyA *sidh.PrivateKey
-	var sidHPubKeyA *sidh.PublicKey
+	var dhPriv, dhPub *cyclic.Int
+	var sidhPriv *sidh.PrivateKey
+	var sidhPub *sidh.PublicKey
 
-	// in this case we have an ongoing request so we can resend the extant
-	// request
-	if resend {
-		newPrivKey = sr.GetMyPrivKey()
-		newPubKey = sr.GetMyPubKey()
-		sidHPrivKeyA = sr.GetMySIDHPrivKey()
-		sidHPubKeyA = sr.GetMySIDHPubKey()
-		//in this case it is a new request and we must generate new keys
-	} else {
-		//generate new keypair
-		newPrivKey = diffieHellman.GeneratePrivateKey(256, grp, rng)
-		newPubKey = diffieHellman.GeneratePublicKey(newPrivKey, grp)
+	// NOTE: E2E group is the group used for DH key exchange, not cMix
+	dhGrp := storage.E2e().GetGroup()
+	// origin DH Priv key is the DH Key corresponding to the public key
+	// registered with user discovery
+	originDHPrivKey := storage.E2e().GetDHPrivateKey()
 
-		sidHPrivKeyA = util.NewSIDHPrivateKey(sidh.KeyVariantSidhA)
-		sidHPubKeyA = util.NewSIDHPublicKey(sidh.KeyVariantSidhA)
-
-		if err = sidHPrivKeyA.Generate(rng); err != nil {
-			return 0, errors.WithMessagef(err, "RequestAuth: "+
-				"could not generate SIDH private key")
-		}
-		sidHPrivKeyA.GeneratePublicKey(sidHPubKeyA)
+	// If we are resending (valid sent request), reuse those keys
+	if resend {
+		dhPriv = sr.GetMyPrivKey()
+		dhPub = sr.GetMyPubKey()
+		sidhPriv = sr.GetMySIDHPrivKey()
+		sidhPub = sr.GetMySIDHPubKey()
 
+	} else {
+		dhPriv, dhPub = genDHKeys(dhGrp, rng)
+		sidhPriv, sidhPub = util.GenerateSIDHKeyPair(
+			sidh.KeyVariantSidhA, rng)
 	}
 
-	if len(msgPayloadBytes) > requestFmt.MsgPayloadLen() {
-		return 0, errors.Errorf("Combined message longer than space "+
-			"available in payload; available: %v, length: %v",
-			requestFmt.MsgPayloadLen(), len(msgPayloadBytes))
-	}
+	jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", dhPub.Bytes())
+	jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v",
+		partner.DhPubKey.Bytes())
 
-	//generate ownership proof
-	ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(),
-		partner.DhPubKey, storage.E2e().GetGroup())
+	cMixPrimeSize := storage.Cmix().GetGroup().GetP().ByteLen()
+	cMixPayloadSize := getMixPayloadSize(cMixPrimeSize)
 
-	jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", newPubKey.Bytes())
-	jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v", partner.DhPubKey.Bytes())
+	sender := storage.GetUser().ReceptionID
 
-	/*encrypt payload*/
-	requestFmt.SetID(storage.GetUser().ReceptionID)
-	requestFmt.SetMsgPayload(msgPayloadBytes)
-	ecrFmt.SetOwnership(ownership)
-	ecrFmt.SetSidHPubKey(sidHPubKeyA)
-	ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey,
-		ecrFmt.data, grp)
+	//generate ownership proof
+	ownership := cAuth.MakeOwnershipProof(originDHPrivKey, partner.DhPubKey,
+		dhGrp)
 	confirmFp := cAuth.MakeOwnershipProofFP(ownership)
+
+	// cMix fingerprint so the recipient can recognize this is a
+	// request message.
 	requestfp := cAuth.MakeRequestFingerprint(partner.DhPubKey)
 
-	/*construct message*/
-	baseFmt.SetEcrPayload(ecrPayload)
-	baseFmt.SetPubKey(newPubKey)
+	// My fact data so we can display in the interface.
+	msgPayload := []byte(me.Facts.Stringify() + terminator)
 
-	cmixMsg.SetKeyFP(requestfp)
-	cmixMsg.SetMac(mac)
-	cmixMsg.SetContents(baseFmt.Marshal())
+	// Create the request packet.
+	request, mac, err := createRequestAuth(sender, msgPayload, ownership,
+		dhPriv, dhPub, partner.DhPubKey, sidhPub,
+		dhGrp, cMixPayloadSize)
+	if err != nil {
+		return 0, err
+	}
+	contents := request.Marshal()
 
 	storage.GetEdge().Add(edge.Preimage{
 		Data:   preimage.Generate(confirmFp[:], preimage.Confirm),
@@ -153,40 +161,77 @@ func RequestAuth(partner, me contact.Contact, rng io.Reader,
 		Source: partner.ID[:],
 	}, me.ID)
 
-	jww.TRACE.Printf("RequestAuth ECRPAYLOAD: %v", baseFmt.GetEcrPayload())
+	jww.TRACE.Printf("RequestAuth ECRPAYLOAD: %v", request.GetEcrPayload())
 	jww.TRACE.Printf("RequestAuth MAC: %v", mac)
 
 	/*store state*/
-	//fixme: channel is bricked if the first store succedes but the second fails
-	//store the in progress auth
+	//fixme: channel is bricked if the first store succedes but the second
+	//       fails
+	//store the in progress auth if this is not a resend.
 	if !resend {
-		err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey,
-			newPubKey, sidHPrivKeyA, sidHPubKeyA, confirmFp)
+		err = storage.Auth().AddSent(partner.ID, partner.DhPubKey,
+			dhPriv, dhPub, sidhPriv, sidhPub, confirmFp)
 		if err != nil {
-			return 0, errors.Errorf("Failed to store auth request: %s", err)
+			return 0, errors.Errorf(
+				"Failed to store auth request: %s", err)
 		}
 	}
 
-	jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s",
-		partner.ID, cmixMsg.Digest())
+	cMixParams := params.GetDefaultCMIX()
+	rndID, err := sendAuthRequest(partner.ID, contents, mac, cMixPrimeSize,
+		requestfp, net, cMixParams)
+	return rndID, err
+}
+
+// genDHKeys is a short helper to generate a Diffie-Helman Keypair
+func genDHKeys(dhGrp *cyclic.Group, csprng io.Reader) (priv, pub *cyclic.Int) {
+	numBytes := len(dhGrp.GetPBytes())
+	newPrivKey := diffieHellman.GeneratePrivateKey(numBytes, dhGrp, csprng)
+	newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, dhGrp)
+	return newPrivKey, newPubKey
+}
 
-	/*send message*/
-	p := params.GetDefaultCMIX()
-	p.IdentityPreimage = preimage.GenerateRequest(partner.ID)
-	p.DebugTag = "auth.Request"
-	round, _, err := net.SendCMIX(cmixMsg, partner.ID, p)
+// createRequestAuth Creates the request packet, including encrypting the
+// required parts of it.
+func createRequestAuth(sender *id.ID, payload, ownership []byte, myDHPriv,
+	myDHPub, theirDHPub *cyclic.Int, mySIDHPub *sidh.PublicKey,
+	dhGrp *cyclic.Group, cMixSize int) (*baseFormat, []byte, error) {
+	/*generate embedded message structures and check payload*/
+	dhPrimeSize := dhGrp.GetP().ByteLen()
+
+	// FIXME: This base -> ecr -> request structure is a little wonky.
+	// We should refactor so that is is more direct.
+	// I recommend we move to a request object that takes:
+	//   sender, dhPub, sidhPub, ownershipProof, payload
+	// with a Marshal/Unmarshal that takes the Dh/grp needed to gen
+	// the session key and encrypt or decrypt.
+
+	// baseFmt wraps ecrFmt. ecrFmt is encrypted
+	baseFmt := newBaseFormat(cMixSize, dhPrimeSize)
+	// ecrFmt wraps requestFmt
+	ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
+	requestFmt, err := newRequestFormat(ecrFmt)
 	if err != nil {
-		// if the send fails just set it to failed, it will
-		// but automatically retried
-		return 0, errors.WithMessagef(err, "Auth Request with %s "+
-			"(msgDigest: %s) failed to transmit: %+v", partner.ID,
-			cmixMsg.Digest(), err)
+		return nil, nil, errors.Errorf("failed to make request format: %+v", err)
+	}
+
+	if len(payload) > requestFmt.MsgPayloadLen() {
+		return nil, nil, errors.Errorf(
+			"Combined message longer than space "+
+				"available in payload; available: %v, length: %v",
+			requestFmt.MsgPayloadLen(), len(payload))
 	}
 
-	em := fmt.Sprintf("Auth Request with %s (msgDigest: %s) sent"+
-		" on round %d", partner.ID, cmixMsg.Digest(), round)
-	jww.INFO.Print(em)
-	net.GetEventManager().Report(1, "Auth", "RequestSent", em)
+	/*encrypt payload*/
+	requestFmt.SetID(sender)
+	requestFmt.SetMsgPayload(payload)
+	ecrFmt.SetOwnership(ownership)
+	ecrFmt.SetSidHPubKey(mySIDHPub)
+	ecrPayload, mac := cAuth.Encrypt(myDHPriv, theirDHPub, ecrFmt.data,
+		dhGrp)
+	/*construct message*/
+	baseFmt.SetEcrPayload(ecrPayload)
+	baseFmt.SetPubKey(myDHPub)
 
-	return round, nil
+	return &baseFmt, mac, nil
 }
diff --git a/backup/backup.go b/backup/backup.go
new file mode 100644
index 0000000000000000000000000000000000000000..47bdd4cca85487d18971117d9bd27bf19f79385a
--- /dev/null
+++ b/backup/backup.go
@@ -0,0 +1,283 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package backup
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/api"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/crypto/backup"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"sync"
+)
+
+// Error messages.
+const (
+	// initializeBackup
+	errSavePassword      = "failed to save password: %+v"
+	errSaveKeySaltParams = "failed to save key, salt, and params: %+v"
+
+	// resumeBackup
+	errLoadPassword = "backup not initialized: load user password failed: %+v"
+
+	// Backup.StopBackup
+	errDeletePassword = "failed to delete password: %+v"
+	errDeleteCrypto   = "failed to delete key, salt, and parameters: %+v"
+)
+
+// Backup stores the user's key and backup callback used to encrypt and transmit
+// the backup data.
+type Backup struct {
+	// Callback that is called with the encrypted backup when triggered
+	updateBackupCb UpdateBackupFn
+
+	mux sync.RWMutex
+
+	// Client structures
+	client          *api.Client
+	store           *storage.Session
+	backupContainer *interfaces.BackupContainer
+	rng             *fastRNG.StreamGenerator
+}
+
+// UpdateBackupFn is the callback that encrypted backup data is returned on
+type UpdateBackupFn func(encryptedBackup []byte)
+
+// InitializeBackup creates a new Backup object with the callback to return
+// backups when triggered. On initialization, 32-bit key is derived from the
+// user's password via Argon2 and a 16-bit salt is generated. Both are saved to
+// storage along with the parameters used in Argon2 to be used when encrypting
+// new backups.
+// Call this to turn on backups for the first time or to replace the user's
+// password.
+func InitializeBackup(password string, updateBackupCb UpdateBackupFn,
+	c *api.Client) (*Backup, error) {
+	return initializeBackup(
+		password, updateBackupCb, c, c.GetStorage(), c.GetBackup(), c.GetRng())
+}
+
+// initializeBackup is a helper function that takes in all the fields for Backup
+// as parameters for easier testing.
+func initializeBackup(password string, updateBackupCb UpdateBackupFn,
+	c *api.Client, store *storage.Session,
+	backupContainer *interfaces.BackupContainer, rng *fastRNG.StreamGenerator) (
+	*Backup, error) {
+	b := &Backup{
+		updateBackupCb:  updateBackupCb,
+		client:          c,
+		store:           store,
+		backupContainer: backupContainer,
+		rng:             rng,
+	}
+
+	// Save password to storage
+	err := savePassword(password, b.store.GetKV())
+	if err != nil {
+		return nil, errors.Errorf(errSavePassword, err)
+	}
+
+	// Derive key and get generated salt and parameters
+	rand := b.rng.GetStream()
+	salt, err := backup.MakeSalt(rand)
+	if err != nil {
+		return nil, err
+	}
+	rand.Close()
+
+	params := backup.DefaultParams()
+	key := backup.DeriveKey(password, salt, params)
+
+	// Save key, salt, and parameters to storage
+	err = saveBackup(key, salt, params, b.store.GetKV())
+	if err != nil {
+		return nil, errors.Errorf(errSaveKeySaltParams, err)
+	}
+
+	// Setting backup trigger in client
+	b.backupContainer.SetBackup(b.TriggerBackup)
+
+	jww.INFO.Print("Initialized backup with new user key.")
+
+	return b, nil
+}
+
+// ResumeBackup resumes a backup by restoring the Backup object and registering
+// a new callback. Call this to resume backups that have already been
+// initialized. Returns an error if backups have not already been initialized.
+func ResumeBackup(updateBackupCb UpdateBackupFn, c *api.Client) (*Backup, error) {
+	return resumeBackup(
+		updateBackupCb, c, c.GetStorage(), c.GetBackup(), c.GetRng())
+}
+
+// resumeBackup is a helper function that takes in all the fields for Backup as
+// parameters for easier testing.
+func resumeBackup(updateBackupCb UpdateBackupFn, c *api.Client,
+	store *storage.Session, backupContainer *interfaces.BackupContainer,
+	rng *fastRNG.StreamGenerator) (*Backup, error) {
+	_, err := loadPassword(store.GetKV())
+	if err != nil {
+		return nil, errors.Errorf(errLoadPassword, err)
+	}
+
+	b := &Backup{
+		updateBackupCb:  updateBackupCb,
+		client:          c,
+		store:           store,
+		backupContainer: backupContainer,
+		rng:             rng,
+	}
+
+	// Setting backup trigger in client
+	b.backupContainer.SetBackup(b.TriggerBackup)
+
+	jww.INFO.Print("Resumed backup with password loaded from storage.")
+
+	return b, nil
+}
+
+// getKeySaltParams derives a key from the user's password, a generated salt,
+// and the default parameters and return all three.
+func (b *Backup) getKeySaltParams(password string) (
+	key, salt []byte, params backup.Params, err error) {
+	rand := b.rng.GetStream()
+	salt, err = backup.MakeSalt(rand)
+	if err != nil {
+		return
+	}
+	rand.Close()
+
+	params = backup.DefaultParams()
+	key = backup.DeriveKey(password, salt, params)
+
+	return
+}
+
+// TriggerBackup assembles the backup and calls it on the registered backup
+// callback. Does nothing if no encryption key or backup callback is registered.
+// The passed in reason will be printed to the log when the backup is sent. It
+// should be in the past tense. For example, if a contact is deleted, the
+// reason can be "contact deleted" and the log will show:
+//	Triggering backup: contact deleted
+func (b *Backup) TriggerBackup(reason string) {
+	b.mux.RLock()
+	defer b.mux.RUnlock()
+
+	key, salt, params, err := loadBackup(b.store.GetKV())
+	if err != nil {
+		jww.ERROR.Printf("Backup Failed: could not load key, salt, and "+
+			"parameters for encrypting backup from storage: %+v", err)
+		return
+	}
+
+	// Grab backup data
+	collatedBackup := b.assembleBackup()
+
+	// Encrypt backup data with user key
+	rand := b.rng.GetStream()
+	encryptedBackup, err := collatedBackup.Encrypt(rand, key, salt, params)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to encrypt backup: %+v", err)
+	}
+	rand.Close()
+
+	jww.INFO.Printf("Backup triggered: %s", reason)
+
+	// Send backup on callback
+	go b.updateBackupCb(encryptedBackup)
+}
+
+// StopBackup stops the backup processes and deletes the user's password, key,
+// salt, and parameters from storage.
+func (b *Backup) StopBackup() error {
+	b.mux.Lock()
+	defer b.mux.Unlock()
+	b.updateBackupCb = nil
+
+	err := deletePassword(b.store.GetKV())
+	if err != nil {
+		return errors.Errorf(errDeletePassword, err)
+	}
+
+	err = deleteBackup(b.store.GetKV())
+	if err != nil {
+		return errors.Errorf(errDeleteCrypto, err)
+	}
+
+	jww.INFO.Print("Stopped backups.")
+
+	return nil
+}
+
+// IsBackupRunning returns true if the backup has been initialized and is
+// running. Returns false if it has been stopped.
+func (b *Backup) IsBackupRunning() bool {
+	b.mux.RLock()
+	defer b.mux.RUnlock()
+	return b.updateBackupCb != nil
+}
+
+// assembleBackup gathers all the contents of the backup and stores them in a
+// backup.Backup. This backup contains:
+//  1. Cryptographic information for the transmission identity
+//  2. Cryptographic information for the reception identity
+//  3. User's UD facts (username, email, phone number)
+//  4. Contact list
+func (b *Backup) assembleBackup() backup.Backup {
+	bu := backup.Backup{
+		TransmissionIdentity:      backup.TransmissionIdentity{},
+		ReceptionIdentity:         backup.ReceptionIdentity{},
+		UserDiscoveryRegistration: backup.UserDiscoveryRegistration{},
+		Contacts:                  backup.Contacts{},
+	}
+
+	// Get user and storage user
+	u := b.store.GetUser()
+	su := b.store.User()
+
+	// Get registration timestamp
+	bu.RegistrationTimestamp = u.RegistrationTimestamp
+
+	// Get registration code; ignore the error because if there is no
+	// registration, then an empty string is returned
+	bu.RegistrationCode, _ = b.store.GetRegCode()
+
+	// Get transmission identity
+	bu.TransmissionIdentity = backup.TransmissionIdentity{
+		RSASigningPrivateKey: u.TransmissionRSA,
+		RegistrarSignature:   su.GetTransmissionRegistrationValidationSignature(),
+		Salt:                 u.TransmissionSalt,
+		ComputedID:           u.TransmissionID,
+	}
+
+	// Get reception identity
+	bu.ReceptionIdentity = backup.ReceptionIdentity{
+		RSASigningPrivateKey: u.ReceptionRSA,
+		RegistrarSignature:   su.GetReceptionRegistrationValidationSignature(),
+		Salt:                 u.ReceptionSalt,
+		ComputedID:           u.ReceptionID,
+		DHPrivateKey:         u.E2eDhPrivateKey,
+		DHPublicKey:          u.E2eDhPublicKey,
+	}
+
+	// Get facts
+	bu.UserDiscoveryRegistration.FactList = b.store.GetUd().GetFacts()
+
+	// Get contacts
+	bu.Contacts.Identities = b.store.E2e().GetPartners()
+
+	return bu
+}
diff --git a/backup/backup_test.go b/backup/backup_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fc80163e64f39fd19882fb563f7a586b1d14c2bf
--- /dev/null
+++ b/backup/backup_test.go
@@ -0,0 +1,325 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package backup
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/crypto/backup"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/crypto/csprng"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Tests that Backup.initializeBackup returns a new Backup with a copy of the
+// key and the callback.
+func Test_initializeBackup(t *testing.T) {
+	cbChan := make(chan []byte)
+	cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
+	expectedPassword := "MySuperSecurePassword"
+	b, err := initializeBackup(expectedPassword, cb, nil,
+		storage.InitTestingSession(t), &interfaces.BackupContainer{},
+		fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
+	if err != nil {
+		t.Errorf("initializeBackup returned an error: %+v", err)
+	}
+
+	// Check that the correct password is in storage
+	loadedPassword, err := loadPassword(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to load password: %+v", err)
+	}
+	if expectedPassword != loadedPassword {
+		t.Errorf("Loaded invalid key.\nexpected: %q\nreceived: %q",
+			expectedPassword, loadedPassword)
+	}
+
+	// Check that the key, salt, and params were saved to storage
+	key, salt, p, err := loadBackup(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to load key, salt, and params: %+v", err)
+	}
+	if len(key) != keyLen || bytes.Equal(key, make([]byte, keyLen)) {
+		t.Errorf("Invalid key: %v", key)
+	}
+	if len(salt) != saltLen || bytes.Equal(salt, make([]byte, saltLen)) {
+		t.Errorf("Invalid salt: %v", salt)
+	}
+	if !reflect.DeepEqual(p, backup.DefaultParams()) {
+		t.Errorf("Invalid params.\nexpected: %+v\nreceived: %+v",
+			backup.DefaultParams(), p)
+	}
+
+	encryptedBackup := []byte("encryptedBackup")
+	go b.updateBackupCb(encryptedBackup)
+
+	select {
+	case r := <-cbChan:
+		if !bytes.Equal(encryptedBackup, r) {
+			t.Errorf("Callback has unexepected data."+
+				"\nexpected: %q\nreceived: %q", encryptedBackup, r)
+		}
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback.")
+	}
+}
+
+// Initialises a new backup and then tests that Backup.resumeBackup overwrites
+// the callback but keeps the password.
+func Test_resumeBackup(t *testing.T) {
+	// Start the first backup
+	cbChan1 := make(chan []byte)
+	cb1 := func(encryptedBackup []byte) { cbChan1 <- encryptedBackup }
+	s := storage.InitTestingSession(t)
+	expectedPassword := "MySuperSecurePassword"
+	b, err := initializeBackup(expectedPassword, cb1, nil, s,
+		&interfaces.BackupContainer{},
+		fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
+	if err != nil {
+		t.Errorf("Failed to initialize new Backup: %+v", err)
+	}
+
+	// Get key and salt to compare to later
+	key1, salt1, _, err := loadBackup(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to load key, salt, and params from newly "+
+			"initialized backup: %+v", err)
+	}
+
+	// Resume the backup with a new callback
+	cbChan2 := make(chan []byte)
+	cb2 := func(encryptedBackup []byte) { cbChan2 <- encryptedBackup }
+	b2, err := resumeBackup(cb2, nil, s, &interfaces.BackupContainer{},
+		fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG))
+	if err != nil {
+		t.Errorf("resumeBackup returned an error: %+v", err)
+	}
+
+	// Check that the correct password is in storage
+	loadedPassword, err := loadPassword(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to load password: %+v", err)
+	}
+	if expectedPassword != loadedPassword {
+		t.Errorf("Loaded invalid key.\nexpected: %q\nreceived: %q",
+			expectedPassword, loadedPassword)
+	}
+
+	// Get key, salt, and parameters of resumed backup
+	key2, salt2, _, err := loadBackup(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to load key, salt, and params from resumed "+
+			"backup: %+v", err)
+	}
+
+	// Check that the loaded key and salt are the same
+	if !bytes.Equal(key1, key2) {
+		t.Errorf("New key does not match old key.\nold: %v\nnew: %v", key1, key2)
+	}
+	if !bytes.Equal(salt1, salt2) {
+		t.Errorf("New salt does not match old salt.\nold: %v\nnew: %v", salt1, salt2)
+	}
+
+	encryptedBackup := []byte("encryptedBackup")
+	go b2.updateBackupCb(encryptedBackup)
+
+	select {
+	case r := <-cbChan1:
+		t.Errorf("Callback of first Backup called: %q", r)
+	case r := <-cbChan2:
+		if !bytes.Equal(encryptedBackup, r) {
+			t.Errorf("Callback has unexepected data."+
+				"\nexpected: %q\nreceived: %q", encryptedBackup, r)
+		}
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback.")
+	}
+}
+
+// Error path: Tests that Backup.resumeBackup returns an error if no password is
+// present in storage.
+func Test_resumeBackup_NoKeyError(t *testing.T) {
+	expectedErr := strings.Split(errLoadPassword, "%")[0]
+	s := storage.InitTestingSession(t)
+	_, err := resumeBackup(nil, nil, s, &interfaces.BackupContainer{}, nil)
+	if err == nil || !strings.Contains(err.Error(), expectedErr) {
+		t.Errorf("resumeBackup did not return the expected error when no "+
+			"password is present.\nexpected: %s\nreceived: %+v", expectedErr, err)
+	}
+}
+
+// Tests that Backup.TriggerBackup triggers the callback and that the data
+// received can be decrypted.
+func TestBackup_TriggerBackup(t *testing.T) {
+	cbChan := make(chan []byte)
+	cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
+	b := newTestBackup("MySuperSecurePassword", cb, t)
+
+	// Get password
+	password, err := loadPassword(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to load password from storage: %+v", err)
+	}
+
+	collatedBackup := b.assembleBackup()
+
+	b.TriggerBackup("")
+
+	select {
+	case r := <-cbChan:
+		receivedCollatedBackup := backup.Backup{}
+		err := receivedCollatedBackup.Decrypt(password, r)
+		if err != nil {
+			t.Errorf("Failed to decrypt collated backup: %+v", err)
+		} else if !reflect.DeepEqual(collatedBackup, receivedCollatedBackup) {
+			t.Errorf("Unexpected decrypted collated backup."+
+				"\nexpected: %#v\nreceived: %#v",
+				collatedBackup, receivedCollatedBackup)
+		}
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback.")
+	}
+}
+
+// Tests that Backup.TriggerBackup does not call the callback if there is no
+// key, salt, and params in storage.
+func TestBackup_TriggerBackup_NoKey(t *testing.T) {
+	cbChan := make(chan []byte)
+	cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
+	b := newTestBackup("MySuperSecurePassword", cb, t)
+
+	err := deleteBackup(b.store.GetKV())
+	if err != nil {
+		t.Errorf("Failed to delete key, salt, and params: %+v", err)
+	}
+
+	b.TriggerBackup("")
+
+	select {
+	case r := <-cbChan:
+		t.Errorf("Callback received when it should not have been called: %q", r)
+	case <-time.After(10 * time.Millisecond):
+	}
+}
+
+// Tests that Backup.StopBackup prevents the callback from triggering and that
+// the password, key, salt, and parameters were deleted.
+func TestBackup_StopBackup(t *testing.T) {
+	cbChan := make(chan []byte)
+	cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
+	b := newTestBackup("MySuperSecurePassword", cb, t)
+
+	err := b.StopBackup()
+	if err != nil {
+		t.Errorf("StopBackup returned an error: %+v", err)
+	}
+
+	if b.updateBackupCb != nil {
+		t.Error("Callback not cleared.")
+	}
+
+	b.TriggerBackup("")
+
+	select {
+	case r := <-cbChan:
+		t.Errorf("Callback received when it should not have been called: %q", r)
+	case <-time.After(10 * time.Millisecond):
+	}
+
+	// Make sure password is deleted
+	password, err := loadPassword(b.store.GetKV())
+	if err == nil || len(password) != 0 {
+		t.Errorf("Loaded password that should be deleted: %q", password)
+	}
+
+	// Make sure key, salt, and params are deleted
+	key, salt, p, err := loadBackup(b.store.GetKV())
+	if err == nil || len(key) != 0 || len(salt) != 0 || p != (backup.Params{}) {
+		t.Errorf("Loaded key, salt, and params that should be deleted.")
+	}
+}
+
+func TestBackup_IsBackupRunning(t *testing.T) {
+	cbChan := make(chan []byte)
+	cb := func(encryptedBackup []byte) { cbChan <- encryptedBackup }
+	b := newTestBackup("MySuperSecurePassword", cb, t)
+
+	// Check that the backup is running after being initialized
+	if !b.IsBackupRunning() {
+		t.Error("Backup is not running after initialization.")
+	}
+
+	// Stop the backup
+	err := b.StopBackup()
+	if err != nil {
+		t.Errorf("Failed to stop backup: %+v", err)
+	}
+
+	// Check that the backup is stopped
+	if b.IsBackupRunning() {
+		t.Error("Backup is running after being stopped.")
+	}
+}
+
+// Tests that Backup.assembleBackup returns the backup.Backup with the expected
+// results.
+func TestBackup_assembleBackup(t *testing.T) {
+	b := newTestBackup("MySuperSecurePassword", nil, t)
+	s := b.store
+
+	expectedCollatedBackup := backup.Backup{
+		RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
+		TransmissionIdentity: backup.TransmissionIdentity{
+			RSASigningPrivateKey: s.GetUser().TransmissionRSA,
+			RegistrarSignature:   s.User().GetTransmissionRegistrationValidationSignature(),
+			Salt:                 s.GetUser().TransmissionSalt,
+			ComputedID:           s.GetUser().TransmissionID,
+		},
+		ReceptionIdentity: backup.ReceptionIdentity{
+			RSASigningPrivateKey: s.GetUser().ReceptionRSA,
+			RegistrarSignature:   s.User().GetReceptionRegistrationValidationSignature(),
+			Salt:                 s.GetUser().ReceptionSalt,
+			ComputedID:           s.GetUser().ReceptionID,
+			DHPrivateKey:         s.GetUser().E2eDhPrivateKey,
+			DHPublicKey:          s.GetUser().E2eDhPublicKey,
+		},
+		UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
+			FactList: s.GetUd().GetFacts(),
+		},
+		Contacts: backup.Contacts{Identities: s.E2e().GetPartners()},
+	}
+
+	collatedBackup := b.assembleBackup()
+
+	if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
+		t.Errorf("Collated backup does not match expected."+
+			"\nexpected: %+v\nreceived: %+v",
+			expectedCollatedBackup, collatedBackup)
+	}
+}
+
+// newTestBackup creates a new Backup for testing.
+func newTestBackup(password string, cb UpdateBackupFn, t *testing.T) *Backup {
+	b, err := initializeBackup(
+		password,
+		cb,
+		nil,
+		storage.InitTestingSession(t),
+		&interfaces.BackupContainer{},
+		fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG),
+	)
+	if err != nil {
+		t.Fatalf("Failed to initialize backup: %+v", err)
+	}
+
+	return b
+}
diff --git a/backup/keyStorage.go b/backup/keyStorage.go
new file mode 100644
index 0000000000000000000000000000000000000000..c4627396d6c4abb8be19c6d0d0d3c8d1859c57e4
--- /dev/null
+++ b/backup/keyStorage.go
@@ -0,0 +1,128 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package backup
+
+import (
+	"bytes"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/backup"
+	"gitlab.com/xx_network/primitives/netTime"
+)
+
+const (
+	passwordStorageVersion = 0
+	passwordStorageKey     = "BackupPassword"
+	cryptoStorageVersion   = 0
+	cryptoStorageKey       = "BackupCryptoInfo"
+)
+
+// Length of marshalled fields.
+const (
+	keyLen    = backup.KeyLen
+	saltLen   = backup.SaltLen
+	paramsLen = backup.ParamsLen
+)
+
+// saveBackup saves the key, salt, and params to storage.
+func saveBackup(key, salt []byte, params backup.Params, kv *versioned.KV) error {
+
+	obj := &versioned.Object{
+		Version:   cryptoStorageVersion,
+		Timestamp: netTime.Now(),
+		Data:      marshalBackup(key, salt, params),
+	}
+
+	return kv.Set(cryptoStorageKey, cryptoStorageVersion, obj)
+}
+
+// loadBackup loads the key, salt, and params from storage.
+func loadBackup(kv *versioned.KV) (key, salt []byte, params backup.Params, err error) {
+	obj, err := kv.Get(cryptoStorageKey, cryptoStorageVersion)
+	if err != nil {
+		return
+	}
+
+	return unmarshalBackup(obj.Data)
+}
+
+// deleteBackup deletes the key, salt, and params from storage.
+func deleteBackup(kv *versioned.KV) error {
+	return kv.Delete(cryptoStorageKey, cryptoStorageVersion)
+}
+
+// marshalBackup marshals the backup's key, salt, and params into a byte slice.
+func marshalBackup(key, salt []byte, params backup.Params) []byte {
+	buff := bytes.NewBuffer(nil)
+	buff.Grow(keyLen + saltLen + paramsLen)
+
+	// Write key to buffer
+	buff.Write(key)
+
+	// Write salt to buffer
+	buff.Write(salt)
+
+	// Write marshalled params to buffer
+	buff.Write(params.Marshal())
+
+	return buff.Bytes()
+}
+
+// unmarshalBackup unmarshalls the byte slice into a key, salt, and params.
+func unmarshalBackup(buf []byte) (key, salt []byte, params backup.Params, err error) {
+	buff := bytes.NewBuffer(buf)
+	// Get key
+	key = make([]byte, keyLen)
+	n, err := buff.Read(key)
+	if err != nil || n != keyLen {
+		err = errors.Errorf("reading key failed: %+v", err)
+		return
+	}
+
+	// Get salt
+	salt = make([]byte, saltLen)
+	n, err = buff.Read(salt)
+	if err != nil || n != saltLen {
+		err = errors.Errorf("reading salt failed: %+v", err)
+		return
+	}
+
+	// Get params from remaining bytes
+	err = params.Unmarshal(buff.Bytes())
+	if err != nil {
+		err = errors.Errorf("reading params failed: %+v", err)
+	}
+
+	return
+}
+
+// savePassword saves the user's backup password to storage.
+func savePassword(password string, kv *versioned.KV) error {
+	obj := &versioned.Object{
+		Version:   passwordStorageVersion,
+		Timestamp: netTime.Now(),
+		Data:      []byte(password),
+	}
+
+	return kv.Set(passwordStorageKey, passwordStorageVersion, obj)
+}
+
+// loadPassword returns the user's backup password from storage.
+func loadPassword(kv *versioned.KV) (string, error) {
+	obj, err := kv.Get(passwordStorageKey, passwordStorageVersion)
+	if err != nil {
+		return "", err
+	}
+
+	return string(obj.Data), nil
+}
+
+// deletePassword deletes the user's backup password from storage.
+func deletePassword(kv *versioned.KV) error {
+	return kv.Delete(passwordStorageKey, passwordStorageVersion)
+}
diff --git a/backup/keyStorage_test.go b/backup/keyStorage_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..939ca2c632fbf8d5ce41af81b158be0a3be7d4aa
--- /dev/null
+++ b/backup/keyStorage_test.go
@@ -0,0 +1,94 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package backup
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/netTime"
+	"testing"
+)
+
+// Tests that savePassword saves the password to storage by loading it and
+// comparing it to the original.
+func Test_savePassword(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	expectedPassword := "MySuperSecurePassword"
+
+	// Save the password
+	err := savePassword(expectedPassword, kv)
+	if err != nil {
+		t.Errorf("savePassword returned an error: %+v", err)
+	}
+
+	// Attempt to load the password
+	obj, err := kv.Get(passwordStorageKey, passwordStorageVersion)
+	if err != nil {
+		t.Errorf("Failed to get password from storage: %+v", err)
+	}
+
+	// Check that the password matches the original
+	if expectedPassword != string(obj.Data) {
+		t.Errorf("Loaded password does not match original."+
+			"\nexpected: %q\nreceived: %q", expectedPassword, obj.Data)
+	}
+}
+
+// Tests that loadPassword restores the original password saved to stage and
+// compares it to the original.
+func Test_loadPassword(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	expectedPassword := "MySuperSecurePassword"
+
+	// Save the password
+	err := kv.Set(passwordStorageKey, passwordStorageVersion, &versioned.Object{
+		Version:   passwordStorageVersion,
+		Timestamp: netTime.Now(),
+		Data:      []byte(expectedPassword),
+	})
+	if err != nil {
+		t.Errorf("Failed to save password to storage: %+v", err)
+	}
+
+	// Attempt to load the password
+	loadedPassword, err := loadPassword(kv)
+	if err != nil {
+		t.Errorf("loadPassword returned an error: %+v", err)
+	}
+
+	// Check that the password matches the original
+	if expectedPassword != loadedPassword {
+		t.Errorf("Loaded password does not match original."+
+			"\nexpected: %q\nreceived: %q", expectedPassword, loadedPassword)
+	}
+}
+
+// Tests that deletePassword deletes the password from storage by trying to recover a
+// deleted password.
+func Test_deletePassword(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	expectedPassword := "MySuperSecurePassword"
+
+	// Save the password
+	err := savePassword(expectedPassword, kv)
+	if err != nil {
+		t.Errorf("Failed to save password to storage: %+v", err)
+	}
+
+	// Delete the password
+	err = deletePassword(kv)
+	if err != nil {
+		t.Errorf("deletePassword returned an error: %+v", err)
+	}
+
+	// Attempt to load the password
+	obj, err := loadPassword(kv)
+	if err == nil || obj != "" {
+		t.Errorf("Loaded object from storage when it should be deleted: %+v", obj)
+	}
+}
diff --git a/bindings/backup.go b/bindings/backup.go
new file mode 100644
index 0000000000000000000000000000000000000000..9621fdfe69a2117a3939550490ae25fc3d5562dd
--- /dev/null
+++ b/bindings/backup.go
@@ -0,0 +1,67 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"gitlab.com/elixxir/client/backup"
+)
+
+type Backup struct {
+	b *backup.Backup
+}
+
+// UpdateBackupFunc contains a function callback that returns new backups.
+type UpdateBackupFunc interface {
+	UpdateBackup(encryptedBackup []byte)
+}
+
+// InitializeBackup starts the backup processes that returns backup updates when
+// they occur. Any time an event occurs that changes the contents of the backup,
+// such as adding or deleting a contact, the backup is triggered and an
+// encrypted backup is generated and returned on the updateBackupCb callback.
+// Call this function only when enabling backup if it has not already been
+// initialized or when the user wants to change their password.
+// To resume backup process on app recovery, use ResumeBackup.
+func InitializeBackup(
+	password string, updateBackupCb UpdateBackupFunc, c *Client) (*Backup, error) {
+	b, err := backup.InitializeBackup(
+		password, updateBackupCb.UpdateBackup, &c.api)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Backup{b}, nil
+}
+
+// ResumeBackup starts the backup processes back up with a new callback after it
+// has been initialized.
+// Call this function only when resuming a backup that has already been
+// initialized or to replace the callback.
+// To start the backup for the first time or to use a new password, use
+// InitializeBackup.
+func ResumeBackup(cb UpdateBackupFunc, c *Client) (
+	*Backup, error) {
+	b, err := backup.ResumeBackup(cb.UpdateBackup, &c.api)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Backup{b}, nil
+}
+
+// StopBackup stops the backup processes and deletes the user's password from
+// storage. To enable backups again, call InitializeBackup.
+func (b *Backup) StopBackup() error {
+	return b.b.StopBackup()
+}
+
+// IsBackupRunning returns true if the backup has been initialized and is
+// running. Returns false if it has been stopped.
+func (b *Backup) IsBackupRunning() bool {
+	return b.b.IsBackupRunning()
+}
diff --git a/bindings/client.go b/bindings/client.go
index 1996507cbc0a005ae20006763f6596fe1ca25834..e543b80428c9b6ac9a5439c0534142bd8a1d17f1 100644
--- a/bindings/client.go
+++ b/bindings/client.go
@@ -10,6 +10,7 @@ package bindings
 import (
 	"bytes"
 	"encoding/csv"
+	"encoding/json"
 	"errors"
 	"fmt"
 	jww "github.com/spf13/jwalterweatherman"
@@ -81,6 +82,22 @@ func NewPrecannedClient(precannedID int, network, storageDir string, password []
 	return nil
 }
 
+// NewClientFromBackup constructs a new Client from an encrypted backup. The backup
+// is decrypted using the backupPassphrase. On success a successful client creation,
+// the function will return a JSON encoded list of the E2E partners
+// contained in the backup.
+func NewClientFromBackup(ndfJSON, storageDir, sessionPassword,
+	backupPassphrase string, backupFileContents []byte) ([]byte, error) {
+	backupPartnerIds, err := api.NewClientFromBackup(ndfJSON, storageDir,
+		sessionPassword, backupPassphrase, backupFileContents)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to create new "+
+			"client from backup: %+v", err))
+	}
+
+	return json.Marshal(backupPartnerIds)
+}
+
 // Login will load an existing client from the storageDir
 // using the password. This will fail if the client doesn't exist or
 // the password is incorrect.
diff --git a/bindings/ud.go b/bindings/ud.go
index f9a12eb39972f94743a833e9ec4e081139b5ac40..8c467c810a452847cce7989ed9224d55167a36e1 100644
--- a/bindings/ud.go
+++ b/bindings/ud.go
@@ -102,6 +102,37 @@ func (ud *UserDiscovery) RemoveUser(fStr string) error {
 	return ud.ud.RemoveUser(f)
 }
 
+//BackUpMissingFacts adds a registered fact to the Store object and saves
+// it to storage. It can take in both an email or a phone number, passed into
+// the function in that order.  Any one of these fields may be empty,
+// however both fields being empty will cause an error. Any other fact that is not
+// an email or phone number will return an error. You may only add a fact for the
+// accepted types once each. If you attempt to back up a fact type that has already
+// been backed up, an error will be returned. Anytime an error is returned, it means
+// the backup was not successful.
+// NOTE: Do not use this as a direct store operation. This feature is intended to add facts
+// to a backend store that have ALREADY BEEN REGISTERED on the account.
+// THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
+func (ud *UserDiscovery) BackUpMissingFacts(email, phone string) error {
+	var emailFact, phoneFact fact.Fact
+	var err error
+	if len(email) > 2 {
+		emailFact, err = fact.UnstringifyFact(email)
+		if err != nil {
+			return errors.WithMessagef(err, "Failed to parse malformed email fact: %s", email)
+		}
+	}
+
+	if len(phone) > 2 {
+		phoneFact, err = fact.UnstringifyFact(phone)
+		if err != nil {
+			return errors.WithMessagef(err, "Failed to parse malformed phone fact: %s", phone)
+		}
+	}
+
+	return ud.ud.BackUpMissingFacts(emailFact, phoneFact)
+}
+
 // SearchCallback returns the result of a search
 type SearchCallback interface {
 	Callback(contacts *ContactList, error string)
diff --git a/bindings/user.go b/bindings/user.go
index 722fe6af8dd48a3c80ef102750a3110ec48ff54d..f05245daabd7a71b23468424d89d9662216574c6 100644
--- a/bindings/user.go
+++ b/bindings/user.go
@@ -52,14 +52,6 @@ func (u *User) IsPrecanned() bool {
 	return u.u.Precanned
 }
 
-func (u *User) GetCmixDhPrivateKey() []byte {
-	return u.u.CmixDhPrivateKey.Bytes()
-}
-
-func (u *User) GetCmixDhPublicKey() []byte {
-	return u.u.CmixDhPublicKey.Bytes()
-}
-
 func (u *User) GetE2EDhPrivateKey() []byte {
 	return u.u.E2eDhPrivateKey.Bytes()
 }
diff --git a/cmd/root.go b/cmd/root.go
index c9f094a1883304107ea6cbdf5cd7d0c51fc6a7a4..aa9a1c716608fc5eb87ecadd566a7dad5f592083 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -12,26 +12,30 @@ import (
 	"encoding/base64"
 	"encoding/binary"
 	"encoding/hex"
+	"encoding/json"
 	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"runtime/pprof"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
 	"github.com/spf13/cobra"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 	"gitlab.com/elixxir/client/api"
+	"gitlab.com/elixxir/client/backup"
 	"gitlab.com/elixxir/client/interfaces/message"
 	"gitlab.com/elixxir/client/interfaces/params"
 	"gitlab.com/elixxir/client/switchboard"
+	backupCrypto "gitlab.com/elixxir/crypto/backup"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/primitives/excludedRounds"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/utils"
-	"io/ioutil"
-	"log"
-	"os"
-	"runtime/pprof"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
 )
 
 // Deployment environment constants for the download-ndf code path
@@ -247,6 +251,8 @@ var rootCmd = &cobra.Command{
 				numReg, total)
 		}
 
+		client.GetBackup().TriggerBackup("Integration test.")
+
 		// Send Messages
 		msgBody := viper.GetString("message")
 
@@ -278,6 +284,12 @@ var rootCmd = &cobra.Command{
 			addPrecanAuthenticatedChannel(client,
 				recipientID, recipientContact)
 			authConfirmed = true
+		} else if !unsafe && authConfirmed && !isPrecanPartner &&
+			sendAuthReq {
+			jww.WARN.Printf("Resetting negotiated auth channel")
+			resetAuthenticatedChannel(client, recipientID,
+				recipientContact)
+			authConfirmed = false
 		}
 
 		if !unsafe && !authConfirmed {
@@ -428,7 +440,12 @@ var rootCmd = &cobra.Command{
 
 		// wait an extra 5 seconds to make sure no messages were missed
 		done = false
-		timer := time.NewTimer(5 * time.Second)
+		waitTime := time.Duration(5 * time.Second)
+		if expectedCnt == 0 {
+			// Wait longer if we didn't expect to receive anything
+			waitTime = time.Duration(15 * time.Second)
+		}
+		timer := time.NewTimer(waitTime)
 		for !done {
 			select {
 			case <-timer.C:
@@ -499,46 +516,6 @@ func initClientCallbacks(client *api.Client) (chan *id.ID,
 	return authConfirmed, recvCh
 }
 
-// Helper function which prints the round resuls
-func printRoundResults(allRoundsSucceeded, timedOut bool,
-	rounds map[id.Round]api.RoundResult, roundIDs []id.Round, msg message.Send) {
-
-	// Done as string slices for easy and human readable printing
-	successfulRounds := make([]string, 0)
-	failedRounds := make([]string, 0)
-	timedOutRounds := make([]string, 0)
-
-	for _, r := range roundIDs {
-		// Group all round reports into a category based on their
-		// result (successful, failed, or timed out)
-		if result, exists := rounds[r]; exists {
-			if result == api.Succeeded {
-				successfulRounds = append(successfulRounds, strconv.Itoa(int(r)))
-			} else if result == api.Failed {
-				failedRounds = append(failedRounds, strconv.Itoa(int(r)))
-			} else {
-				timedOutRounds = append(timedOutRounds, strconv.Itoa(int(r)))
-			}
-		}
-	}
-
-	jww.INFO.Printf("Result of sending message \"%s\" to \"%v\":",
-		msg.Payload, msg.Recipient)
-
-	// Print out all rounds results, if they are populated
-	if len(successfulRounds) > 0 {
-		jww.INFO.Printf("\tRound(s) %v successful", strings.Join(successfulRounds, ","))
-	}
-	if len(failedRounds) > 0 {
-		jww.ERROR.Printf("\tRound(s) %v failed", strings.Join(failedRounds, ","))
-	}
-	if len(timedOutRounds) > 0 {
-		jww.ERROR.Printf("\tRound(s) %v timed "+
-			"\n\tout (no network resolution could be found)", strings.Join(timedOutRounds, ","))
-	}
-
-}
-
 func createClient() *api.Client {
 	logLevel := viper.GetUint("logLevel")
 	initLog(logLevel, viper.GetString("log"))
@@ -550,6 +527,8 @@ func createClient() *api.Client {
 	precannedID := viper.GetUint("sendid")
 	userIDprefix := viper.GetString("userid-prefix")
 	protoUserPath := viper.GetString("protoUserPath")
+	backupPath := viper.GetString("backupIn")
+	backupPass := viper.GetString("backupPass")
 
 	// create a new client if none exist
 	if _, err := os.Stat(storeDir); os.IsNotExist(err) {
@@ -572,6 +551,42 @@ func createClient() *api.Client {
 		} else if userIDprefix != "" {
 			err = api.NewVanityClient(string(ndfJSON), storeDir,
 				[]byte(pass), regCode, userIDprefix)
+		} else if backupPath != "" {
+
+			b, backupFile := loadBackup(backupPath, backupPass)
+
+			// Marshal the backup object in JSON
+			backupJson, err := json.Marshal(b)
+			if err != nil {
+				jww.ERROR.Printf("Failed to JSON Marshal backup: %+v", err)
+			}
+
+			// Write the backup JSON to file
+			err = utils.WriteFileDef(viper.GetString("backupJsonOut"), backupJson)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to write backup to file: %+v", err)
+			}
+
+			// Construct client from backup data
+			backupIdList, err := api.NewClientFromBackup(string(ndfJSON), storeDir,
+				pass, backupPass, backupFile)
+
+			backupIdListPath := viper.GetString("backupIdList")
+			if backupIdListPath != "" {
+				// Marshal backed up ID list to JSON
+				backedUpIdListJson, err := json.Marshal(backupIdList)
+				if err != nil {
+					jww.ERROR.Printf("Failed to JSON Marshal backed up IDs: %+v", err)
+				}
+
+				// Write backed up ID list to file
+				err = utils.WriteFileDef(backupIdListPath, backedUpIdListJson)
+				if err != nil {
+					jww.FATAL.Panicf("Failed to write backed up IDs to file %q: %+v",
+						backupIdListPath, err)
+				}
+			}
+
 		} else {
 			err = api.NewClient(string(ndfJSON), storeDir,
 				[]byte(pass), regCode)
@@ -642,35 +657,44 @@ func initClient() *api.Client {
 
 	}
 
-	return client
-}
+	if backupOut := viper.GetString("backupOut"); backupOut != "" {
+		backupPass := viper.GetString("backupPass")
+		updateBackupCb := func(encryptedBackup []byte) {
+			jww.INFO.Printf("Backup update received, size %d", len(encryptedBackup))
+			fmt.Println("Backup update received.")
+			err = utils.WriteFileDef(backupOut, encryptedBackup)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to write backup to file: %+v", err)
+			}
 
-func writeContact(c contact.Contact) {
-	outfilePath := viper.GetString("writeContact")
-	if outfilePath == "" {
-		return
-	}
-	err := ioutil.WriteFile(outfilePath, c.Marshal(), 0644)
-	if err != nil {
-		jww.FATAL.Panicf("%+v", err)
-	}
-}
+			backupJsonPath := viper.GetString("backupJsonOut")
 
-func readContact() contact.Contact {
-	inputFilePath := viper.GetString("destfile")
-	if inputFilePath == "" {
-		return contact.Contact{}
-	}
-	data, err := ioutil.ReadFile(inputFilePath)
-	jww.INFO.Printf("Contact file size read in: %d", len(data))
-	if err != nil {
-		jww.FATAL.Panicf("Failed to read contact file: %+v", err)
-	}
-	c, err := contact.Unmarshal(data)
-	if err != nil {
-		jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err)
+			if backupJsonPath != "" {
+				var b backupCrypto.Backup
+				err = b.Decrypt(backupPass, encryptedBackup)
+				if err != nil {
+					jww.ERROR.Printf("Failed to decrypt backup: %+v", err)
+				}
+
+				backupJson, err := json.Marshal(b)
+				if err != nil {
+					jww.ERROR.Printf("Failed to JSON unmarshal backup: %+v", err)
+				}
+
+				err = utils.WriteFileDef(backupJsonPath, backupJson)
+				if err != nil {
+					jww.FATAL.Panicf("Failed to write backup to file: %+v", err)
+				}
+			}
+		}
+		_, err = backup.InitializeBackup(backupPass, updateBackupCb, client)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to initialize backup with key %q: %+v",
+				backupPass, err)
+		}
 	}
-	return c
+
+	return client
 }
 
 func acceptChannel(client *api.Client, recipientID *id.ID) {
@@ -693,14 +717,6 @@ func deleteChannel(client *api.Client, partnerId *id.ID) {
 	}
 }
 
-func printChanRequest(requestor contact.Contact) {
-	msg := fmt.Sprintf("Authentication channel request from: %s\n",
-		requestor.ID)
-	jww.INFO.Printf(msg)
-	fmt.Printf(msg)
-	// fmt.Printf(msg)
-}
-
 func addPrecanAuthenticatedChannel(client *api.Client, recipientID *id.ID,
 	recipient contact.Contact) {
 	jww.WARN.Printf("Precanned user id detected: %s", recipientID)
@@ -757,6 +773,43 @@ func addAuthenticatedChannel(client *api.Client, recipientID *id.ID,
 	}
 }
 
+func resetAuthenticatedChannel(client *api.Client, recipientID *id.ID,
+	recipient contact.Contact) {
+	var allowed bool
+	if viper.GetBool("unsafe-channel-creation") {
+		msg := "unsafe channel creation enabled\n"
+		jww.WARN.Printf(msg)
+		fmt.Printf("WARNING: %s", msg)
+		allowed = true
+	} else {
+		allowed = askToCreateChannel(recipientID)
+	}
+	if !allowed {
+		jww.FATAL.Panicf("User did not allow channel reset!")
+	}
+
+	msg := fmt.Sprintf("Resetting authenticated channel for: %s\n",
+		recipientID)
+	jww.INFO.Printf(msg)
+	fmt.Printf(msg)
+
+	recipientContact := recipient
+
+	if recipientContact.ID != nil && recipientContact.DhPubKey != nil {
+		me := client.GetUser().GetContact()
+		jww.INFO.Printf("Requesting auth channel from: %s",
+			recipientID)
+		_, err := client.ResetSession(recipientContact,
+			me, msg)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+	} else {
+		jww.ERROR.Printf("Could not reset auth channel for %s",
+			recipientID)
+	}
+}
+
 func waitUntilConnected(connected chan bool) {
 	waitTimeout := time.Duration(viper.GetUint("waitTimeout"))
 	timeoutTimer := time.NewTimer(waitTimeout * time.Second)
@@ -1117,6 +1170,28 @@ func init() {
 			"will write proto user JSON file")
 	viper.BindPFlag("protoUserOut", rootCmd.Flags().Lookup("protoUserOut"))
 
+	// Backup flags
+	rootCmd.Flags().String("backupOut", "",
+		"Path to output encrypted client backup. If no path is supplied, the "+
+			"backup system is not started.")
+	viper.BindPFlag("backupOut", rootCmd.Flags().Lookup("backupOut"))
+
+	rootCmd.Flags().String("backupJsonOut", "",
+		"Path to output unencrypted client JSON backup.")
+	viper.BindPFlag("backupJsonOut", rootCmd.Flags().Lookup("backupJsonOut"))
+
+	rootCmd.Flags().String("backupIn", "",
+		"Path to load backup client from")
+	viper.BindPFlag("backupIn", rootCmd.Flags().Lookup("backupIn"))
+
+	rootCmd.Flags().String("backupPass", "",
+		"Passphrase to encrypt/decrypt backup")
+	viper.BindPFlag("backupPass", rootCmd.Flags().Lookup("backupPass"))
+
+	rootCmd.Flags().String("backupIdList", "",
+		"JSON file containing the backed up partner IDs")
+	viper.BindPFlag("backupIdList", rootCmd.Flags().Lookup("backupIdList"))
+
 }
 
 // initConfig reads in config file and ENV variables if set.
diff --git a/cmd/ud.go b/cmd/ud.go
index cebbe7371079cbc9619aa7c74c4fc68ab419b0c7..ba6445c30b8b3e837082890ce2da7f7a173ecd0a 100644
--- a/cmd/ud.go
+++ b/cmd/ud.go
@@ -9,6 +9,7 @@
 package cmd
 
 import (
+	"encoding/json"
 	"fmt"
 	"github.com/spf13/cobra"
 	jww "github.com/spf13/jwalterweatherman"
@@ -19,6 +20,8 @@ import (
 	"gitlab.com/elixxir/client/ud"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
 	"time"
 )
 
@@ -158,6 +161,44 @@ var udCmd = &cobra.Command{
 			time.Sleep(31 * time.Second)
 		}
 
+		if viper.GetString("batchadd") != "" {
+			idListFile, err := utils.ReadFile(viper.GetString("batchadd"))
+			if err != nil {
+				fmt.Printf("BATCHADD: Couldn't read file: %s\n",
+					err.Error())
+				jww.FATAL.Panicf("BATCHADD: Couldn't read file: %+v", err)
+			}
+
+			var idList []*id.ID
+			err = json.Unmarshal(idListFile, &idList)
+			if err != nil {
+				fmt.Printf("BATCHADD: Couldn't umarshal id list: %s\n",
+					err.Error())
+				jww.FATAL.Panicf("BATCHADD: Couldn't read file: %+v", err)
+			}
+
+			jww.INFO.Printf("BATCHADD: %d IDs: %v", len(idList), idList)
+
+			cb := func(newContact contact.Contact, err error) {
+				if err != nil {
+					jww.WARN.Printf("BATCHADD: %+v", err)
+					return
+				}
+
+				jww.INFO.Printf("BATCHADD: contact %s", newContact)
+
+				addAuthenticatedChannel(client, newContact.ID, newContact)
+			}
+
+			userDiscoveryMgr.BatchLookup(idList, cb, 90*time.Second)
+
+			for _, uid := range idList {
+				for client.HasAuthenticatedChannel(uid) == false {
+					time.Sleep(time.Second)
+				}
+				jww.INFO.Printf("Authenticated channel established for %s", uid)
+			}
+		}
 		usernameSearchStr := viper.GetString("searchusername")
 		emailSearchStr := viper.GetString("searchemail")
 		phoneSearchStr := viper.GetString("searchphone")
@@ -270,6 +311,10 @@ func init() {
 		"Search for users with this email address.")
 	_ = viper.BindPFlag("searchphone", udCmd.Flags().Lookup("searchphone"))
 
+	udCmd.Flags().String("batchadd", "",
+		"Path to JSON marshalled slice of partner IDs that will be looked up on UD.")
+	_ = viper.BindPFlag("batchadd", udCmd.Flags().Lookup("batchadd"))
+
 	rootCmd.AddCommand(udCmd)
 }
 
diff --git a/cmd/utils.go b/cmd/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..9e26e2c762c343432991c5e161a57e9eb1ad0995
--- /dev/null
+++ b/cmd/utils.go
@@ -0,0 +1,114 @@
+package cmd
+
+import (
+	"fmt"
+	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/viper"
+	"gitlab.com/elixxir/client/api"
+	"gitlab.com/elixxir/client/interfaces/message"
+	backupCrypto "gitlab.com/elixxir/crypto/backup"
+	"gitlab.com/elixxir/crypto/contact"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
+	"io/ioutil"
+	"strconv"
+	"strings"
+)
+
+// todo: go through cmd package and organize utility functions
+
+func loadBackup(backupPath, backupPass string) (backupCrypto.Backup, []byte) {
+	jww.INFO.Printf("Loading backup from path %q with password %q", backupPath, backupPass)
+	backupFile, err := utils.ReadFile(backupPath)
+	if err != nil {
+		jww.FATAL.Panicf("%v", err)
+	}
+
+	var b backupCrypto.Backup
+	err = b.Decrypt(backupPass, backupFile)
+	if err != nil {
+		jww.ERROR.Printf("Failed to decrypt backup: %+v", err)
+	}
+
+	return b, backupFile
+}
+
+/////////////////////////////////////////////////////////////////
+////////////////// Print functions /////////////////////////////
+/////////////////////////////////////////////////////////////////
+
+func printChanRequest(requestor contact.Contact) {
+	msg := fmt.Sprintf("Authentication channel request from: %s\n",
+		requestor.ID)
+	jww.INFO.Printf(msg)
+	fmt.Printf(msg)
+	// fmt.Printf(msg)
+}
+
+// Helper function which prints the round resuls
+func printRoundResults(allRoundsSucceeded, timedOut bool,
+	rounds map[id.Round]api.RoundResult, roundIDs []id.Round, msg message.Send) {
+
+	// Done as string slices for easy and human readable printing
+	successfulRounds := make([]string, 0)
+	failedRounds := make([]string, 0)
+	timedOutRounds := make([]string, 0)
+
+	for _, r := range roundIDs {
+		// Group all round reports into a category based on their
+		// result (successful, failed, or timed out)
+		if result, exists := rounds[r]; exists {
+			if result == api.Succeeded {
+				successfulRounds = append(successfulRounds, strconv.Itoa(int(r)))
+			} else if result == api.Failed {
+				failedRounds = append(failedRounds, strconv.Itoa(int(r)))
+			} else {
+				timedOutRounds = append(timedOutRounds, strconv.Itoa(int(r)))
+			}
+		}
+	}
+
+	jww.INFO.Printf("Result of sending message \"%s\" to \"%v\":",
+		msg.Payload, msg.Recipient)
+
+	// Print out all rounds results, if they are populated
+	if len(successfulRounds) > 0 {
+		jww.INFO.Printf("\tRound(s) %v successful", strings.Join(successfulRounds, ","))
+	}
+	if len(failedRounds) > 0 {
+		jww.ERROR.Printf("\tRound(s) %v failed", strings.Join(failedRounds, ","))
+	}
+	if len(timedOutRounds) > 0 {
+		jww.ERROR.Printf("\tRound(s) %v timed "+
+			"\n\tout (no network resolution could be found)", strings.Join(timedOutRounds, ","))
+	}
+
+}
+
+func writeContact(c contact.Contact) {
+	outfilePath := viper.GetString("writeContact")
+	if outfilePath == "" {
+		return
+	}
+	err := ioutil.WriteFile(outfilePath, c.Marshal(), 0644)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+}
+
+func readContact() contact.Contact {
+	inputFilePath := viper.GetString("destfile")
+	if inputFilePath == "" {
+		return contact.Contact{}
+	}
+	data, err := ioutil.ReadFile(inputFilePath)
+	jww.INFO.Printf("Contact file size read in: %d", len(data))
+	if err != nil {
+		jww.FATAL.Panicf("Failed to read contact file: %+v", err)
+	}
+	c, err := contact.Unmarshal(data)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err)
+	}
+	return c
+}
diff --git a/go.mod b/go.mod
index 4a0f6367b73509140ecc2885b5e5b7bd87396bd4..84912eac5c80f77824defbc906025bd1f23584e3 100644
--- a/go.mod
+++ b/go.mod
@@ -12,8 +12,8 @@ require (
 	github.com/spf13/jwalterweatherman v1.1.0
 	github.com/spf13/viper v1.7.1
 	gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228
-	gitlab.com/elixxir/comms v0.0.4-0.20220222212253-41a1a0067369
-	gitlab.com/elixxir/crypto v0.0.7-0.20220222212142-d3303373ee78
+	gitlab.com/elixxir/comms v0.0.4-0.20220214214811-4a1bd320aa45
+	gitlab.com/elixxir/crypto v0.0.7-0.20220214184248-2b6499b3c024
 	gitlab.com/elixxir/ekv v0.1.6
 	gitlab.com/elixxir/primitives v0.0.3-0.20220222212109-d412a6e46623
 	gitlab.com/xx_network/comms v0.0.4-0.20220222212058-5a37737af57e
diff --git a/go.sum b/go.sum
index 5cecc075ac7209f3664ffc22c85e679008a5edfb..e3960a188284efb90a562f1b079b3b7e96cce39d 100644
--- a/go.sum
+++ b/go.sum
@@ -276,8 +276,11 @@ gitlab.com/elixxir/comms v0.0.4-0.20220222212253-41a1a0067369 h1:Bk5T3unbs3cjEqz
 gitlab.com/elixxir/comms v0.0.4-0.20220222212253-41a1a0067369/go.mod h1:AligJKSltFDPe/rqE2EZBfWCMSrae0zUo7scsXoyMPE=
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
 gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
-gitlab.com/elixxir/crypto v0.0.7-0.20220222212142-d3303373ee78 h1:MvZ0UwyhCSuNUcmHT905oadu7XYT2WSz+QD3Rjcgg00=
-gitlab.com/elixxir/crypto v0.0.7-0.20220222212142-d3303373ee78/go.mod h1:bPD4FmnnaDFLxn+d4YDWZhVnevWXArKwOMMza4MU5uQ=
+gitlab.com/elixxir/crypto v0.0.7-0.20220110170041-7e42f2e8b062/go.mod h1:qmW0OGPB21GcaGg1Jvt527/qUw7ke6W8DKCiYBfsx48=
+gitlab.com/elixxir/crypto v0.0.7-0.20220211185439-4a6d9f41f8ab h1:UKtFrw8qyLQhwZoftn2926Cm02ZL5HQitTLGF4sQ+ys=
+gitlab.com/elixxir/crypto v0.0.7-0.20220211185439-4a6d9f41f8ab/go.mod h1:WyLFCxOOgaCHElpH0Ha893tfjxg3HXYU7lSJz2M4JUE=
+gitlab.com/elixxir/crypto v0.0.7-0.20220214184248-2b6499b3c024 h1:uF6X1josZiOWt6v4Ym2b4IHN1v+UF7xbSxebiV3NFlk=
+gitlab.com/elixxir/crypto v0.0.7-0.20220214184248-2b6499b3c024/go.mod h1:WyLFCxOOgaCHElpH0Ha893tfjxg3HXYU7lSJz2M4JUE=
 gitlab.com/elixxir/ekv v0.1.6 h1:M2hUSNhH/ChxDd+s8xBqSEKgoPtmE6hOEBqQ73KbN6A=
 gitlab.com/elixxir/ekv v0.1.6/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
diff --git a/interfaces/auth.go b/interfaces/auth.go
index 4ce22fba789b45a5616a640f0409a054f6a05ecb..28443cf800727ad20cd9ff9586dcb5945e0a87e5 100644
--- a/interfaces/auth.go
+++ b/interfaces/auth.go
@@ -14,6 +14,7 @@ import (
 
 type RequestCallback func(requestor contact.Contact)
 type ConfirmCallback func(partner contact.Contact)
+type ResetCallback func(partner contact.Contact)
 
 type Auth interface {
 	// Adds a general callback to be used on auth requests. This will be preempted
@@ -42,6 +43,8 @@ type Auth interface {
 	AddSpecificConfirmCallback(id *id.ID, cb ConfirmCallback)
 	// Removes a specific callback to be used on auth confirm.
 	RemoveSpecificConfirmCallback(id *id.ID)
+	// Add a callback to receive session renegotiations
+	AddResetCallback(cb ResetCallback)
 	//Replays all pending received requests over tha callbacks
 	ReplayRequests()
 }
diff --git a/interfaces/backup.go b/interfaces/backup.go
new file mode 100644
index 0000000000000000000000000000000000000000..559b4b0f8756ee772aba9064757601d2ade417d6
--- /dev/null
+++ b/interfaces/backup.go
@@ -0,0 +1,40 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package interfaces
+
+import "sync"
+
+type TriggerBackup func(reason string)
+
+// BackupContainer contains the trigger to call to initiate a backup.
+type BackupContainer struct {
+	triggerBackup TriggerBackup
+	mux           sync.RWMutex
+}
+
+// TriggerBackup triggers a backup if a backup trigger has been set.
+// The passed in reason will be printed to the log when the backup is sent. It
+// should be in the paste tense. For example, if a contact is deleted, the
+// reason can be "contact deleted" and the log will show:
+//	Triggering backup: contact deleted
+func (bc *BackupContainer) TriggerBackup(reason string) {
+	bc.mux.RLock()
+	defer bc.mux.RUnlock()
+	if bc.triggerBackup != nil {
+		bc.triggerBackup(reason)
+	}
+}
+
+// SetBackup sets the backup trigger function which will cause a backup to start
+// on the next event that triggers is.
+func (bc *BackupContainer) SetBackup(triggerBackup TriggerBackup) {
+	bc.mux.Lock()
+	defer bc.mux.Unlock()
+
+	bc.triggerBackup = triggerBackup
+}
diff --git a/interfaces/user/proto.go b/interfaces/user/proto.go
index 6b9cf1e3a6b338a08abd386b92772314b5f499e3..657c2e714d70f06b88c6d0866ec2bbca5015f24e 100644
--- a/interfaces/user/proto.go
+++ b/interfaces/user/proto.go
@@ -23,10 +23,6 @@ type Proto struct {
 	TransmissionRegValidationSig []byte
 	ReceptionRegValidationSig    []byte
 
-	//cmix Identity
-	CmixDhPrivateKey *cyclic.Int
-	CmixDhPublicKey  *cyclic.Int
-
 	//e2e Identity
 	E2eDhPrivateKey *cyclic.Int
 	E2eDhPublicKey  *cyclic.Int
diff --git a/interfaces/user/user.go b/interfaces/user/user.go
index 56f260d03527ce2c605c5fe781f3be3935cc106c..5dc559917a7622710f356936c3b3f2f1985651f2 100644
--- a/interfaces/user/user.go
+++ b/interfaces/user/user.go
@@ -8,6 +8,7 @@
 package user
 
 import (
+	"gitlab.com/elixxir/crypto/backup"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/primitives/fact"
@@ -27,10 +28,6 @@ type User struct {
 	// Timestamp in which user has registered with the network
 	RegistrationTimestamp int64
 
-	//cmix Identity
-	CmixDhPrivateKey *cyclic.Int
-	CmixDhPublicKey  *cyclic.Int
-
 	//e2e Identity
 	E2eDhPrivateKey *cyclic.Int
 	E2eDhPublicKey  *cyclic.Int
@@ -54,9 +51,22 @@ func NewUserFromProto(proto *Proto) User {
 		ReceptionRSA:          proto.ReceptionRSA,
 		Precanned:             proto.Precanned,
 		RegistrationTimestamp: proto.RegistrationTimestamp,
-		CmixDhPrivateKey:      proto.CmixDhPrivateKey,
-		CmixDhPublicKey:       proto.CmixDhPublicKey,
 		E2eDhPrivateKey:       proto.E2eDhPrivateKey,
 		E2eDhPublicKey:        proto.E2eDhPublicKey,
 	}
 }
+
+func NewUserFromBackup(backup *backup.Backup) User {
+	return User{
+		TransmissionID:        backup.TransmissionIdentity.ComputedID,
+		TransmissionSalt:      backup.TransmissionIdentity.Salt,
+		TransmissionRSA:       backup.TransmissionIdentity.RSASigningPrivateKey,
+		ReceptionID:           backup.ReceptionIdentity.ComputedID,
+		ReceptionSalt:         backup.ReceptionIdentity.Salt,
+		ReceptionRSA:          backup.ReceptionIdentity.RSASigningPrivateKey,
+		Precanned:             false,
+		RegistrationTimestamp: backup.RegistrationTimestamp,
+		E2eDhPrivateKey:       backup.ReceptionIdentity.DHPrivateKey,
+		E2eDhPublicKey:        backup.ReceptionIdentity.DHPublicKey,
+	}
+}
diff --git a/network/node/register.go b/network/node/register.go
index 4336575dee390d0b948b914595d9bb15cfafd5bd..1576fe89a027cbba1a29ef374a84c809848e0d57 100644
--- a/network/node/register.go
+++ b/network/node/register.go
@@ -21,6 +21,7 @@ import (
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/comms/network"
 	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/crypto/hash"
 	"gitlab.com/elixxir/crypto/registration"
@@ -154,7 +155,20 @@ func requestKey(sender *gateway.Sender, comms RegisterNodeCommsInterface,
 	uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source,
 	stop *stoppable.Single) (*cyclic.Int, []byte, uint64, error) {
 
-	dhPub := store.GetDHPublicKey().Bytes()
+
+	grp := store.GetGroup()
+
+	// FIXME: Why 256 bits? -- this is spec but not explained, it has
+	// to do with optimizing operations on one side and still preserves
+	// decent security -- cite this.
+	dhPrivBytes, err := csprng.GenerateInGroup(store.GetGroup().GetPBytes(), 256, rng)
+	if err != nil {
+		return nil, nil, 0, err
+	}
+
+	dhPriv := grp.NewIntFromBytes(dhPrivBytes)
+
+	dhPub := diffieHellman.GeneratePublicKey(dhPriv, grp)
 
 	// Reconstruct client confirmation message
 	userPubKeyRSA := rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic())
@@ -170,7 +184,7 @@ func requestKey(sender *gateway.Sender, comms RegisterNodeCommsInterface,
 			RegistrarSignature:             &messages.RSASignature{Signature: regSig},
 			ClientRegistrationConfirmation: confirmationSerialized,
 		},
-		ClientDHPubKey:        dhPub,
+		ClientDHPubKey:        dhPub.Bytes(),
 		RegistrationTimestamp: registrationTimestampNano,
 		RequestTimestamp:      netTime.Now().UnixNano(),
 	}
@@ -262,12 +276,11 @@ func requestKey(sender *gateway.Sender, comms RegisterNodeCommsInterface,
 	h.Reset()
 
 	// Convert Node DH Public key to a cyclic.Int
-	grp := store.GetGroup()
 	nodeDHPub := grp.NewIntFromBytes(keyResponse.NodeDHPubKey)
 
 	// Construct the session key
 	sessionKey := registration.GenerateBaseKey(grp,
-		nodeDHPub, store.GetDHPrivateKey(), h)
+		nodeDHPub, dhPriv, h)
 
 	// Verify the HMAC
 	h.Reset()
diff --git a/storage/auth/confirmation.go b/storage/auth/confirmation.go
new file mode 100644
index 0000000000000000000000000000000000000000..b55c79433db9d7caf51d303caee06e00665df011
--- /dev/null
+++ b/storage/auth/confirmation.go
@@ -0,0 +1,61 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"encoding/base64"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
+)
+
+const (
+	confirmationKeyPrefix      = "Confirmation/"
+	currentConfirmationVersion = 0
+)
+
+// StoreConfirmation saves the confirmation to storage for the given partner and
+// fingerprint.
+func (s *Store) StoreConfirmation(
+	partner *id.ID, fingerprint, confirmation []byte) error {
+	obj := &versioned.Object{
+		Version:   currentConfirmationVersion,
+		Timestamp: netTime.Now(),
+		Data:      confirmation,
+	}
+
+	return s.kv.Set(makeConfirmationKey(partner, fingerprint),
+		currentConfirmationVersion, obj)
+}
+
+// LoadConfirmation loads the confirmation for the given partner and fingerprint
+// from storage.
+func (s *Store) LoadConfirmation(partner *id.ID, fingerprint []byte) (
+	[]byte, error) {
+	obj, err := s.kv.Get(
+		makeConfirmationKey(partner, fingerprint), currentConfirmationVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	return obj.Data, nil
+}
+
+// deleteConfirmation deletes the confirmation for the given partner and
+// fingerprint from storage.
+func (s *Store) deleteConfirmation(partner *id.ID, fingerprint []byte) error {
+	return s.kv.Delete(
+		makeConfirmationKey(partner, fingerprint), currentConfirmationVersion)
+}
+
+// makeConfirmationKey generates the key used to load and store confirmations
+// for the partner and fingerprint.
+func makeConfirmationKey(partner *id.ID, fingerprint []byte) string {
+	return confirmationKeyPrefix + partner.String() + "/" +
+		base64.StdEncoding.EncodeToString(fingerprint)
+}
diff --git a/storage/auth/confirmation_test.go b/storage/auth/confirmation_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..94f4dad1b27d227e6742da07917f75b1dc79253e
--- /dev/null
+++ b/storage/auth/confirmation_test.go
@@ -0,0 +1,174 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"github.com/cloudflare/circl/dh/sidh"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// Tests that a confirmation for different partners and fingerprints can be
+// saved and loaded from storage via Store.StoreConfirmation and
+// Store.LoadConfirmation.
+func TestStore_StoreConfirmation_LoadConfirmation(t *testing.T) {
+	s := &Store{kv: versioned.NewKV(make(ekv.Memstore))}
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+
+	testValues := make([]struct {
+		partner                   *id.ID
+		fingerprint, confirmation []byte
+	}, 10)
+
+	partner, _ := id.NewRandomID(prng, id.User)
+	for i := range testValues {
+		if i%2 == 0 {
+			partner, _ = id.NewRandomID(prng, id.User)
+		}
+
+		// Generate original fingerprint
+		var fp []byte
+		if i%2 == 1 {
+			dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+			_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, prng)
+			fp = auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+		}
+
+		// Generate confirmation
+		confirmation := make([]byte, 32)
+		prng.Read(confirmation)
+
+		testValues[i] = struct {
+			partner                   *id.ID
+			fingerprint, confirmation []byte
+		}{partner: partner, fingerprint: fp, confirmation: confirmation}
+
+		err := s.StoreConfirmation(partner, fp, confirmation)
+		if err != nil {
+			t.Errorf("StoreConfirmation returned an error (%d): %+v", i, err)
+		}
+	}
+
+	for i, val := range testValues {
+		loadedConfirmation, err := s.LoadConfirmation(val.partner, val.fingerprint)
+		if err != nil {
+			t.Errorf("LoadConfirmation returned an error (%d): %+v", i, err)
+		}
+
+		if !reflect.DeepEqual(val.confirmation, loadedConfirmation) {
+			t.Errorf("Loaded confirmation does not match original (%d)."+
+				"\nexpected: %v\nreceived: %v", i, val.confirmation,
+				loadedConfirmation)
+		}
+	}
+}
+
+// Tests that Store.deleteConfirmation deletes the correct confirmation from
+// storage and that it cannot be loaded from storage.
+func TestStore_deleteConfirmation(t *testing.T) {
+	s := &Store{kv: versioned.NewKV(make(ekv.Memstore))}
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+
+	testValues := make([]struct {
+		partner                   *id.ID
+		fingerprint, confirmation []byte
+	}, 10)
+
+	partner, _ := id.NewRandomID(prng, id.User)
+	for i := range testValues {
+		if i%2 == 0 {
+			partner, _ = id.NewRandomID(prng, id.User)
+		}
+
+		// Generate original fingerprint
+		var fp []byte
+		if i%2 == 1 {
+			dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+			_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, prng)
+			fp = auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+		}
+
+		// Generate confirmation
+		confirmation := make([]byte, 32)
+		prng.Read(confirmation)
+
+		testValues[i] = struct {
+			partner                   *id.ID
+			fingerprint, confirmation []byte
+		}{partner: partner, fingerprint: fp, confirmation: confirmation}
+
+		err := s.StoreConfirmation(partner, fp, confirmation)
+		if err != nil {
+			t.Errorf("StoreConfirmation returned an error (%d): %+v", i, err)
+		}
+	}
+
+	for i, val := range testValues {
+		err := s.deleteConfirmation(val.partner, val.fingerprint)
+		if err != nil {
+			t.Errorf("deleteConfirmation returned an error (%d): %+v", i, err)
+		}
+
+		loadedConfirmation, err := s.LoadConfirmation(val.partner, val.fingerprint)
+		if err == nil || loadedConfirmation != nil {
+			t.Errorf("LoadConfirmation returned a confirmation for partner "+
+				"%s and fingerprint %v (%d)", val.partner, val.fingerprint, i)
+		}
+	}
+}
+
+// Consistency test of makeConfirmationKey.
+func Test_makeConfirmationKey_Consistency(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	expectedKeys := []string{
+		"Confirmation/U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID/VzgXG/mlQA68iq1eCgEMoew1rnuVG6mA2x2U34PYiOs=",
+		"Confirmation/P2HTdbwCtB30+Rkp4Y/anm+C5U50joHnnku9b+NM3LoD/DT1RkZJUbdDqNLQv+Pp+Ilx7ZvOX5zBzl8gseeRLu1w=",
+		"Confirmation/r66IG4KnURCKQu08kDyqQ0ZaeGIGFpeK7QzjxsTzrnsD/BVkxRTRPx5+16fRHsq5bYkpZDJyVJaon0roLGsOBSmI=",
+		"Confirmation/otwtwlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGFJiUcD/jKSgdUKni0rsIDDutHlO1fiss+BiNd1vxSGxJL0u2e8=",
+		"Confirmation/lk39x56NU0NzZhz9ZtdP7B4biUkatyNuS3UhYpDPK+sD/prNQTXAQjkTRhltOQuhU8XagwwWP0RfwJe6yrtI3aaY=",
+		"Confirmation/l4KD1KCaNvlsIJQXRuPtTaZGqa6LT6e0/Doguvoade0D/D+xEPt5A44s0BD5u/fz1iiPFoCnOR52PefTFOehdkbU=",
+		"Confirmation/HPCdo54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vcD/cPDqZ3S1mqVxRTQ1p7Gwg7cEc34Xz/fUsIpghGiJygg=",
+		"Confirmation/Ud9mj4dOLJ8c4JyoYBfn4hdIMD/0HBsj4RxI7RdTnWgD/minVwOqyN3l4zy7A4dvJDQ5ZLUcM2NmNdAWhR5/NTDc=",
+		"Confirmation/Ximg3KRqw6DVcBM7whVx9fVKZDEFUT/YQpsZSuG6nyoD/dK0ZnuwEmyeXqjQj5mri5f8ChTHOVgTgUKkOGjUfPyQ=",
+		"Confirmation/ZxkHLWcvYfqgvob0V5Iew3wORgzw1wPQfcX1ZhpFATMD/r0Nylw9Bd+eol1+4UWwWD8SBchPbjtnLYJx1zX1htEo=",
+		"Confirmation/IpwYPBkzqRZYXhg7twkZLbDmyNcJudc4O5k8aUmZRbAD/eszeUU8yAglf5TrE5U4L8SVqKOPqypt9RbVjworRBbk=",
+		"Confirmation/Rc0b8Lz8GjRsQ08RzwBBb6YWlbkgLmg2Ohx4f0eE4K4D/jhddD9Kqk6rcSJAB/Jy88cwhozR43M1nL+VTyl34SEk=",
+		"Confirmation/1ieMn3yHL4QPnZTZ/e2uk9sklXGPWAuMjyvsxqp2w7AD/aaMF2inM08M9FdFOHPfGKMnoqqEJ4MiXxDhY2J84cE8=",
+		"Confirmation/FER0v9N80ga1Gs4FCrYZnsezltYY/eDhopmabz2fi3oD/TJ5e0/2ji9eZSYa78RIP2ZvDW/PxP685D3xZAqHkGHY=",
+		"Confirmation/KRnCqHpJlPweQB4RxaScfo6p5l1sxARl/TUvLELsPT4D/mlbwi77z/XUw/LfzX8L67k0/0dAIDHAYicLd2RukYO0=",
+		"Confirmation/Q9EGMwNtPUa4GRauRv8T1qay+tkHnW3zRAWQKWZ7LrQD/0J3tuOL9xxfZdFQ73YEktXkeoFY6sAJIcgzlyDl3BxQ=",
+	}
+
+	for i, expected := range expectedKeys {
+		partner, _ := id.NewRandomID(prng, id.User)
+		dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+		_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, prng)
+		fp := auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+
+		key := makeConfirmationKey(partner, fp)
+		if expected != key {
+			t.Errorf("Confirmation key does not match expected for partner "+
+				"%s and fingerprint %v (%d).\nexpected: %q\nreceived: %q",
+				partner, fp, i, expected, key)
+		}
+
+		// fmt.Printf("\"%s\",\n", key)
+	}
+}
diff --git a/storage/auth/previousNegotiations.go b/storage/auth/previousNegotiations.go
new file mode 100644
index 0000000000000000000000000000000000000000..6a538f51825098f8602a741ea2617825eab4245a
--- /dev/null
+++ b/storage/auth/previousNegotiations.go
@@ -0,0 +1,276 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"bytes"
+	"encoding/binary"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
+)
+
+const (
+	negotiationPartnersKey                = "NegotiationPartners"
+	negotiationPartnersVersion            = 0
+	negotiationFingerprintsKeyPrefix      = "NegotiationFingerprints/"
+	currentNegotiationFingerprintsVersion = 0
+)
+
+// AddIfNew adds a new negotiation fingerprint if it is new.
+// If the partner does not exist, it will add it and the new fingerprint and
+// return newFingerprint = true, latest = true.
+// If the partner exists and the fingerprint does not exist, add it adds it as
+// the latest fingerprint and returns newFingerprint = true, latest = true
+// If the partner exists and the fingerprint exists, return
+// newFingerprint = false, latest = false or latest = true if it is the last one
+// in the list.
+func (s *Store) AddIfNew(partner *id.ID, negotiationFingerprint []byte) (
+	newFingerprint, latest bool) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	// If the partner does not exist, add it to the list and store a new
+	// fingerprint to storage
+	_, exists := s.previousNegotiations[*partner]
+	if !exists {
+		s.previousNegotiations[*partner] = struct{}{}
+
+		// Save fingerprint to storage
+		err := s.saveNegotiationFingerprints(partner, negotiationFingerprint)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to save negotiation fingerprints for "+
+				"partner %s: %+v", partner, err)
+		}
+
+		// Save partner list to storage
+		err = s.savePreviousNegotiations()
+		if err != nil {
+			jww.FATAL.Panicf(
+				"Failed to save negotiation partners %s: %+v", partner, err)
+		}
+
+		newFingerprint = true
+		latest = true
+
+		return
+	}
+
+	// Get the fingerprint list from storage
+	fingerprints, err := s.loadNegotiationFingerprints(partner)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to load negotiation fingerprints for "+
+			"partner %s: %+v", partner, err)
+	}
+
+	// If the partner does exist and the fingerprint exists, then make no
+	// changes to the list
+	for i, fp := range fingerprints {
+		if bytes.Equal(fp, negotiationFingerprint) {
+			newFingerprint = false
+
+			// Latest = true if it is the last fingerprint in the list
+			latest = i == len(fingerprints)-1
+
+			return
+		}
+	}
+
+	// If the partner does exist and the fingerprint does not exist, then add
+	// the fingerprint to the list as latest
+	fingerprints = append(fingerprints, negotiationFingerprint)
+	err = s.saveNegotiationFingerprints(partner, fingerprints...)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to save negotiation fingerprints for "+
+			"partner %s: %+v", partner, err)
+	}
+
+	newFingerprint = true
+	latest = true
+
+	return
+}
+
+// deletePreviousNegotiationPartner removes the partner, its fingerprints, and
+// its confirmations from memory and storage.
+func (s *Store) deletePreviousNegotiationPartner(partner *id.ID) error {
+
+	// Do nothing if the partner does not exist
+	if _, exists := s.previousNegotiations[*partner]; !exists {
+		return nil
+	}
+
+	// Delete partner from memory
+	delete(s.previousNegotiations, *partner)
+
+	// Delete partner from storage and return an error
+	err := s.savePreviousNegotiations()
+	if err != nil {
+		return err
+	}
+
+	// Check if fingerprints exist
+	fingerprints, err := s.loadNegotiationFingerprints(partner)
+
+	// If fingerprints exist for this partner, delete them from storage and any
+	// accompanying confirmations
+	if err == nil {
+		// Delete the fingerprint list from storage but do not return the error
+		// until after attempting to delete the confirmations
+		err = s.kv.Delete(makeNegotiationFingerprintsKey(partner),
+			currentNegotiationFingerprintsVersion)
+
+		// Delete all confirmations from storage
+		for _, fp := range fingerprints {
+			// Ignore the error since confirmations rarely exist
+			_ = s.deleteConfirmation(partner, fp)
+		}
+	}
+
+	// Return any error from loading or deleting fingerprints
+	return err
+}
+
+// savePreviousNegotiations saves the list of previousNegotiations partners to
+// storage.
+func (s *Store) savePreviousNegotiations() error {
+	obj := &versioned.Object{
+		Version:   negotiationPartnersVersion,
+		Timestamp: netTime.Now(),
+		Data:      marshalPreviousNegotiations(s.previousNegotiations),
+	}
+
+	return s.kv.Set(negotiationPartnersKey, negotiationPartnersVersion, obj)
+}
+
+// loadPreviousNegotiations loads the list of previousNegotiations partners from
+// storage.
+func (s *Store) loadPreviousNegotiations() (map[id.ID]struct{}, error) {
+	obj, err := s.kv.Get(negotiationPartnersKey, negotiationPartnersVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	return unmarshalPreviousNegotiations(obj.Data), nil
+}
+
+// marshalPreviousNegotiations marshals the list of partners into a byte slice.
+func marshalPreviousNegotiations(partners map[id.ID]struct{}) []byte {
+	buff := bytes.NewBuffer(nil)
+	buff.Grow(8 + (len(partners) * id.ArrIDLen))
+
+	// Write number of partners to buffer
+	b := make([]byte, 8)
+	binary.LittleEndian.PutUint64(b, uint64(len(partners)))
+	buff.Write(b)
+
+	// Write each partner ID to buffer
+	for partner := range partners {
+		buff.Write(partner.Marshal())
+	}
+
+	return buff.Bytes()
+}
+
+// unmarshalPreviousNegotiations unmarshalls the marshalled byte slice into a
+// list of partner IDs.
+func unmarshalPreviousNegotiations(buf []byte) map[id.ID]struct{} {
+	buff := bytes.NewBuffer(buf)
+
+	numberOfPartners := binary.LittleEndian.Uint64(buff.Next(8))
+	partners := make(map[id.ID]struct{}, numberOfPartners)
+
+	for i := uint64(0); i < numberOfPartners; i++ {
+		partner, err := id.Unmarshal(buff.Next(id.ArrIDLen))
+		if err != nil {
+			jww.FATAL.Panicf(
+				"Failed to unmarshal negotiation partner ID: %+v", err)
+		}
+
+		partners[*partner] = struct{}{}
+	}
+
+	return partners
+}
+
+// saveNegotiationFingerprints saves the list of fingerprints for the given
+// partner to storage.
+func (s *Store) saveNegotiationFingerprints(
+	partner *id.ID, fingerprints ...[]byte) error {
+
+	obj := &versioned.Object{
+		Version:   currentNegotiationFingerprintsVersion,
+		Timestamp: netTime.Now(),
+		Data:      marshalNegotiationFingerprints(fingerprints...),
+	}
+
+	return s.kv.Set(makeNegotiationFingerprintsKey(partner),
+		currentNegotiationFingerprintsVersion, obj)
+}
+
+// loadNegotiationFingerprints loads the list of fingerprints for the given
+// partner from storage.
+func (s *Store) loadNegotiationFingerprints(partner *id.ID) ([][]byte, error) {
+	obj, err := s.kv.Get(makeNegotiationFingerprintsKey(partner),
+		currentNegotiationFingerprintsVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	return unmarshalNegotiationFingerprints(obj.Data), nil
+}
+
+// marshalNegotiationFingerprints marshals the list of fingerprints into a byte
+// slice for storage.
+func marshalNegotiationFingerprints(fingerprints ...[]byte) []byte {
+	buff := bytes.NewBuffer(nil)
+	buff.Grow(8 + (len(fingerprints) * auth.NegotiationFingerprintLen))
+
+	// Write number of fingerprints to buffer
+	b := make([]byte, 8)
+	binary.LittleEndian.PutUint64(b, uint64(len(fingerprints)))
+	buff.Write(b)
+
+	for _, fp := range fingerprints {
+		// Write fingerprint to buffer
+		buff.Write(fp[:auth.NegotiationFingerprintLen])
+	}
+
+	return buff.Bytes()
+}
+
+// unmarshalNegotiationFingerprints unmarshalls the marshalled byte slice into a
+// list of fingerprints.
+func unmarshalNegotiationFingerprints(buf []byte) [][]byte {
+	buff := bytes.NewBuffer(buf)
+
+	listLen := binary.LittleEndian.Uint64(buff.Next(8))
+	fingerprints := make([][]byte, listLen)
+
+	for i := range fingerprints {
+		fingerprints[i] = make([]byte, auth.NegotiationFingerprintLen)
+		copy(fingerprints[i], buff.Next(auth.NegotiationFingerprintLen))
+	}
+
+	return fingerprints
+}
+
+// makeNegotiationFingerprintsKey generates the key used to load and store
+// negotiation fingerprints for the partner.
+func makeNegotiationFingerprintsKey(partner *id.ID) string {
+	return negotiationFingerprintsKeyPrefix + partner.String()
+}
diff --git a/storage/auth/previousNegotiations_test.go b/storage/auth/previousNegotiations_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..94351aac983f8b58439177a927c05bc9df6ee616
--- /dev/null
+++ b/storage/auth/previousNegotiations_test.go
@@ -0,0 +1,431 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"github.com/cloudflare/circl/dh/sidh"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// Tests the four possible cases of Store.AddIfNew:
+//  1. If the partner does not exist, add partner with the new fingerprint.
+//		Returns newFingerprint = true, latest = true.
+//	2. If the partner exists and the fingerprint does not, add the fingerprint.
+//		Returns newFingerprint = true, latest = true.
+//	3. If the partner exists and the fingerprint exists, do nothing.
+//		Return newFingerprint = false, latest = false.
+//	4. If the partner exists, the fingerprint exists, and the fingerprint is the
+//     latest, do nothing.
+//      Return newFingerprint = false, latest = true.
+func TestStore_AddIfNew(t *testing.T) {
+	s := &Store{
+		kv:                   versioned.NewKV(make(ekv.Memstore)),
+		previousNegotiations: make(map[id.ID]struct{}),
+	}
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	newPartner := func() *id.ID {
+		partner, _ := id.NewRandomID(prng, id.User)
+		return partner
+	}
+	newFps := func() []byte {
+		dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+		_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, prng)
+		return auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+	}
+
+	type test struct {
+		name string
+
+		addPartner bool     // If true, partner is added to list first
+		addFp      bool     // If true, fingerprint is added to list first
+		latestFp   bool     // If true, fingerprint is added as latest
+		otherFps   [][]byte // Other fingerprints to add first
+
+		// Inputs
+		partner *id.ID
+		fp      []byte
+
+		// Expected values
+		newFingerprint bool
+		latest         bool
+	}
+
+	tests := []test{
+		{
+			name:           "Case 1: partner does not exist",
+			addPartner:     false,
+			addFp:          false,
+			latestFp:       false,
+			partner:        newPartner(),
+			fp:             newFps(),
+			newFingerprint: true,
+			latest:         true,
+		}, {
+			name:           "Case 2: partner exists, fingerprint does not",
+			addPartner:     true,
+			addFp:          false,
+			latestFp:       false,
+			otherFps:       [][]byte{newFps(), newFps(), newFps()},
+			partner:        newPartner(),
+			fp:             newFps(),
+			newFingerprint: true,
+			latest:         true,
+		}, {
+			name:           "Case 3: partner and fingerprint exist",
+			addPartner:     true,
+			addFp:          true,
+			latestFp:       false,
+			otherFps:       [][]byte{newFps(), newFps(), newFps()},
+			partner:        newPartner(),
+			fp:             newFps(),
+			newFingerprint: false,
+			latest:         false,
+		}, {
+			name:           "Case 4: partner and fingerprint exist, fingerprint latest",
+			addPartner:     true,
+			addFp:          true,
+			latestFp:       true,
+			otherFps:       [][]byte{newFps(), newFps(), newFps()},
+			partner:        newPartner(),
+			fp:             newFps(),
+			newFingerprint: false,
+			latest:         true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.addPartner {
+				s.previousNegotiations[*tt.partner] = struct{}{}
+				err := s.savePreviousNegotiations()
+				if err != nil {
+					t.Errorf(
+						"savePreviousNegotiations returned an error: %+v", err)
+				}
+
+				var fps [][]byte
+				if tt.addFp {
+					fps, _ = s.loadNegotiationFingerprints(tt.partner)
+
+					for _, fp := range tt.otherFps {
+						fps = append(fps, fp)
+					}
+
+					if tt.latestFp {
+						fps = append(fps, tt.fp)
+					} else {
+						fps = append([][]byte{tt.fp}, fps...)
+					}
+				}
+				err = s.saveNegotiationFingerprints(tt.partner, fps...)
+				if err != nil {
+					t.Errorf("saveNegotiationFingerprints returned an "+
+						"error: %+v", err)
+				}
+			}
+
+			newFingerprint, latest := s.AddIfNew(tt.partner, tt.fp)
+
+			if newFingerprint != tt.newFingerprint {
+				t.Errorf("Unexpected value for newFingerprint."+
+					"\nexpected: %t\nreceived: %t",
+					tt.newFingerprint, newFingerprint)
+			}
+			if latest != tt.latest {
+				t.Errorf("Unexpected value for latest."+
+					"\nexpected: %t\nreceived: %t", tt.latest, latest)
+			}
+		})
+	}
+}
+
+// Tests that Store.deletePreviousNegotiationPartner deletes the partner from
+// previousNegotiations in memory, previousNegotiations in storage, fingerprints
+// in storage, and any confirmations in storage.
+func TestStore_deletePreviousNegotiationPartner(t *testing.T) {
+	s := &Store{
+		kv:                   versioned.NewKV(make(ekv.Memstore)),
+		previousNegotiations: make(map[id.ID]struct{}),
+	}
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+
+	type values struct {
+		partner *id.ID
+		fps     [][]byte
+	}
+
+	testValues := make([]values, 16)
+
+	for i := range testValues {
+		partner, _ := id.NewRandomID(prng, id.User)
+		s.previousNegotiations[*partner] = struct{}{}
+
+		err := s.savePreviousNegotiations()
+		if err != nil {
+			t.Errorf("savePreviousNegotiations returned an error (%d): %+v",
+				i, err)
+		}
+
+		// Generate fingerprints
+		fingerprints := make([][]byte, i+1)
+		for j := range fingerprints {
+			dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+			_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, prng)
+			fingerprints[j] = auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+		}
+
+		err = s.saveNegotiationFingerprints(partner, fingerprints...)
+		if err != nil {
+			t.Errorf("saveNegotiationFingerprints returned an error (%d): %+v",
+				i, err)
+		}
+
+		testValues[i] = values{partner, fingerprints}
+
+		// Generate confirmation
+		confirmation := make([]byte, 32)
+		prng.Read(confirmation)
+
+		err = s.StoreConfirmation(partner, fingerprints[0], confirmation)
+		if err != nil {
+			t.Errorf("StoreConfirmation returned an error (%d): %+v", i, err)
+		}
+	}
+
+	// Add partner that is not in list
+	partner, _ := id.NewRandomID(prng, id.User)
+	testValues = append(testValues, values{partner, [][]byte{}})
+
+	for i, v := range testValues {
+		err := s.deletePreviousNegotiationPartner(v.partner)
+		if err != nil {
+			t.Errorf("deletePreviousNegotiationPartner returned an error "+
+				"(%d): %+v", i, err)
+		}
+
+		// Check previousNegotiations in memory
+		_, exists := s.previousNegotiations[*v.partner]
+		if exists {
+			t.Errorf("Parter %s exists in previousNegotiations (%d).",
+				v.partner, i)
+		}
+
+		// Check previousNegotiations in storage
+		previousNegotiations, err := s.loadPreviousNegotiations()
+		if err != nil {
+			t.Errorf("loadPreviousNegotiations returned an error (%d): %+v",
+				i, err)
+		}
+		_, exists = previousNegotiations[*v.partner]
+		if exists {
+			t.Errorf("Parter %s exists in previousNegotiations in storage (%d).",
+				v.partner, i)
+		}
+
+		// Check negotiation fingerprints in storage
+		fps, err := s.loadNegotiationFingerprints(v.partner)
+		if err == nil || fps != nil {
+			t.Errorf("Loaded fingerprints for partner %s (%d): %v",
+				v.partner, i, fps)
+		}
+
+		// Check all possible confirmations in storage
+		for j, fp := range v.fps {
+			confirmation, err := s.LoadConfirmation(v.partner, fp)
+			if err == nil || fps != nil {
+				t.Errorf("Loaded confirmation for partner %s and "+
+					"fingerprint %v (%d, %d): %v",
+					v.partner, fp, i, j, confirmation)
+			}
+		}
+	}
+}
+
+// Tests that Store.previousNegotiations can be saved and loaded from storage
+// via Store.savePreviousNegotiations andStore.loadPreviousNegotiations.
+func TestStore_savePreviousNegotiations_loadPreviousNegotiations(t *testing.T) {
+	s := &Store{
+		kv:                   versioned.NewKV(make(ekv.Memstore)),
+		previousNegotiations: make(map[id.ID]struct{}),
+	}
+	prng := rand.New(rand.NewSource(42))
+	expected := make(map[id.ID]struct{})
+
+	for i := 0; i < 16; i++ {
+		partner, _ := id.NewRandomID(prng, id.User)
+		s.previousNegotiations[*partner] = struct{}{}
+		expected[*partner] = struct{}{}
+
+		err := s.savePreviousNegotiations()
+		if err != nil {
+			t.Errorf("savePreviousNegotiations returned an error (%d): %+v",
+				i, err)
+		}
+
+		s.previousNegotiations, err = s.loadPreviousNegotiations()
+		if err != nil {
+			t.Errorf("loadPreviousNegotiations returned an error (%d): %+v",
+				i, err)
+		}
+
+		if !reflect.DeepEqual(expected, s.previousNegotiations) {
+			t.Errorf("Loaded previousNegotiations does not match expected (%d)."+
+				"\nexpected: %v\nreceived: %v", i, expected, s.previousNegotiations)
+		}
+	}
+}
+
+// Tests that a list of partner IDs that is marshalled and unmarshalled via
+// marshalPreviousNegotiations and unmarshalPreviousNegotiations matches the
+// original list
+func Test_marshalPreviousNegotiations_unmarshalPreviousNegotiations(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+
+	// Create original map of partner IDs
+	originalPartners := make(map[id.ID]struct{}, 50)
+	for i := 0; i < 50; i++ {
+		partner, _ := id.NewRandomID(prng, id.User)
+		originalPartners[*partner] = struct{}{}
+	}
+
+	// Marshal and unmarshal the partner list
+	marshalledPartners := marshalPreviousNegotiations(originalPartners)
+	unmarshalledPartners := unmarshalPreviousNegotiations(marshalledPartners)
+
+	// Check that the original matches the unmarshalled
+	if !reflect.DeepEqual(originalPartners, unmarshalledPartners) {
+		t.Errorf("Unmarshalled partner list does not match original."+
+			"\nexpected: %v\nreceived: %v",
+			originalPartners, unmarshalledPartners)
+	}
+}
+
+// Tests that a list of fingerprints for different partners can be saved and
+// loaded from storage via Store.saveNegotiationFingerprints and
+// Store.loadNegotiationFingerprints.
+func TestStore_saveNegotiationFingerprints_loadNegotiationFingerprints(t *testing.T) {
+	s := &Store{kv: versioned.NewKV(make(ekv.Memstore))}
+	rng := csprng.NewSystemRNG()
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+
+	testValues := make([]struct {
+		partner *id.ID
+		fps     [][]byte
+	}, 10)
+
+	for i := range testValues {
+		partner, _ := id.NewRandomID(rng, id.User)
+
+		// Generate original fingerprints to marshal
+		originalFps := make([][]byte, 50)
+		for j := range originalFps {
+			dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+			_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, rng)
+			originalFps[j] = auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+		}
+
+		testValues[i] = struct {
+			partner *id.ID
+			fps     [][]byte
+		}{partner: partner, fps: originalFps}
+
+		err := s.saveNegotiationFingerprints(partner, originalFps...)
+		if err != nil {
+			t.Errorf("saveNegotiationFingerprints returned an error (%d): %+v",
+				i, err)
+		}
+	}
+
+	for i, val := range testValues {
+		loadedFps, err := s.loadNegotiationFingerprints(val.partner)
+		if err != nil {
+			t.Errorf("loadNegotiationFingerprints returned an error (%d): %+v",
+				i, err)
+		}
+
+		if !reflect.DeepEqual(val.fps, loadedFps) {
+			t.Errorf("Loaded fingerprints do not match original (%d)."+
+				"\nexpected: %v\nreceived: %v", i, val.fps, loadedFps)
+		}
+	}
+}
+
+// Tests that a list of fingerprints that is marshalled and unmarshalled via
+// marshalNegotiationFingerprints and unmarshalNegotiationFingerprints matches
+// the original list
+func Test_marshalNegotiationFingerprints_unmarshalNegotiationFingerprints(t *testing.T) {
+	rng := csprng.NewSystemRNG()
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+
+	// Generate original fingerprints to marshal
+	originalFps := make([][]byte, 50)
+	for i := range originalFps {
+		dhPubKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+		_, sidhPubkey := utility.GenerateSIDHKeyPair(sidh.KeyVariantSidhA, rng)
+		originalFps[i] = auth.CreateNegotiationFingerprint(dhPubKey, sidhPubkey)
+	}
+
+	// Marshal and unmarshal the fingerprint list
+	marshalledFingerprints := marshalNegotiationFingerprints(originalFps...)
+	unmarshalledFps := unmarshalNegotiationFingerprints(marshalledFingerprints)
+
+	// Check that the original matches the unmarshalled
+	if !reflect.DeepEqual(originalFps, unmarshalledFps) {
+		t.Errorf("Unmarshalled fingerprints do not match original."+
+			"\nexpected: %v\nreceived: %v", originalFps, unmarshalledFps)
+	}
+}
+
+// Consistency test of makeNegotiationFingerprintsKey.
+func Test_makeNegotiationFingerprintsKey_Consistency(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	expectedKeys := []string{
+		"NegotiationFingerprints/U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID",
+		"NegotiationFingerprints/15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD",
+		"NegotiationFingerprints/YdN1vAK0HfT5GSnhj9qeb4LlTnSOgeeeS71v40zcuoQD",
+		"NegotiationFingerprints/6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44bC68D",
+		"NegotiationFingerprints/iBuCp1EQikLtPJA8qkNGWnhiBhaXiu0M48bE8657w+AD",
+		"NegotiationFingerprints/W1cS/v2+DBAoh+EA2s0tiF9pLLYH2gChHBxwceeWotwD",
+		"NegotiationFingerprints/wlpbdLLhKXBeJz8FySMmgo4rBW44F2WOEGFJiUf980QD",
+		"NegotiationFingerprints/DtTBFgI/qONXa2/tJ/+JdLrAyv2a0FaSsTYZ5ziWTf0D",
+		"NegotiationFingerprints/no1TQ3NmHP1m10/sHhuJSRq3I25LdSFikM8r60LDyicD",
+		"NegotiationFingerprints/hWDxqsBnzqbov0bUqytGgEAsX7KCDohdMmDx3peCg9QD",
+		"NegotiationFingerprints/mjb5bCCUF0bj7U2mRqmui0+ntPw6ILr6GnXtMnqGuLAD",
+		"NegotiationFingerprints/mvHP0rO1EhnqeVM6v0SNLEedMmB1M5BZFMjMHPCdo54D",
+		"NegotiationFingerprints/kp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQiR9ZcD",
+		"NegotiationFingerprints/KSqiuKoEfGHNszNz6+csJ6CYwCGX2ua3MsNR32aPh04D",
+		"NegotiationFingerprints/nxzgnKhgF+fiF0gwP/QcGyPhHEjtF1OdaF928qeYvGQD",
+		"NegotiationFingerprints/Dl2yhksq08Js5jgjQnZaE9aW5S33YPbDRl4poNykasMD",
+	}
+
+	for i, expected := range expectedKeys {
+		partner, _ := id.NewRandomID(prng, id.User)
+
+		key := makeNegotiationFingerprintsKey(partner)
+		if expected != key {
+			t.Errorf("Negotiation fingerprints key does not match expected "+
+				"for partner %s (%d).\nexpected: %q\nreceived: %q", partner, i,
+				expected, key)
+		}
+
+		// fmt.Printf("\"%s\",\n", key)
+	}
+}
diff --git a/storage/auth/store.go b/storage/auth/store.go
index fb9aa2b04be8875bae32de4c74752db3dd80ddee..83a306d9bbb50f43cf15b4091c8b275feff414d1 100644
--- a/storage/auth/store.go
+++ b/storage/auth/store.go
@@ -31,11 +31,12 @@ const requestMapKey = "map"
 const requestMapVersion = 0
 
 type Store struct {
-	kv           *versioned.KV
-	grp          *cyclic.Group
-	requests     map[id.ID]*request
-	fingerprints map[format.Fingerprint]fingerprint
-	mux          sync.RWMutex
+	kv                   *versioned.KV
+	grp                  *cyclic.Group
+	requests             map[id.ID]*request
+	fingerprints         map[format.Fingerprint]fingerprint
+	previousNegotiations map[id.ID]struct{}
+	mux                  sync.RWMutex
 }
 
 // NewStore creates a new store. All passed in private keys are added as
@@ -43,10 +44,11 @@ type Store struct {
 func NewStore(kv *versioned.KV, grp *cyclic.Group, privKeys []*cyclic.Int) (*Store, error) {
 	kv = kv.Prefix(storePrefix)
 	s := &Store{
-		kv:           kv,
-		grp:          grp,
-		requests:     make(map[id.ID]*request),
-		fingerprints: make(map[format.Fingerprint]fingerprint),
+		kv:                   kv,
+		grp:                  grp,
+		requests:             make(map[id.ID]*request),
+		fingerprints:         make(map[format.Fingerprint]fingerprint),
+		previousNegotiations: make(map[id.ID]struct{}),
 	}
 
 	for _, key := range privKeys {
@@ -59,6 +61,12 @@ func NewStore(kv *versioned.KV, grp *cyclic.Group, privKeys []*cyclic.Int) (*Sto
 		}
 	}
 
+	err := s.savePreviousNegotiations()
+	if err != nil {
+		return nil, errors.Errorf(
+			"failed to load previousNegotiations partners: %+v", err)
+	}
+
 	return s, s.save()
 }
 
@@ -72,10 +80,11 @@ func LoadStore(kv *versioned.KV, grp *cyclic.Group, privKeys []*cyclic.Int) (*St
 	}
 
 	s := &Store{
-		kv:           kv,
-		grp:          grp,
-		requests:     make(map[id.ID]*request),
-		fingerprints: make(map[format.Fingerprint]fingerprint),
+		kv:                   kv,
+		grp:                  grp,
+		requests:             make(map[id.ID]*request),
+		fingerprints:         make(map[format.Fingerprint]fingerprint),
+		previousNegotiations: make(map[id.ID]struct{}),
 	}
 
 	for _, key := range privKeys {
@@ -141,10 +150,17 @@ func LoadStore(kv *versioned.KV, grp *cyclic.Group, privKeys []*cyclic.Int) (*St
 			jww.FATAL.Panicf("Unknown request type: %d", r.rt)
 		}
 
-		//store in the request map
+		// store in the request map
 		s.requests[*rid] = r
 	}
 
+	// Load previous negotiations from storage
+	s.previousNegotiations, err = s.loadPreviousNegotiations()
+	if err != nil {
+		return nil, errors.Errorf("failed to load list of previouse "+
+			"negotation partner IDs: %+v", err)
+	}
+
 	return s, nil
 }
 
@@ -429,6 +445,11 @@ func (s *Store) Delete(partner *id.ID) error {
 			"deletion: %+v", err)
 	}
 
+	err := s.deletePreviousNegotiationPartner(partner)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to delete partner negotiations: %+v", err)
+	}
+
 	return nil
 }
 
diff --git a/storage/auth/store_test.go b/storage/auth/store_test.go
index f1f10414836eb6c37596593322d112dbcfcc9394..1fc808235b9d48c1c9b59027a32de86feabd535b 100644
--- a/storage/auth/store_test.go
+++ b/storage/auth/store_test.go
@@ -88,6 +88,9 @@ func TestLoadStore(t *testing.T) {
 		t.Fatalf("AddSent() produced an error: %+v", err)
 	}
 
+	s.AddIfNew(
+		sr.partner, auth.CreateNegotiationFingerprint(privKeys[0], sidhPubKey))
+
 	// Attempt to load the store
 	store, err := LoadStore(kv, s.grp, privKeys)
 	if err != nil {
diff --git a/storage/cmix/store.go b/storage/cmix/store.go
index 32b7d506c7de63b4fa901773ff6ba5551747d293..641261c9d85fb0f2718058c420ecb797cd00aaea 100644
--- a/storage/cmix/store.go
+++ b/storage/cmix/store.go
@@ -14,7 +14,6 @@ import (
 	"gitlab.com/elixxir/client/storage/utility"
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/netTime"
@@ -25,15 +24,11 @@ const prefix = "cmix"
 const currentStoreVersion = 0
 const (
 	storeKey   = "KeyStore"
-	pubKeyKey  = "DhPubKey"
-	privKeyKey = "DhPrivKey"
 	grpKey     = "GroupKey"
 )
 
 type Store struct {
 	nodes        map[id.ID]*key
-	dhPrivateKey *cyclic.Int
-	dhPublicKey  *cyclic.Int
 	validUntil   uint64
 	keyId        []byte
 	grp          *cyclic.Group
@@ -42,32 +37,17 @@ type Store struct {
 }
 
 // NewStore returns a new cMix storage object.
-func NewStore(grp *cyclic.Group, kv *versioned.KV, priv *cyclic.Int) (*Store, error) {
+func NewStore(grp *cyclic.Group, kv *versioned.KV) (*Store, error) {
 	// Generate public key
-	pub := diffieHellman.GeneratePublicKey(priv, grp)
 	kv = kv.Prefix(prefix)
 
 	s := &Store{
 		nodes:        make(map[id.ID]*key),
-		dhPrivateKey: priv,
-		dhPublicKey:  pub,
 		grp:          grp,
 		kv:           kv,
 	}
 
-	err := utility.StoreCyclicKey(kv, pub, pubKeyKey)
-	if err != nil {
-		return nil,
-			errors.WithMessage(err, "Failed to store cMix DH public key")
-	}
-
-	err = utility.StoreCyclicKey(kv, priv, privKeyKey)
-	if err != nil {
-		return nil,
-			errors.WithMessage(err, "Failed to store cMix DH private key")
-	}
-
-	err = utility.StoreGroup(kv, grp, grpKey)
+	err := utility.StoreGroup(kv, grp, grpKey)
 	if err != nil {
 		return nil, errors.WithMessage(err, "Failed to store cMix group")
 	}
@@ -172,16 +152,6 @@ func (s *Store) GetRoundKeys(topology *connect.Circuit) (*RoundKeys, []*id.ID) {
 	return rk, missingNodes
 }
 
-// GetDHPrivateKey returns the diffie hellman private key
-func (s *Store) GetDHPrivateKey() *cyclic.Int {
-	return s.dhPrivateKey
-}
-
-// GetDHPublicKey returns the diffie hellman public key.
-func (s *Store) GetDHPublicKey() *cyclic.Int {
-	return s.dhPublicKey
-}
-
 // GetGroup returns the cyclic group used for cMix.
 func (s *Store) GetGroup() *cyclic.Group {
 	return s.grp
@@ -251,16 +221,6 @@ func (s *Store) unmarshal(b []byte) error {
 		s.nodes[nid] = k
 	}
 
-	s.dhPrivateKey, err = utility.LoadCyclicKey(s.kv, privKeyKey)
-	if err != nil {
-		return errors.WithMessage(err, "Failed to load cMix DH private key")
-	}
-
-	s.dhPublicKey, err = utility.LoadCyclicKey(s.kv, pubKeyKey)
-	if err != nil {
-		return errors.WithMessage(err, "Failed to load cMix DH public key")
-	}
-
 	s.grp, err = utility.LoadGroup(s.kv, grpKey)
 	if err != nil {
 		return errors.WithMessage(err, "Failed to load cMix group")
diff --git a/storage/cmix/store_test.go b/storage/cmix/store_test.go
index d84b2dff66723020d5c0b16f7b7d2b0d059911d1..6fb614257dc2e23fab664bd73cd8a3055a6a7c06 100644
--- a/storage/cmix/store_test.go
+++ b/storage/cmix/store_test.go
@@ -11,7 +11,6 @@ import (
 	"bytes"
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/crypto/large"
@@ -26,10 +25,8 @@ func TestNewStore(t *testing.T) {
 	vkv := versioned.NewKV(kv)
 
 	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
-	priv := grp.NewInt(2)
-	pub := diffieHellman.GeneratePublicKey(priv, grp)
 
-	store, err := NewStore(grp, vkv, priv)
+	store, err := NewStore(grp, vkv)
 	if err != nil {
 		t.Fatal(err.Error())
 	}
@@ -37,12 +34,6 @@ func TestNewStore(t *testing.T) {
 	if store.nodes == nil {
 		t.Errorf("Failed to initialize nodes")
 	}
-	if store.GetDHPrivateKey() == nil || store.GetDHPrivateKey().Cmp(priv) != 0 {
-		t.Errorf("Failed to set store.dhPrivateKey correctly")
-	}
-	if store.GetDHPublicKey() == nil || store.GetDHPublicKey().Cmp(pub) != 0 {
-		t.Errorf("Failed to set store.dhPublicKey correctly")
-	}
 	if store.grp == nil {
 		t.Errorf("Failed to set store.grp")
 	}
@@ -132,12 +123,6 @@ func TestLoadStore(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Unable to load store: %+v", err)
 	}
-	if store.GetDHPublicKey().Cmp(testStore.GetDHPublicKey()) != 0 {
-		t.Errorf("LoadStore failed to load public key")
-	}
-	if store.GetDHPrivateKey().Cmp(testStore.GetDHPrivateKey()) != 0 {
-		t.Errorf("LoadStore failed to load public key")
-	}
 	if len(store.nodes) != len(testStore.nodes) {
 		t.Errorf("LoadStore failed to load node keys")
 	}
@@ -221,7 +206,7 @@ func TestStore_Count(t *testing.T) {
 	vkv := versioned.NewKV(make(ekv.Memstore))
 	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
 
-	store, err := NewStore(grp, vkv, grp.NewInt(2))
+	store, err := NewStore(grp, vkv)
 	if err != nil {
 		t.Fatalf("Failed to generate new Store: %+v", err)
 	}
@@ -249,9 +234,8 @@ func makeTestStore() (*Store, *versioned.KV) {
 	vkv := versioned.NewKV(kv)
 
 	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
-	priv := grp.NewInt(2)
 
-	testStore, _ := NewStore(grp, vkv, priv)
+	testStore, _ := NewStore(grp, vkv)
 
 	return testStore, vkv
 }
diff --git a/storage/e2e/manager.go b/storage/e2e/manager.go
index f6c000f78ff002e3a3c82db143b8e783ca8a497e..ff76e9985b72ef1c355be64ee0f643e1c07c3205 100644
--- a/storage/e2e/manager.go
+++ b/storage/e2e/manager.go
@@ -297,4 +297,4 @@ func (m *Manager) GetFileTransferPreimage() []byte {
 // fingerprint for group requests received from this user.
 func (m *Manager) GetGroupRequestPreimage() []byte {
 	return preimage.Generate(m.GetRelationshipFingerprintBytes(), preimage.GroupRq)
-}
\ No newline at end of file
+}
diff --git a/storage/e2e/session.go b/storage/e2e/session.go
index 8fbcae123c8a22ab87504bdac5188e78c9139902..10d7c4faf6ddcdd7e46ecbece5c0447b0ed74c36 100644
--- a/storage/e2e/session.go
+++ b/storage/e2e/session.go
@@ -639,7 +639,7 @@ func (s *Session) generate(kv *versioned.KV) *versioned.KV {
 		s.baseKey.Bytes(), h).Int64() + int64(p.MinKeys))
 
 	// start rekeying when enough keys have been used
-	s.rekeyThreshold = uint32(math.Ceil(s.e2eParams.RekeyThreshold*float64(numKeys)))
+	s.rekeyThreshold = uint32(math.Ceil(s.e2eParams.RekeyThreshold * float64(numKeys)))
 
 	// the total number of keys should be the number of rekeys plus the
 	// number of keys to use
diff --git a/storage/e2e/store.go b/storage/e2e/store.go
index 9be32c5c490e715dfa424e8b788dfb21f68b47a9..1aea1efe8ddff38327d44fbfc84a07a1f32db458 100644
--- a/storage/e2e/store.go
+++ b/storage/e2e/store.go
@@ -246,6 +246,21 @@ func (s *Store) GetPartnerContact(partnerID *id.ID) (contact.Contact, error) {
 	return c, nil
 }
 
+// GetPartners returns a list of all partner IDs that the user has
+// an E2E relationship with.
+func (s *Store) GetPartners() []*id.ID {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	partnerIds := make([]*id.ID, 0, len(s.managers))
+
+	for partnerId := range s.managers {
+		partnerIds = append(partnerIds, &partnerId)
+	}
+
+	return partnerIds
+}
+
 // PopKey pops a key for use based upon its fingerprint.
 func (s *Store) PopKey(f format.Fingerprint) (*Key, bool) {
 	return s.fingerprints.Pop(f)
diff --git a/storage/edge/edge.go b/storage/edge/edge.go
index 7e88d35eccbbe19752dccd4de63363e37e5a12f5..e6a25fa821587393fca2e5c236e36111a7bcadd9 100644
--- a/storage/edge/edge.go
+++ b/storage/edge/edge.go
@@ -2,13 +2,14 @@ package edge
 
 import (
 	"encoding/json"
+	"sync"
+
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/storage/versioned"
 	fingerprint2 "gitlab.com/elixxir/crypto/fingerprint"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/netTime"
-	"sync"
 )
 
 // This stores Preimages which can be used with the identity fingerprint system.
@@ -65,6 +66,8 @@ func (s *Store) Add(preimage Preimage, identity *id.ID) {
 
 	// Add to the list
 	if !preimages.add(preimage) {
+		jww.ERROR.Printf("Preimage already exists for id %s: %v",
+			identity, preimage)
 		return
 	}
 
diff --git a/storage/session.go b/storage/session.go
index d11e11fc6697edd4e52d3550c6de2d6677a37bd1..1fc0aec0e43ffa46d95ce07db69936e381a4e0db 100644
--- a/storage/session.go
+++ b/storage/session.go
@@ -13,6 +13,7 @@ import (
 	"gitlab.com/elixxir/client/storage/edge"
 	"gitlab.com/elixxir/client/storage/hostList"
 	"gitlab.com/elixxir/client/storage/rounds"
+	"gitlab.com/elixxir/client/storage/ud"
 	"gitlab.com/xx_network/primitives/rateLimiting"
 	"sync"
 	"testing"
@@ -74,6 +75,7 @@ type Session struct {
 	hostList            *hostList.Store
 	edgeCheck           *edge.Store
 	ringBuff            *conversation.Buff
+	ud                  *ud.Store
 }
 
 // Initialize a new Session object
@@ -116,7 +118,7 @@ func New(baseDir, password string, u userInterface.User,
 	}
 	uid := s.user.GetCryptographicIdentity().GetReceptionID()
 
-	s.cmix, err = cmix.NewStore(cmixGrp, s.kv, u.CmixDhPrivateKey)
+	s.cmix, err = cmix.NewStore(cmixGrp, s.kv)
 	if err != nil {
 		return nil, errors.WithMessage(err, "Failed to create cmix store")
 	}
@@ -178,6 +180,11 @@ func New(baseDir, password string, u userInterface.User,
 	s.bucketStore = utility.NewStoredBucket(uint32(rateLimitParams.Capacity), uint32(rateLimitParams.LeakedTokens),
 		time.Duration(rateLimitParams.LeakDuration), s.kv)
 
+	s.ud, err = ud.NewStore(s.kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create ud store")
+	}
+
 	return s, nil
 }
 
@@ -275,6 +282,11 @@ func Load(baseDir, password string, currentVersion version.Version,
 			"Failed to load bucket store")
 	}
 
+	s.ud, err = ud.LoadStore(s.kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load ud store")
+	}
+
 	return s, nil
 }
 
@@ -364,6 +376,12 @@ func (s *Session) GetEdge() *edge.Store {
 	return s.edgeCheck
 }
 
+func (s *Session) GetUd() *ud.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.ud
+}
+
 // GetBucketParams returns the bucket params store.
 func (s *Session) GetBucketParams() *utility.BucketParamStore {
 	s.mux.RLock()
@@ -445,7 +463,7 @@ func InitTestingSession(i interface{}) *Session {
 			"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
 			"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
 			"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
-	cmixStore, err := cmix.NewStore(cmixGrp, kv, cmixGrp.NewInt(2))
+	cmixStore, err := cmix.NewStore(cmixGrp, kv)
 	if err != nil {
 		jww.FATAL.Panicf("InitTestingSession failed to create dummy cmix session: %+v", err)
 	}
@@ -509,5 +527,10 @@ func InitTestingSession(i interface{}) *Session {
 	//	jww.FATAL.Panicf("Failed to create ring buffer store: %+v", err)
 	//}
 
+	s.ud, err = ud.NewStore(s.kv)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to create ud store: %v", err)
+	}
+
 	return s
 }
diff --git a/storage/ud/facts.go b/storage/ud/facts.go
new file mode 100644
index 0000000000000000000000000000000000000000..9762bc068b915c4d5138226bf827157911508332
--- /dev/null
+++ b/storage/ud/facts.go
@@ -0,0 +1,217 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package ud
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/primitives/fact"
+	"sync"
+)
+
+const (
+	factTypeExistsErr               = "Fact %v cannot be added as fact type %s has already been stored. Cancelling backup operation!"
+	backupMissingInvalidFactTypeErr = "BackUpMissingFacts expects input in the order (email, phone). " +
+		"%s (%s) is non-empty but not an email. Cancelling backup operation"
+	backupMissingAllZeroesFactErr = "Cannot backup missing facts: Both email and phone facts are empty!"
+	factNotInStoreErr             = "Fact %v does not exist in store"
+)
+
+// Store is the storage object for the higher level ud.Manager object.
+// This storage implementation is written for client side.
+type Store struct {
+	// confirmedFacts contains facts that have been confirmed
+	confirmedFacts map[fact.Fact]struct{}
+	// Stores facts that have been added by UDB but unconfirmed facts.
+	// Maps confirmID to fact
+	unconfirmedFacts map[string]fact.Fact
+	kv               *versioned.KV
+	mux              sync.RWMutex
+}
+
+// NewStore creates a new, empty Store object.
+func NewStore(kv *versioned.KV) (*Store, error) {
+	kv = kv.Prefix(prefix)
+
+	s := &Store{
+		confirmedFacts:   make(map[fact.Fact]struct{}, 0),
+		unconfirmedFacts: make(map[string]fact.Fact, 0),
+		kv:               kv,
+	}
+
+	return s, s.save()
+}
+
+// StoreUnconfirmedFact stores a fact that has been added to UD but has not been
+// confirmed by the user. It is keyed on the confirmation ID given by UD.
+func (s *Store) StoreUnconfirmedFact(confirmationId string, f fact.Fact) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	s.unconfirmedFacts[confirmationId] = f
+	return s.saveUnconfirmedFacts()
+}
+
+// ConfirmFact will delete the fact from the unconfirmed store and
+// add it to the confirmed fact store. The Store will then be saved
+func (s *Store) ConfirmFact(confirmationId string) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	f, exists := s.unconfirmedFacts[confirmationId]
+	if !exists {
+		return errors.New(fmt.Sprintf("No fact exists in store "+
+			"with confirmation ID %q", confirmationId))
+	}
+
+	delete(s.unconfirmedFacts, confirmationId)
+	s.confirmedFacts[f] = struct{}{}
+	return s.save()
+}
+
+// BackUpMissingFacts adds a registered fact to the Store object. It can take in both an
+// email and a phone number. One or the other may be an empty string, however both is considered
+// an error. It checks for each whether that fact type already exists in the structure. If a fact
+// type already exists, an error is returned.
+// ************************************************************************
+// NOTE: This is done since BackUpMissingFacts is exposed to the
+// bindings layer. This prevents front end from using this as the method
+// to store facts on their end, which is not its intended use case. It's intended use
+// case is to store already registered facts, prior to the creation of this function.
+// We handle storage of newly registered internally using Store.ConfirmFact.
+// ************************************************************************
+// Any other fact.FactType is not accepted and returns an error and nothing is backed up.
+// If you attempt to back up a fact type that has already been backed up,
+// an error will be returned and nothing will be backed up.
+// Otherwise, it adds the fact and returns whether the Store saved successfully.
+func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if isFactZero(email) && isFactZero(phone) {
+		return errors.New(backupMissingAllZeroesFactErr)
+	}
+
+	modifiedEmail, modifiedPhone := false, false
+
+	// Handle email if it is not zero (empty string)
+	if !isFactZero(email) {
+		// check if fact is expected type
+		if email.T != fact.Email {
+			return errors.New(fmt.Sprintf(backupMissingInvalidFactTypeErr, fact.Email, email.Fact))
+		}
+
+		// Check if fact type is already in map. See docstring NOTE for explanation
+		if isFactTypeInMap(fact.Email, s.confirmedFacts) {
+			// If an email exists in memory, return an error
+			return errors.Errorf(factTypeExistsErr, email, fact.Email)
+		} else {
+			modifiedEmail = true
+		}
+	}
+
+	if !isFactZero(phone) {
+		// check if fact is expected type
+		if phone.T != fact.Phone {
+			return errors.New(fmt.Sprintf(backupMissingInvalidFactTypeErr, fact.Phone, phone.Fact))
+		}
+
+		// Check if fact type is already in map. See docstring NOTE for explanation
+		if isFactTypeInMap(fact.Phone, s.confirmedFacts) {
+			// If a phone exists in memory, return an error
+			return errors.Errorf(factTypeExistsErr, phone, fact.Phone)
+		} else {
+			modifiedPhone = true
+		}
+	}
+
+	if modifiedPhone || modifiedEmail {
+		if modifiedEmail {
+			s.confirmedFacts[email] = struct{}{}
+		}
+
+		if modifiedPhone {
+			s.confirmedFacts[phone] = struct{}{}
+		}
+
+		return s.saveConfirmedFacts()
+	}
+
+	return nil
+
+}
+
+// DeleteFact is our internal use function which will delete the registered fact
+// from memory and storage. An error is returned if the fact does not exist in
+// memory.
+func (s *Store) DeleteFact(f fact.Fact) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if _, exists := s.confirmedFacts[f]; !exists {
+		return errors.Errorf(factNotInStoreErr, f)
+	}
+
+	delete(s.confirmedFacts, f)
+	return s.saveConfirmedFacts()
+}
+
+// GetStringifiedFacts returns a list of stringified facts from the Store's
+// confirmedFacts map.
+func (s *Store) GetStringifiedFacts() []string {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	return s.serializeConfirmedFacts()
+}
+
+// GetFacts returns a list of fact.Fact objects that exist within the
+// Store's confirmedFacts map.
+func (s *Store) GetFacts() []fact.Fact {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	// Flatten the facts into a slice
+	facts := make([]fact.Fact, 0, len(s.confirmedFacts))
+	for f := range s.confirmedFacts {
+		facts = append(facts, f)
+	}
+
+	return facts
+}
+
+// serializeConfirmedFacts is a helper function which serializes Store's confirmedFacts
+// map into a list of strings. Each string in the list represents
+// a fact.Fact that has been Stringified.
+func (s *Store) serializeConfirmedFacts() []string {
+	fStrings := make([]string, 0, len(s.confirmedFacts))
+	for f := range s.confirmedFacts {
+		fStrings = append(fStrings, f.Stringify())
+	}
+
+	return fStrings
+}
+
+// fixme: consider this being a method on the fact.Fact object?
+// isFactZero tests whether a fact has been uninitialized.
+func isFactZero(f fact.Fact) bool {
+	return f.T == fact.Username && f.Fact == ""
+}
+
+// isFactTypeInMap is a helper function which determines whether a fact type exists within
+// the data structure.
+func isFactTypeInMap(factType fact.FactType, facts map[fact.Fact]struct{}) bool {
+	for f := range facts {
+		if f.T == factType {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/storage/ud/facts_test.go b/storage/ud/facts_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c1f5dfc5c965d8b1bece3ab7fa3b903dc21e2b33
--- /dev/null
+++ b/storage/ud/facts_test.go
@@ -0,0 +1,296 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package ud
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/fact"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+func TestNewStore(t *testing.T) {
+
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	_, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+}
+
+func TestStore_ConfirmFact(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	confirmId := "confirm"
+
+	expected := fact.Fact{
+		Fact: "josh",
+		T:    fact.Username,
+	}
+
+	err = expectedStore.StoreUnconfirmedFact(confirmId, expected)
+	if err != nil {
+		t.Fatalf("StoreUnconfirmedFact error: %v", err)
+	}
+
+	err = expectedStore.ConfirmFact(confirmId)
+	if err != nil {
+		t.Fatalf("ConfirmFact() produced an error: %v", err)
+	}
+
+	_, exists := expectedStore.confirmedFacts[expected]
+	if !exists {
+		t.Fatalf("Fact %s does not exist in map", expected)
+	}
+
+	// Check that fact was removed from unconfirmed
+	_, exists = expectedStore.unconfirmedFacts[confirmId]
+	if exists {
+		t.Fatalf("Confirmed fact %v should be removed from unconfirmed"+
+			" map", expected)
+	}
+}
+
+func TestStore_StoreUnconfirmedFact(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	confirmId := "confirm"
+
+	expected := fact.Fact{
+		Fact: "josh",
+		T:    fact.Username,
+	}
+
+	err = expectedStore.StoreUnconfirmedFact(confirmId, expected)
+	if err != nil {
+		t.Fatalf("StoreUnconfirmedFact error: %v", err)
+	}
+
+	// Check that fact exists in unconfirmed
+	_, exists := expectedStore.unconfirmedFacts[confirmId]
+	if !exists {
+		t.Fatalf("Confirmed fact %v should be removed from unconfirmed"+
+			" map", expected)
+	}
+}
+
+func TestStore_DeleteFact(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	expected := fact.Fact{
+		Fact: "josh",
+		T:    fact.Username,
+	}
+
+	expectedStore.confirmedFacts[expected] = struct{}{}
+
+	_, exists := expectedStore.confirmedFacts[expected]
+	if !exists {
+		t.Fatalf("Fact %s does not exist in map", expected)
+	}
+
+	err = expectedStore.DeleteFact(expected)
+	if err != nil {
+		t.Fatalf("DeleteFact() produced an error: %v", err)
+	}
+
+	err = expectedStore.DeleteFact(expected)
+	if err == nil {
+		t.Fatalf("DeleteFact should produce an error when deleting a fact not in store")
+	}
+
+}
+
+func TestStore_BackUpMissingFacts(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	email := fact.Fact{
+		Fact: "josh@elixxir.io",
+		T:    fact.Email,
+	}
+
+	phone := fact.Fact{
+		Fact: "6175555678",
+		T:    fact.Phone,
+	}
+
+	err = expectedStore.BackUpMissingFacts(email, phone)
+	if err != nil {
+		t.Fatalf("BackUpMissingFacts() produced an error: %v", err)
+	}
+
+	_, exists := expectedStore.confirmedFacts[email]
+	if !exists {
+		t.Fatalf("Fact %v not found in store.", email)
+	}
+
+	_, exists = expectedStore.confirmedFacts[phone]
+	if !exists {
+		t.Fatalf("Fact %v not found in store.", phone)
+	}
+
+}
+
+func TestStore_BackUpMissingFacts_DuplicateFactType(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	email := fact.Fact{
+		Fact: "josh@elixxir.io",
+		T:    fact.Email,
+	}
+
+	phone := fact.Fact{
+		Fact: "6175555678",
+		T:    fact.Phone,
+	}
+
+	err = expectedStore.BackUpMissingFacts(email, phone)
+	if err != nil {
+		t.Fatalf("BackUpMissingFacts() produced an error: %v", err)
+	}
+
+	err = expectedStore.BackUpMissingFacts(email, fact.Fact{})
+	if err == nil {
+		t.Fatalf("BackUpMissingFacts() should not allow backing up an "+
+			"email when an email has already been backed up: %v", err)
+	}
+
+	err = expectedStore.BackUpMissingFacts(fact.Fact{}, phone)
+	if err == nil {
+		t.Fatalf("BackUpMissingFacts() should not allow backing up a "+
+			"phone number when a phone number has already been backed up: %v", err)
+	}
+
+}
+
+func TestStore_GetFacts(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	testStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	emailFact := fact.Fact{
+		Fact: "josh@elixxir.io",
+		T:    fact.Email,
+	}
+
+	emptyFact := fact.Fact{}
+
+	err = testStore.BackUpMissingFacts(emailFact, emptyFact)
+	if err != nil {
+		t.Fatalf("Faild to add fact %v: %v", emailFact, err)
+	}
+
+	phoneFact := fact.Fact{
+		Fact: "6175555212",
+		T:    fact.Phone,
+	}
+
+	err = testStore.BackUpMissingFacts(emptyFact, phoneFact)
+	if err != nil {
+		t.Fatalf("Faild to add fact %v: %v", phoneFact, err)
+	}
+
+	expectedFacts := []fact.Fact{emailFact, phoneFact}
+
+	receivedFacts := testStore.GetFacts()
+
+	sort.SliceStable(receivedFacts, func(i, j int) bool {
+		return receivedFacts[i].Fact > receivedFacts[j].Fact
+	})
+
+	sort.SliceStable(expectedFacts, func(i, j int) bool {
+		return expectedFacts[i].Fact > expectedFacts[j].Fact
+	})
+
+	if !reflect.DeepEqual(expectedFacts, receivedFacts) {
+		t.Fatalf("GetFacts() did not return expected fact list."+
+			"\nExpected: %v"+
+			"\nReceived: %v", expectedFacts, receivedFacts)
+	}
+}
+
+func TestStore_GetFactStrings(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	testStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	emailFact := fact.Fact{
+		Fact: "josh@elixxir.io",
+		T:    fact.Email,
+	}
+
+	emptyFact := fact.Fact{}
+
+	err = testStore.BackUpMissingFacts(emailFact, emptyFact)
+	if err != nil {
+		t.Fatalf("Faild to add fact %v: %v", emailFact, err)
+	}
+
+	phoneFact := fact.Fact{
+		Fact: "6175555212",
+		T:    fact.Phone,
+	}
+
+	err = testStore.BackUpMissingFacts(emptyFact, phoneFact)
+	if err != nil {
+		t.Fatalf("Faild to add fact %v: %v", phoneFact, err)
+	}
+
+	expectedFacts := []string{emailFact.Stringify(), phoneFact.Stringify()}
+
+	receivedFacts := testStore.GetStringifiedFacts()
+	sort.SliceStable(receivedFacts, func(i, j int) bool {
+		return receivedFacts[i] > receivedFacts[j]
+	})
+
+	sort.SliceStable(expectedFacts, func(i, j int) bool {
+		return expectedFacts[i] > expectedFacts[j]
+	})
+
+	if !reflect.DeepEqual(expectedFacts, receivedFacts) {
+		t.Fatalf("GetStringifiedFacts() did not return expected fact list."+
+			"\nExpected: %v"+
+			"\nReceived: %v", expectedFacts, receivedFacts)
+	}
+
+}
diff --git a/storage/ud/store.go b/storage/ud/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..33f62113e8218b378df3e6afd2f72ea972a2a6e0
--- /dev/null
+++ b/storage/ud/store.go
@@ -0,0 +1,241 @@
+package ud
+
+// This file handles the storage operations on facts.
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/netTime"
+)
+
+// Storage constants
+const (
+	version            = 0
+	prefix             = "udStorePrefix"
+	unconfirmedFactKey = "unconfirmedFactKey"
+	confirmedFactKey   = "confirmedFactKey"
+)
+
+// Error constants
+const (
+	malformedFactErr = "Failed to load due to " +
+		"malformed fact"
+	loadConfirmedFactErr   = "Failed to load confirmed facts"
+	loadUnconfirmedFactErr = "Failed to load unconfirmed facts"
+	saveUnconfirmedFactErr = "Failed to save unconfirmed facts"
+	saveConfirmedFactErr   = "Failed to save confirmed facts"
+)
+
+// unconfirmedFactDisk is an object used to store the data of an unconfirmed fact.
+// It combines the key (confirmationId) and fact data (stringifiedFact) into a
+// single JSON-able object.
+type unconfirmedFactDisk struct {
+	confirmationId  string
+	stringifiedFact string
+}
+
+/////////////////////////////////////////////////////////////////
+// SAVE FUNCTIONS
+/////////////////////////////////////////////////////////////////
+
+// save serializes the state within Store into byte data and stores
+// that data into storage via the EKV.
+func (s *Store) save() error {
+
+	err := s.saveUnconfirmedFacts()
+	if err != nil {
+		return errors.WithMessage(err, saveUnconfirmedFactErr)
+	}
+
+	err = s.saveConfirmedFacts()
+	if err != nil {
+		return errors.WithMessage(err, saveConfirmedFactErr)
+	}
+
+	return nil
+}
+
+// saveConfirmedFacts saves all the data within Store.confirmedFacts into storage.
+func (s *Store) saveConfirmedFacts() error {
+
+	data, err := s.marshalConfirmedFacts()
+	if err != nil {
+		return err
+	}
+
+	// Construct versioned object
+	now := netTime.Now()
+	obj := versioned.Object{
+		Version:   version,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	// Save to storage
+	return s.kv.Set(confirmedFactKey, version, &obj)
+}
+
+// saveUnconfirmedFacts saves all data within Store.unconfirmedFacts into storage.
+func (s *Store) saveUnconfirmedFacts() error {
+	data, err := s.marshalUnconfirmedFacts()
+	if err != nil {
+		return err
+	}
+
+	// Construct versioned object
+	now := netTime.Now()
+	obj := versioned.Object{
+		Version:   version,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	// Save to storage
+	return s.kv.Set(unconfirmedFactKey, version, &obj)
+
+}
+
+/////////////////////////////////////////////////////////////////
+// LOAD FUNCTIONS
+/////////////////////////////////////////////////////////////////
+
+// LoadStore loads the Store object from the provided versioned.KV.
+func LoadStore(kv *versioned.KV) (*Store, error) {
+	kv = kv.Prefix(prefix)
+
+	s := &Store{
+		confirmedFacts:   make(map[fact.Fact]struct{}, 0),
+		unconfirmedFacts: make(map[string]fact.Fact, 0),
+		kv:               kv,
+	}
+
+	return s, s.load()
+
+}
+
+// load is a helper function which loads all data stored in storage from
+// the save operation.
+func (s *Store) load() error {
+
+	err := s.loadUnconfirmedFacts()
+	if err != nil {
+		return errors.WithMessage(err, loadUnconfirmedFactErr)
+	}
+
+	err = s.loadConfirmedFacts()
+	if err != nil {
+		return errors.WithMessage(err, loadConfirmedFactErr)
+	}
+
+	return nil
+}
+
+// loadConfirmedFacts loads all confirmed facts from storage.
+// It is the inverse operation of saveConfirmedFacts.
+func (s *Store) loadConfirmedFacts() error {
+	// Pull data from storage
+	obj, err := s.kv.Get(confirmedFactKey, version)
+	if err != nil {
+		return err
+	}
+
+	// Place the map in memory
+	s.confirmedFacts, err = s.unmarshalConfirmedFacts(obj.Data)
+
+	return nil
+}
+
+// loadUnconfirmedFacts loads all unconfirmed facts from storage.
+// It is the inverse operation of saveUnconfirmedFacts.
+func (s *Store) loadUnconfirmedFacts() error {
+	// Pull data from storage
+	obj, err := s.kv.Get(unconfirmedFactKey, version)
+	if err != nil {
+		return err
+	}
+
+	// Place the map in memory
+	s.unconfirmedFacts, err = s.unmarshalUnconfirmedFacts(obj.Data)
+
+	return nil
+}
+
+/////////////////////////////////////////////////////////////////
+// MARSHAL/UNMARSHAL FUNCTIONS
+/////////////////////////////////////////////////////////////////
+
+// marshalConfirmedFacts is a marshaller which serializes the data
+//// in the confirmedFacts map into a JSON.
+func (s *Store) marshalConfirmedFacts() ([]byte, error) {
+	// Flatten confirmed facts to a list
+	fStrings := s.serializeConfirmedFacts()
+
+	// Marshal to JSON
+	return json.Marshal(&fStrings)
+}
+
+// marshalUnconfirmedFacts is a marshaller which serializes the data
+// in the unconfirmedFacts map into a JSON.
+func (s *Store) marshalUnconfirmedFacts() ([]byte, error) {
+	// Flatten unconfirmed facts to a list
+	ufdList := make([]unconfirmedFactDisk, 0, len(s.unconfirmedFacts))
+	for confirmationId, f := range s.unconfirmedFacts {
+		ufd := unconfirmedFactDisk{
+			confirmationId:  confirmationId,
+			stringifiedFact: f.Stringify(),
+		}
+		ufdList = append(ufdList, ufd)
+	}
+
+	return json.Marshal(&ufdList)
+}
+
+// unmarshalConfirmedFacts is a function which deserializes the data from storage
+// into a structure matching the confirmedFacts map.
+func (s *Store) unmarshalConfirmedFacts(data []byte) (map[fact.Fact]struct{}, error) {
+	// Unmarshal into list
+	var fStrings []string
+	err := json.Unmarshal(data, &fStrings)
+	if err != nil {
+		return nil, err
+	}
+
+	// Deserialize the list into a map
+	confirmedFacts := make(map[fact.Fact]struct{}, 0)
+	for _, fStr := range fStrings {
+		f, err := fact.UnstringifyFact(fStr)
+		if err != nil {
+			return nil, errors.WithMessage(err, malformedFactErr)
+		}
+
+		confirmedFacts[f] = struct{}{}
+	}
+
+	return confirmedFacts, nil
+}
+
+// unmarshalUnconfirmedFacts is a function which deserializes the data from storage
+// into a structure matching the unconfirmedFacts map.
+func (s *Store) unmarshalUnconfirmedFacts(data []byte) (map[string]fact.Fact, error) {
+	// Unmarshal into list
+	var ufdList []unconfirmedFactDisk
+	err := json.Unmarshal(data, &ufdList)
+	if err != nil {
+		return nil, err
+	}
+
+	// Deserialize the list into a map
+	unconfirmedFacts := make(map[string]fact.Fact, 0)
+	for _, ufd := range ufdList {
+		f, err := fact.UnstringifyFact(ufd.stringifiedFact)
+		if err != nil {
+			return nil, errors.WithMessage(err, malformedFactErr)
+		}
+
+		unconfirmedFacts[ufd.confirmationId] = f
+	}
+
+	return unconfirmedFacts, nil
+}
diff --git a/storage/ud/store_test.go b/storage/ud/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..135348eed9bfa97db73931db19d81fc1a69b3cff
--- /dev/null
+++ b/storage/ud/store_test.go
@@ -0,0 +1,103 @@
+package ud
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"reflect"
+	"testing"
+)
+
+func TestLoadStore(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	receivedStore, err := LoadStore(kv)
+	if err != nil {
+		t.Fatalf("LoadStore() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedStore, receivedStore) {
+		t.Errorf("LoadStore() returned incorrect Store."+
+			"\nexpected: %#v\nreceived: %#v", expectedStore,
+			receivedStore)
+
+	}
+
+}
+
+func TestStore_MarshalUnmarshal_ConfirmedFacts(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	data, err := expectedStore.kv.Get(confirmedFactKey, version)
+	if err != nil {
+		t.Errorf("Get() error when getting Store from KV: %v", err)
+	}
+
+	expectedData, err := expectedStore.marshalConfirmedFacts()
+	if err != nil {
+		t.Fatalf("marshalConfirmedFact error: %+v", err)
+	}
+
+	if !bytes.Equal(expectedData, data.Data) {
+		t.Errorf("NewStore() returned incorrect Store."+
+			"\nexpected: %+v\nreceived: %+v", expectedData,
+			data.Data)
+	}
+
+	recieved, err := expectedStore.unmarshalConfirmedFacts(data.Data)
+	if err != nil {
+		t.Fatalf("unmarshalUnconfirmedFacts error: %v", err)
+	}
+
+	if !reflect.DeepEqual(recieved, expectedStore.confirmedFacts) {
+		t.Fatalf("Marshal/Unmarshal did not produce identical data"+
+			"\nExpected: %v "+
+			"\nReceived: %v", expectedStore.confirmedFacts, recieved)
+	}
+}
+
+func TestStore_MarshalUnmarshal_UnconfirmedFacts(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	expectedStore, err := NewStore(kv)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	data, err := expectedStore.kv.Get(unconfirmedFactKey, version)
+	if err != nil {
+		t.Errorf("Get() error when getting Store from KV: %v", err)
+	}
+
+	expectedData, err := expectedStore.marshalUnconfirmedFacts()
+	if err != nil {
+		t.Fatalf("marshalConfirmedFact error: %+v", err)
+	}
+
+	if !bytes.Equal(expectedData, data.Data) {
+		t.Errorf("NewStore() returned incorrect Store."+
+			"\nexpected: %+v\nreceived: %+v", expectedData,
+			data.Data)
+	}
+
+	recieved, err := expectedStore.unmarshalUnconfirmedFacts(data.Data)
+	if err != nil {
+		t.Fatalf("unmarshalUnconfirmedFacts error: %v", err)
+	}
+
+	if !reflect.DeepEqual(recieved, expectedStore.unconfirmedFacts) {
+		t.Fatalf("Marshal/Unmarshal did not produce identical data"+
+			"\nExpected: %v "+
+			"\nReceived: %v", expectedStore.unconfirmedFacts, recieved)
+	}
+}
diff --git a/storage/user.go b/storage/user.go
index 313741471ee5e3fefa12349d2e9b441352b983e1..e8f89909279f4496ea625b10f865fe4391d90f6c 100644
--- a/storage/user.go
+++ b/storage/user.go
@@ -22,8 +22,6 @@ func (s *Session) GetUser() user.User {
 		ReceptionSalt:         copySlice(ci.GetReceptionSalt()),
 		ReceptionRSA:          ci.GetReceptionRSA(),
 		Precanned:             ci.IsPrecanned(),
-		CmixDhPrivateKey:      s.cmix.GetDHPrivateKey().DeepCopy(),
-		CmixDhPublicKey:       s.cmix.GetDHPublicKey().DeepCopy(),
 		E2eDhPrivateKey:       s.e2e.GetDHPrivateKey().DeepCopy(),
 		E2eDhPublicKey:        s.e2e.GetDHPublicKey().DeepCopy(),
 	}
diff --git a/ud/addFact.go b/ud/addFact.go
index 1fc1a15f0a24fd327445d4d8220517863cd5eda6..81734a2bdca3e1e0f73c3943b6330fede685228b 100644
--- a/ud/addFact.go
+++ b/ud/addFact.go
@@ -75,6 +75,10 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (strin
 		confirmationID = response.ConfirmationID
 	}
 
+	err = m.storage.GetUd().StoreUnconfirmedFact(confirmationID, f)
+	if err != nil {
+		return "", errors.WithMessagef(err, "Failed to store unconfirmed fact %v", f.Fact)
+	}
 	// Return the error
 	return confirmationID, err
 }
diff --git a/ud/addFact_test.go b/ud/addFact_test.go
index ba6db2dc18596d476676666e7a4eaed4da0feabc..7fecfc4d6d59a3a15666e662cbb149af52c6795d 100644
--- a/ud/addFact_test.go
+++ b/ud/addFact_test.go
@@ -2,6 +2,7 @@ package ud
 
 import (
 	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/primitives/fact"
@@ -50,6 +51,7 @@ func TestAddFact(t *testing.T) {
 		net:        newTestNetworkManager(t),
 		privKey:    cpk,
 		registered: &isReg,
+		storage:    storage.InitTestingSession(t),
 	}
 
 	// Create our test fact
diff --git a/ud/confirmFact.go b/ud/confirmFact.go
index b7c4294e2c026e35da1ae70b7739fe6feea0c0bd..af71eb7a43cc811839e360420dbdca96c7a5e812 100644
--- a/ud/confirmFact.go
+++ b/ud/confirmFact.go
@@ -40,5 +40,14 @@ func (m *Manager) confirmFact(confirmationID, code string, comm confirmFactComm)
 		Code:           code,
 	}
 	_, err = comm.SendConfirmFact(host, msg)
-	return err
+	if err != nil {
+		return err
+	}
+
+	err = m.storage.GetUd().ConfirmFact(confirmationID)
+	if err != nil {
+		return errors.WithMessagef(err, "Failed to confirm fact in storage with confirmation ID: %q", confirmationID)
+	}
+
+	return nil
 }
diff --git a/ud/confirmFact_test.go b/ud/confirmFact_test.go
index 9fe29b0a4491e6df330595d27d570e98d87f7fb9..4b9789b40d07d4ea3689d2caa5c90752ca1fea46 100644
--- a/ud/confirmFact_test.go
+++ b/ud/confirmFact_test.go
@@ -1,8 +1,10 @@
 package ud
 
 import (
+	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/comms/messages"
 	"reflect"
@@ -32,6 +34,7 @@ func TestManager_confirmFact(t *testing.T) {
 		comms:      comms,
 		net:        newTestNetworkManager(t),
 		registered: &isReg,
+		storage:    storage.InitTestingSession(t),
 	}
 
 	c := &testComm{}
@@ -41,6 +44,12 @@ func TestManager_confirmFact(t *testing.T) {
 		Code:           "1234",
 	}
 
+	// Set up store for expected state
+	err = m.storage.GetUd().StoreUnconfirmedFact(expectedRequest.ConfirmationID, fact.Fact{})
+	if err != nil {
+		t.Fatalf("StoreUnconfirmedFact error: %v", err)
+	}
+
 	err = m.confirmFact(expectedRequest.ConfirmationID, expectedRequest.Code, c)
 	if err != nil {
 		t.Errorf("confirmFact() returned an error: %+v", err)
diff --git a/ud/lookup.go b/ud/lookup.go
index 2dc2dd2a8724227423df2ff028809746aa49cc8f..1769b5f34d5cca735eb96097f34e8a3f2ed70e42 100644
--- a/ud/lookup.go
+++ b/ud/lookup.go
@@ -23,7 +23,31 @@ type lookupCallback func(contact.Contact, error)
 // system or returns by the timeout.
 func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error {
 	jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout)
+	return m.lookup(uid, callback, timeout)
+}
+
+// BatchLookup performs a Lookup operation on a list of user IDs.
+// The lookup performs a callback on each lookup on the returned contact object
+// constructed from the response.
+func (m *Manager) BatchLookup(uids []*id.ID, callback lookupCallback, timeout time.Duration) {
+	jww.INFO.Printf("ud.BatchLookup(%s, %s)", uids, timeout)
+
+	for _, uid := range uids {
+		go func(localUid *id.ID) {
+			err := m.lookup(localUid, callback, timeout)
+			if err != nil {
+				jww.WARN.Printf("Failed batch lookup on user %s: %v", localUid, err)
+			}
+		}(uid)
+	}
+
+	return
+}
 
+// lookup is a helper function which sends a lookup request to the user discovery
+// service. It will construct a contact object off of the returned public key.
+// The callback will be called on that contact object.
+func (m *Manager) lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error {
 	// Build the request and marshal it
 	request := &LookupSend{UserID: uid.Marshal()}
 	requestMarshaled, err := proto.Marshal(request)
@@ -50,6 +74,9 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat
 	return nil
 }
 
+// lookupResponseProcess processes the lookup response. The returned public key
+// and the user ID will be constructed into a contact object. The contact object
+// will be passed into the callback.
 func (m *Manager) lookupResponseProcess(uid *id.ID, callback lookupCallback,
 	payload []byte, err error) {
 	if err != nil {
diff --git a/ud/manager.go b/ud/manager.go
index b271c0d90a635e3927728a9337b2902bb8a58be4..4306743ff91bdf0dd6b2d36ab4e8064735cd75ee 100644
--- a/ud/manager.go
+++ b/ud/manager.go
@@ -12,6 +12,7 @@ import (
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/crypto/signature/rsa"
 	"gitlab.com/xx_network/primitives/id"
@@ -150,6 +151,29 @@ func (m *Manager) UnsetAlternativeUserDiscovery() error {
 	return nil
 }
 
+// BackUpMissingFacts adds a registered fact to the Store object. It can take in both an
+// email and a phone number. One or the other may be nil, however both is considered
+// an error. It checks for the proper fact type for the associated fact.
+// Any other fact.FactType is not accepted and returns an error and nothing is backed up.
+// If you attempt to back up a fact type that has already been backed up,
+// an error will be returned and nothing will be backed up.
+// Otherwise, it adds the fact and returns whether the Store saved successfully.
+func (m *Manager) BackUpMissingFacts(email, phone fact.Fact) error {
+	return m.storage.GetUd().BackUpMissingFacts(email, phone)
+}
+
+// GetFacts returns a list of fact.Fact objects that exist within the
+// Store's registeredFacts map.
+func (m *Manager) GetFacts() []fact.Fact {
+	return m.storage.GetUd().GetFacts()
+}
+
+// GetStringifiedFacts returns a list of stringified facts from the Store's
+// registeredFacts map.
+func (m *Manager) GetStringifiedFacts() []string {
+	return m.storage.GetUd().GetStringifiedFacts()
+}
+
 // getHost returns the current UD host for the UD ID found in the NDF. If the
 // host does not exist, then it is added and returned
 func (m *Manager) getHost() (*connect.Host, error) {
diff --git a/ud/remove.go b/ud/remove.go
index 67f6721773baed94402963fad95726dbe34e9365..85547373fc064da7e424640683af4cc74663ae76 100644
--- a/ud/remove.go
+++ b/ud/remove.go
@@ -61,9 +61,12 @@ func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error {
 
 	// Send the message
 	_, err = rFC.SendRemoveFact(host, &remFactMsg)
+	if err != nil {
+		return err
+	}
 
-	// Return the error
-	return err
+	// Remove from storage
+	return m.storage.GetUd().DeleteFact(fact)
 }
 
 type removeUserComms interface {
diff --git a/ud/remove_test.go b/ud/remove_test.go
index 5705a843a4a182b3ad959243f4b94092551803de..ecf0487d68b27047953d245e5ce2710f0ec6cc45 100644
--- a/ud/remove_test.go
+++ b/ud/remove_test.go
@@ -1,6 +1,7 @@
 package ud
 
 import (
+	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/primitives/fact"
@@ -39,6 +40,7 @@ func TestRemoveFact(t *testing.T) {
 		net:        newTestNetworkManager(t),
 		privKey:    cpk,
 		registered: &isReg,
+		storage:    storage.InitTestingSession(t),
 		myID:       &id.ID{},
 	}
 
@@ -47,6 +49,16 @@ func TestRemoveFact(t *testing.T) {
 		T:    2,
 	}
 
+	// Set up storage for expected state
+	confirmId := "test"
+	if err = m.storage.GetUd().StoreUnconfirmedFact(confirmId, f); err != nil {
+		t.Fatalf("StoreUnconfirmedFact error: %v", err)
+	}
+
+	if err = m.storage.GetUd().ConfirmFact(confirmId); err != nil {
+		t.Fatalf("ConfirmFact error: %v", err)
+	}
+
 	tRFC := testRFC{}
 
 	err = m.removeFact(f, &tRFC)