From 1a892678067a8056749aa77fdbd4e0eea6f03c20 Mon Sep 17 00:00:00 2001
From: Jono Wenger <jono@elixxir.io>
Date: Wed, 13 Apr 2022 18:38:48 +0000
Subject: [PATCH] Fix backup package

---
 auth/confirm.go                             |   2 +-
 auth/receivedConfirm.go                     |   4 +-
 auth/state.go                               |  28 +--
 backup/backup.go                            | 176 +++++++++---------
 backup/backup_test.go                       | 187 ++++++++++----------
 interfaces/backup.go => backup/container.go |  10 +-
 backup/utils_test.go                        | 156 ++++++++++++++++
 7 files changed, 368 insertions(+), 195 deletions(-)
 rename interfaces/backup.go => backup/container.go (83%)
 create mode 100644 backup/utils_test.go

diff --git a/auth/confirm.go b/auth/confirm.go
index e523715c7..ee89b8f18 100644
--- a/auth/confirm.go
+++ b/auth/confirm.go
@@ -126,7 +126,7 @@ func (s *state) confirm(partner contact.Contact, serviceTag string) (
 			s.event.Report(10, "Auth", "SendConfirmError", em)
 		}
 
-		//todo: s.backupTrigger("confirmed authenticated channel")
+		s.backupTrigger("confirmed authenticated channel")
 
 		jww.INFO.Printf("Confirming Auth from %s to %s, msgDigest: %s",
 			partner.ID, s.e2e.GetReceptionID(),
diff --git a/auth/receivedConfirm.go b/auth/receivedConfirm.go
index 1990fd5bd..07e12df9b 100644
--- a/auth/receivedConfirm.go
+++ b/auth/receivedConfirm.go
@@ -5,9 +5,9 @@ import (
 	"fmt"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/auth/store"
-	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/cmix/identity/receptionID"
 	"gitlab.com/elixxir/client/cmix/message"
+	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/e2e/ratchet/partner/session"
 	"gitlab.com/elixxir/crypto/contact"
 	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
@@ -95,7 +95,7 @@ func (rcs *receivedConfirmService) Process(msg format.Message,
 			"%s : %+v", rcs.GetPartner(), receptionID.Source, err)
 	}
 
-	//todo: trigger backup
+	rcs.s.backupTrigger("received confirmation from request")
 
 	// remove the service used for notifications of the confirm
 	state.net.DeleteService(receptionID.Source, rcs.notificationsService, nil)
diff --git a/auth/state.go b/auth/state.go
index 7ced7eb8d..dfb6cb03c 100644
--- a/auth/state.go
+++ b/auth/state.go
@@ -33,9 +33,7 @@ import (
 type state struct {
 	callbacks Callbacks
 
-	// net cmix.Client
 	net cmixClient
-	// e2e e2e.Handler
 	e2e e2eHandler
 	rng *fastRNG.StreamGenerator
 
@@ -43,6 +41,8 @@ type state struct {
 	event event.Manager
 
 	params Param
+
+	backupTrigger func(reason string)
 }
 
 type cmixClient interface {
@@ -101,9 +101,10 @@ type Callbacks interface {
 //   NewState and use GetDefaultTemporaryParams() for the parameters
 func NewState(kv *versioned.KV, net cmix.Client, e2e e2e.Handler,
 	rng *fastRNG.StreamGenerator, event event.Manager, params Param,
-	callbacks Callbacks) (State, error) {
+	callbacks Callbacks, backupTrigger func(reason string)) (State, error) {
 	kv = kv.Prefix(makeStorePrefix(e2e.GetReceptionID()))
-	return NewStateLegacy(kv, net, e2e, rng, event, params, callbacks)
+	return NewStateLegacy(
+		kv, net, e2e, rng, event, params, callbacks, backupTrigger)
 }
 
 // NewStateLegacy loads the auth state or creates new auth state if one cannot be
@@ -113,17 +114,16 @@ func NewState(kv *versioned.KV, net cmix.Client, e2e e2e.Handler,
 // Otherwise, acts the same as NewState
 func NewStateLegacy(kv *versioned.KV, net cmix.Client, e2e e2e.Handler,
 	rng *fastRNG.StreamGenerator, event event.Manager, params Param,
-	callbacks Callbacks) (State, error) {
+	callbacks Callbacks, backupTrigger func(reason string)) (State, error) {
 
 	s := &state{
-		callbacks: callbacks,
-
-		net: net,
-		e2e: e2e,
-		rng: rng,
-
-		params: params,
-		event:  event,
+		callbacks:     callbacks,
+		net:           net,
+		e2e:           e2e,
+		rng:           rng,
+		event:         event,
+		params:        params,
+		backupTrigger: backupTrigger,
 	}
 
 	// create the store
@@ -131,7 +131,7 @@ func NewStateLegacy(kv *versioned.KV, net cmix.Client, e2e e2e.Handler,
 	s.store, err = store.NewOrLoadStore(kv, e2e.GetGroup(),
 		&sentRequestHandler{s: s})
 
-	//register services
+	// register services
 	net.AddService(e2e.GetReceptionID(), message.Service{
 		Identifier: e2e.GetReceptionID()[:],
 		Tag:        params.RequestTag,
diff --git a/backup/backup.go b/backup/backup.go
index ea9667c03..291348fa6 100644
--- a/backup/backup.go
+++ b/backup/backup.go
@@ -5,34 +5,30 @@
 // 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 (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
 	"sync"
+	"time"
 
 	"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"
+	"gitlab.com/xx_network/crypto/signature/rsa"
 )
 
 // Error messages.
 const (
-	// initializeBackup
+	// InitializeBackup
 	errSavePassword      = "failed to save password: %+v"
 	errSaveKeySaltParams = "failed to save key, salt, and params: %+v"
 
-	// resumeBackup
+	// ResumeBackup
 	errLoadPassword = "backup not initialized: load user password failed: %+v"
 
 	// Backup.StopBackup
@@ -46,15 +42,43 @@ type Backup struct {
 	// Callback that is called with the encrypted backup when triggered
 	updateBackupCb UpdateBackupFn
 
-	mux sync.RWMutex
+	container *Container
+
+	jsonParams string
 
 	// Client structures
-	client          *api.Client
-	store           *storage.Session
-	backupContainer *interfaces.BackupContainer
-	rng             *fastRNG.StreamGenerator
+	e2e     E2e
+	session Session
+	ud      UserDiscovery
+	kv      *versioned.KV
+	rng     *fastRNG.StreamGenerator
 
-	jsonParams string
+	mux sync.RWMutex
+}
+
+// E2e is a subset of functions from the interface e2e.Handler.
+type E2e interface {
+	GetAllPartnerIDs() []*id.ID
+	GetHistoricalDHPubkey() *cyclic.Int
+	GetHistoricalDHPrivkey() *cyclic.Int
+}
+
+// Session is a subset of functions from the interface storage.Session.
+type Session interface {
+	GetRegCode() (string, error)
+	GetTransmissionID() *id.ID
+	GetTransmissionSalt() []byte
+	GetReceptionID() *id.ID
+	GetReceptionSalt() []byte
+	GetReceptionRSA() *rsa.PrivateKey
+	GetTransmissionRSA() *rsa.PrivateKey
+	GetTransmissionRegistrationValidationSignature() []byte
+	GetReceptionRegistrationValidationSignature() []byte
+	GetRegistrationTimestamp() time.Time
+}
+
+type UserDiscovery interface {
+	GetFacts() fact.FactList
 }
 
 // UpdateBackupFn is the callback that encrypted backup data is returned on
@@ -68,27 +92,20 @@ type UpdateBackupFn func(encryptedBackup []byte)
 // 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) {
+	container *Container, e2e E2e, session Session, ud UserDiscovery,
+	kv *versioned.KV, rng *fastRNG.StreamGenerator) (*Backup, error) {
 	b := &Backup{
-		updateBackupCb:  updateBackupCb,
-		client:          c,
-		store:           store,
-		backupContainer: backupContainer,
-		rng:             rng,
+		updateBackupCb: updateBackupCb,
+		container:      container,
+		e2e:            e2e,
+		session:        session,
+		ud:             ud,
+		kv:             kv,
+		rng:            rng,
 	}
 
 	// Save password to storage
-	err := savePassword(password, b.store.GetKV())
+	err := savePassword(password, b.kv)
 	if err != nil {
 		return nil, errors.Errorf(errSavePassword, err)
 	}
@@ -105,15 +122,15 @@ func initializeBackup(password string, updateBackupCb UpdateBackupFn,
 	key := backup.DeriveKey(password, salt, params)
 
 	// Save key, salt, and parameters to storage
-	err = saveBackup(key, salt, params, b.store.GetKV())
+	err = saveBackup(key, salt, params, b.kv)
 	if err != nil {
 		return nil, errors.Errorf(errSaveKeySaltParams, err)
 	}
 
 	// Setting backup trigger in client
-	b.backupContainer.SetBackup(b.TriggerBackup)
+	b.container.SetBackup(b.TriggerBackup)
 
-	b.TriggerBackup("initializeBackup")
+	b.TriggerBackup("InitializeBackup")
 	jww.INFO.Print("Initialized backup with new user key.")
 
 	return b, nil
@@ -122,32 +139,27 @@ func initializeBackup(password string, updateBackupCb UpdateBackupFn,
 // 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,
+func ResumeBackup(updateBackupCb UpdateBackupFn, container *Container,
+	e2e E2e, session Session, ud UserDiscovery, kv *versioned.KV,
 	rng *fastRNG.StreamGenerator) (*Backup, error) {
-	_, err := loadPassword(store.GetKV())
+	_, err := loadPassword(kv)
 	if err != nil {
 		return nil, errors.Errorf(errLoadPassword, err)
 	}
 
 	b := &Backup{
-		updateBackupCb:  updateBackupCb,
-		client:          c,
-		store:           store,
-		backupContainer: backupContainer,
-		rng:             rng,
-		jsonParams:      loadJson(store.GetKV()),
+		updateBackupCb: updateBackupCb,
+		container:      container,
+		jsonParams:     loadJson(kv),
+		e2e:            e2e,
+		session:        session,
+		ud:             ud,
+		kv:             kv,
+		rng:            rng,
 	}
 
 	// Setting backup trigger in client
-	b.backupContainer.SetBackup(b.TriggerBackup)
+	b.container.SetBackup(b.TriggerBackup)
 
 	jww.INFO.Print("Resumed backup with password loaded from storage.")
 
@@ -181,7 +193,7 @@ func (b *Backup) TriggerBackup(reason string) {
 	b.mux.RLock()
 	defer b.mux.RUnlock()
 
-	key, salt, params, err := loadBackup(b.store.GetKV())
+	key, salt, params, err := loadBackup(b.kv)
 	if err != nil {
 		jww.ERROR.Printf("Backup Failed: could not load key, salt, and "+
 			"parameters for encrypting backup from storage: %+v", err)
@@ -217,7 +229,7 @@ func (b *Backup) AddJson(newJson string) {
 
 	if newJson != b.jsonParams {
 		b.jsonParams = newJson
-		if err := storeJson(newJson, b.store.GetKV()); err != nil {
+		if err := storeJson(newJson, b.kv); err != nil {
 			jww.FATAL.Panicf("Failed to store json: %+v", err)
 		}
 		go b.TriggerBackup("New Json")
@@ -231,12 +243,12 @@ func (b *Backup) StopBackup() error {
 	defer b.mux.Unlock()
 	b.updateBackupCb = nil
 
-	err := deletePassword(b.store.GetKV())
+	err := deletePassword(b.kv)
 	if err != nil {
 		return errors.Errorf(errDeletePassword, err)
 	}
 
-	err = deleteBackup(b.store.GetKV())
+	err = deleteBackup(b.kv)
 	if err != nil {
 		return errors.Errorf(errDeleteCrypto, err)
 	}
@@ -268,42 +280,38 @@ func (b *Backup) assembleBackup() backup.Backup {
 		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 timestamp
+	bu.RegistrationTimestamp = b.session.GetRegistrationTimestamp().UnixNano()
 
-	// get registration code; ignore the error because if there is no
+	// Get registration code; ignore the error because if there is no
 	// registration, then an empty string is returned
-	bu.RegistrationCode, _ = b.store.GetRegCode()
+	bu.RegistrationCode, _ = b.session.GetRegCode()
 
-	// get transmission identity
+	// Get transmission identity
 	bu.TransmissionIdentity = backup.TransmissionIdentity{
-		RSASigningPrivateKey: u.TransmissionRSA,
-		RegistrarSignature:   su.GetTransmissionRegistrationValidationSignature(),
-		Salt:                 u.TransmissionSalt,
-		ComputedID:           u.TransmissionID,
+		RSASigningPrivateKey: b.session.GetTransmissionRSA(),
+		RegistrarSignature:   b.session.GetTransmissionRegistrationValidationSignature(),
+		Salt:                 b.session.GetTransmissionSalt(),
+		ComputedID:           b.session.GetTransmissionID(),
 	}
 
-	// get reception identity
+	// 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,
+		RSASigningPrivateKey: b.session.GetReceptionRSA(),
+		RegistrarSignature:   b.session.GetReceptionRegistrationValidationSignature(),
+		Salt:                 b.session.GetReceptionSalt(),
+		ComputedID:           b.session.GetReceptionID(),
+		DHPrivateKey:         b.e2e.GetHistoricalDHPrivkey(),
+		DHPublicKey:          b.e2e.GetHistoricalDHPubkey(),
 	}
 
-	// get facts
-	bu.UserDiscoveryRegistration.FactList = b.store.GetUd().GetFacts()
+	// Get facts
+	bu.UserDiscoveryRegistration.FactList = b.ud.GetFacts()
 
-	// get contacts
-	bu.Contacts.Identities = b.store.E2e().GetPartners()
+	// Get contacts
+	bu.Contacts.Identities = b.e2e.GetAllPartnerIDs()
 
-	//add the memoized json params
+	// Add the memoized json params
 	bu.JSONParams = b.jsonParams
 
 	return bu
diff --git a/backup/backup_test.go b/backup/backup_test.go
index 434c76e78..076fc2d8c 100644
--- a/backup/backup_test.go
+++ b/backup/backup_test.go
@@ -9,29 +9,30 @@ package backup
 
 import (
 	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
 	"reflect"
 	"strings"
 	"testing"
 	"time"
 
-	"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"
 )
 
-// Tests that Backup.initializeBackup returns a new Backup with a copy of the
+// Tests that Backup.InitializeBackup returns a new Backup with a copy of the
 // key and the callback.
-func Test_initializeBackup(t *testing.T) {
+func Test_InitializeBackup(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG)
 	cbChan := make(chan []byte, 2)
 	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))
+	b, err := InitializeBackup(expectedPassword, cb, &Container{}, newMockE2e(t),
+		newMockSession(t), newMockUserDiscovery(), kv, rngGen)
 	if err != nil {
-		t.Errorf("initializeBackup returned an error: %+v", err)
+		t.Errorf("InitializeBackup returned an error: %+v", err)
 	}
 
 	select {
@@ -41,7 +42,7 @@ func Test_initializeBackup(t *testing.T) {
 	}
 
 	// Check that the correct password is in storage
-	loadedPassword, err := loadPassword(b.store.GetKV())
+	loadedPassword, err := loadPassword(b.kv)
 	if err != nil {
 		t.Errorf("Failed to load password: %+v", err)
 	}
@@ -51,7 +52,7 @@ func Test_initializeBackup(t *testing.T) {
 	}
 
 	// Check that the key, salt, and params were saved to storage
-	key, salt, p, err := loadBackup(b.store.GetKV())
+	key, salt, p, err := loadBackup(b.kv)
 	if err != nil {
 		t.Errorf("Failed to load key, salt, and params: %+v", err)
 	}
@@ -80,17 +81,17 @@ func Test_initializeBackup(t *testing.T) {
 	}
 }
 
-// Initialises a new backup and then tests that Backup.resumeBackup overwrites
-// the callback but keeps the password.
-func Test_resumeBackup(t *testing.T) {
+// Initialises a new backup and then tests that ResumeBackup overwrites the
+// callback but keeps the password.
+func Test_ResumeBackup(t *testing.T) {
 	// Start the first backup
+	kv := versioned.NewKV(make(ekv.Memstore))
+	rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG)
 	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))
+	b, err := InitializeBackup(expectedPassword, cb1, &Container{},
+		newMockE2e(t), newMockSession(t), newMockUserDiscovery(), kv, rngGen)
 	if err != nil {
 		t.Errorf("Failed to initialize new Backup: %+v", err)
 	}
@@ -102,7 +103,7 @@ func Test_resumeBackup(t *testing.T) {
 	}
 
 	// get key and salt to compare to later
-	key1, salt1, _, err := loadBackup(b.store.GetKV())
+	key1, salt1, _, err := loadBackup(b.kv)
 	if err != nil {
 		t.Errorf("Failed to load key, salt, and params from newly "+
 			"initialized backup: %+v", err)
@@ -111,14 +112,14 @@ func Test_resumeBackup(t *testing.T) {
 	// 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))
+	b2, err := ResumeBackup(cb2, &Container{}, newMockE2e(t), newMockSession(t),
+		newMockUserDiscovery(), kv, rngGen)
 	if err != nil {
-		t.Errorf("resumeBackup returned an error: %+v", err)
+		t.Errorf("ResumeBackup returned an error: %+v", err)
 	}
 
 	// Check that the correct password is in storage
-	loadedPassword, err := loadPassword(b.store.GetKV())
+	loadedPassword, err := loadPassword(b.kv)
 	if err != nil {
 		t.Errorf("Failed to load password: %+v", err)
 	}
@@ -128,7 +129,7 @@ func Test_resumeBackup(t *testing.T) {
 	}
 
 	// get key, salt, and parameters of resumed backup
-	key2, salt2, _, err := loadBackup(b.store.GetKV())
+	key2, salt2, _, err := loadBackup(b.kv)
 	if err != nil {
 		t.Errorf("Failed to load key, salt, and params from resumed "+
 			"backup: %+v", err)
@@ -158,14 +159,16 @@ func Test_resumeBackup(t *testing.T) {
 	}
 }
 
-// Error path: Tests that Backup.resumeBackup returns an error if no password is
+// Error path: Tests that ResumeBackup returns an error if no password is
 // present in storage.
-func Test_resumeBackup_NoKeyError(t *testing.T) {
+func Test_ResumeBackup_NoKeyError(t *testing.T) {
 	expectedErr := strings.Split(errLoadPassword, "%")[0]
-	s := storage.InitTestingSession(t)
-	_, err := resumeBackup(nil, nil, s, &interfaces.BackupContainer{}, nil)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	rngGen := fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG)
+	_, err := ResumeBackup(nil, &Container{}, newMockE2e(t), newMockSession(t),
+		newMockUserDiscovery(), kv, rngGen)
 	if err == nil || !strings.Contains(err.Error(), expectedErr) {
-		t.Errorf("resumeBackup did not return the expected error when no "+
+		t.Errorf("ResumeBackup did not return the expected error when no "+
 			"password is present.\nexpected: %s\nreceived: %+v", expectedErr, err)
 	}
 }
@@ -178,7 +181,7 @@ func TestBackup_TriggerBackup(t *testing.T) {
 	b := newTestBackup("MySuperSecurePassword", cb, t)
 
 	// get password
-	password, err := loadPassword(b.store.GetKV())
+	password, err := loadPassword(b.kv)
 	if err != nil {
 		t.Errorf("Failed to load password from storage: %+v", err)
 	}
@@ -215,7 +218,7 @@ func TestBackup_TriggerBackup_NoKey(t *testing.T) {
 		t.Errorf("backup not called")
 	}
 
-	err := deleteBackup(b.store.GetKV())
+	err := deleteBackup(b.kv)
 	if err != nil {
 		t.Errorf("Failed to delete key, salt, and params: %+v", err)
 	}
@@ -260,13 +263,13 @@ func TestBackup_StopBackup(t *testing.T) {
 	}
 
 	// Make sure password is deleted
-	password, err := loadPassword(b.store.GetKV())
+	password, err := loadPassword(b.kv)
 	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())
+	key, salt, p, err := loadBackup(b.kv)
 	if err == nil || len(key) != 0 || len(salt) != 0 || p != (backup.Params{}) {
 		t.Errorf("Loaded key, salt, and params that should be deleted.")
 	}
@@ -296,77 +299,79 @@ func TestBackup_IsBackupRunning(t *testing.T) {
 
 func TestBackup_AddJson(t *testing.T) {
 	b := newTestBackup("MySuperSecurePassword", nil, t)
-	s := b.store
+	s := b.session.(*mockSession)
+	e2e := b.e2e.(*mockE2e)
 	json := "{'data': {'one': 1}}"
 
-	expectedCollatedBackup := backup.Backup{
-		RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
+	expected := backup.Backup{
+		RegistrationCode:      s.regCode,
+		RegistrationTimestamp: s.registrationTimestamp.UnixNano(),
 		TransmissionIdentity: backup.TransmissionIdentity{
-			RSASigningPrivateKey: s.GetUser().TransmissionRSA,
-			RegistrarSignature:   s.User().GetTransmissionRegistrationValidationSignature(),
-			Salt:                 s.GetUser().TransmissionSalt,
-			ComputedID:           s.GetUser().TransmissionID,
+			RSASigningPrivateKey: s.transmissionRSA,
+			RegistrarSignature:   s.transmissionRegistrationValidationSignature,
+			Salt:                 s.transmissionSalt,
+			ComputedID:           s.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,
+			RSASigningPrivateKey: s.receptionRSA,
+			RegistrarSignature:   s.receptionRegistrationValidationSignature,
+			Salt:                 s.receptionSalt,
+			ComputedID:           s.receptionID,
+			DHPrivateKey:         e2e.historicalDHPrivkey,
+			DHPublicKey:          e2e.historicalDHPubkey,
 		},
 		UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
-			FactList: s.GetUd().GetFacts(),
+			FactList: b.ud.(*mockUserDiscovery).facts,
 		},
-		Contacts:   backup.Contacts{Identities: s.E2e().GetPartners()},
+		Contacts:   backup.Contacts{Identities: e2e.partnerIDs},
 		JSONParams: json,
 	}
 
 	b.AddJson(json)
 
 	collatedBackup := b.assembleBackup()
-	if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
+	if !reflect.DeepEqual(expected, collatedBackup) {
 		t.Errorf("Collated backup does not match expected."+
-			"\nexpected: %+v\nreceived: %+v",
-			expectedCollatedBackup, collatedBackup)
+			"\nexpected: %+v\nreceived: %+v", expected, collatedBackup)
 	}
 }
 
 func TestBackup_AddJson_badJson(t *testing.T) {
 	b := newTestBackup("MySuperSecurePassword", nil, t)
-	s := b.store
+	s := b.session.(*mockSession)
+	e2e := b.e2e.(*mockE2e)
 	json := "abc{'i'm a bad json: 'one': 1'''}}"
 
-	expectedCollatedBackup := backup.Backup{
-		RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
+	expected := backup.Backup{
+		RegistrationCode:      s.regCode,
+		RegistrationTimestamp: s.registrationTimestamp.UnixNano(),
 		TransmissionIdentity: backup.TransmissionIdentity{
-			RSASigningPrivateKey: s.GetUser().TransmissionRSA,
-			RegistrarSignature:   s.User().GetTransmissionRegistrationValidationSignature(),
-			Salt:                 s.GetUser().TransmissionSalt,
-			ComputedID:           s.GetUser().TransmissionID,
+			RSASigningPrivateKey: s.transmissionRSA,
+			RegistrarSignature:   s.transmissionRegistrationValidationSignature,
+			Salt:                 s.transmissionSalt,
+			ComputedID:           s.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,
+			RSASigningPrivateKey: s.receptionRSA,
+			RegistrarSignature:   s.receptionRegistrationValidationSignature,
+			Salt:                 s.receptionSalt,
+			ComputedID:           s.receptionID,
+			DHPrivateKey:         e2e.historicalDHPrivkey,
+			DHPublicKey:          e2e.historicalDHPubkey,
 		},
 		UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
-			FactList: s.GetUd().GetFacts(),
+			FactList: b.ud.(*mockUserDiscovery).facts,
 		},
-		Contacts:   backup.Contacts{Identities: s.E2e().GetPartners()},
+		Contacts:   backup.Contacts{Identities: e2e.partnerIDs},
 		JSONParams: json,
 	}
 
 	b.AddJson(json)
 
 	collatedBackup := b.assembleBackup()
-	if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
+	if !reflect.DeepEqual(expected, collatedBackup) {
 		t.Errorf("Collated backup does not match expected."+
-			"\nexpected: %+v\nreceived: %+v",
-			expectedCollatedBackup, collatedBackup)
+			"\nexpected: %+v\nreceived: %+v", expected, collatedBackup)
 	}
 }
 
@@ -374,47 +379,51 @@ func TestBackup_AddJson_badJson(t *testing.T) {
 // results.
 func TestBackup_assembleBackup(t *testing.T) {
 	b := newTestBackup("MySuperSecurePassword", nil, t)
-	s := b.store
+	s := b.session.(*mockSession)
+	e2e := b.e2e.(*mockE2e)
 
-	expectedCollatedBackup := backup.Backup{
-		RegistrationTimestamp: s.GetUser().RegistrationTimestamp,
+	expected := backup.Backup{
+		RegistrationCode:      s.regCode,
+		RegistrationTimestamp: s.registrationTimestamp.UnixNano(),
 		TransmissionIdentity: backup.TransmissionIdentity{
-			RSASigningPrivateKey: s.GetUser().TransmissionRSA,
-			RegistrarSignature:   s.User().GetTransmissionRegistrationValidationSignature(),
-			Salt:                 s.GetUser().TransmissionSalt,
-			ComputedID:           s.GetUser().TransmissionID,
+			RSASigningPrivateKey: s.transmissionRSA,
+			RegistrarSignature:   s.transmissionRegistrationValidationSignature,
+			Salt:                 s.transmissionSalt,
+			ComputedID:           s.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,
+			RSASigningPrivateKey: s.receptionRSA,
+			RegistrarSignature:   s.receptionRegistrationValidationSignature,
+			Salt:                 s.receptionSalt,
+			ComputedID:           s.receptionID,
+			DHPrivateKey:         e2e.historicalDHPrivkey,
+			DHPublicKey:          e2e.historicalDHPubkey,
 		},
 		UserDiscoveryRegistration: backup.UserDiscoveryRegistration{
-			FactList: s.GetUd().GetFacts(),
+			FactList: b.ud.(*mockUserDiscovery).facts,
 		},
-		Contacts: backup.Contacts{Identities: s.E2e().GetPartners()},
+		Contacts: backup.Contacts{Identities: e2e.partnerIDs},
 	}
 
 	collatedBackup := b.assembleBackup()
 
-	if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) {
+	if !reflect.DeepEqual(expected, collatedBackup) {
 		t.Errorf("Collated backup does not match expected."+
 			"\nexpected: %+v\nreceived: %+v",
-			expectedCollatedBackup, collatedBackup)
+			expected, collatedBackup)
 	}
 }
 
 // newTestBackup creates a new Backup for testing.
 func newTestBackup(password string, cb UpdateBackupFn, t *testing.T) *Backup {
-	b, err := initializeBackup(
+	b, err := InitializeBackup(
 		password,
 		cb,
-		nil,
-		storage.InitTestingSession(t),
-		&interfaces.BackupContainer{},
+		&Container{},
+		newMockE2e(t),
+		newMockSession(t),
+		newMockUserDiscovery(),
+		versioned.NewKV(make(ekv.Memstore)),
 		fastRNG.NewStreamGenerator(1000, 10, csprng.NewSystemRNG),
 	)
 	if err != nil {
diff --git a/interfaces/backup.go b/backup/container.go
similarity index 83%
rename from interfaces/backup.go
rename to backup/container.go
index 559b4b0f8..fd456ea96 100644
--- a/interfaces/backup.go
+++ b/backup/container.go
@@ -5,14 +5,14 @@
 // LICENSE file                                                               //
 ////////////////////////////////////////////////////////////////////////////////
 
-package interfaces
+package backup
 
 import "sync"
 
 type TriggerBackup func(reason string)
 
-// BackupContainer contains the trigger to call to initiate a backup.
-type BackupContainer struct {
+// Container contains the trigger to call to initiate a backup.
+type Container struct {
 	triggerBackup TriggerBackup
 	mux           sync.RWMutex
 }
@@ -22,7 +22,7 @@ type BackupContainer struct {
 // 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) {
+func (bc *Container) TriggerBackup(reason string) {
 	bc.mux.RLock()
 	defer bc.mux.RUnlock()
 	if bc.triggerBackup != nil {
@@ -32,7 +32,7 @@ func (bc *BackupContainer) TriggerBackup(reason string) {
 
 // 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) {
+func (bc *Container) SetBackup(triggerBackup TriggerBackup) {
 	bc.mux.Lock()
 	defer bc.mux.Unlock()
 
diff --git a/backup/utils_test.go b/backup/utils_test.go
new file mode 100644
index 000000000..c16969ff8
--- /dev/null
+++ b/backup/utils_test.go
@@ -0,0 +1,156 @@
+////////////////////////////////////////////////////////////////////////////////
+// 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/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+// Adheres to the E2e interface.
+type mockE2e struct {
+	partnerIDs          []*id.ID
+	historicalDHPubkey  *cyclic.Int
+	historicalDHPrivkey *cyclic.Int
+}
+
+func newMockE2e(t *testing.T) *mockE2e {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(0))
+	return &mockE2e{
+		partnerIDs: []*id.ID{
+			id.NewIdFromString("partner1", id.User, t),
+			id.NewIdFromString("partner2", id.User, t),
+			id.NewIdFromString("partner3", id.User, t),
+		},
+		historicalDHPubkey:  grp.NewInt(45),
+		historicalDHPrivkey: grp.NewInt(46),
+	}
+}
+func (m *mockE2e) GetAllPartnerIDs() []*id.ID          { return m.partnerIDs }
+func (m *mockE2e) GetHistoricalDHPubkey() *cyclic.Int  { return m.historicalDHPubkey }
+func (m *mockE2e) GetHistoricalDHPrivkey() *cyclic.Int { return m.historicalDHPrivkey }
+
+// Adheres to the Session interface.
+type mockSession struct {
+	regCode                                     string
+	transmissionID                              *id.ID
+	transmissionSalt                            []byte
+	receptionID                                 *id.ID
+	receptionSalt                               []byte
+	receptionRSA                                *rsa.PrivateKey
+	transmissionRSA                             *rsa.PrivateKey
+	transmissionRegistrationValidationSignature []byte
+	receptionRegistrationValidationSignature    []byte
+	registrationTimestamp                       time.Time
+}
+
+func newMockSession(t *testing.T) *mockSession {
+	receptionRSA, _ := rsa.LoadPrivateKeyFromPem([]byte(privKey))
+	transmissionRSA, _ := rsa.LoadPrivateKeyFromPem([]byte(privKey))
+
+	return &mockSession{
+		regCode:          "regCode",
+		transmissionID:   id.NewIdFromString("transmission", id.User, t),
+		transmissionSalt: []byte("transmissionSalt"),
+		receptionID:      id.NewIdFromString("reception", id.User, t),
+		receptionSalt:    []byte("receptionSalt"),
+		receptionRSA:     receptionRSA,
+		transmissionRSA:  transmissionRSA,
+		transmissionRegistrationValidationSignature: []byte("transmissionSig"),
+		receptionRegistrationValidationSignature:    []byte("receptionSig"),
+		registrationTimestamp:                       time.Date(2012, 12, 21, 22, 8, 41, 0, time.UTC),
+	}
+
+}
+func (m mockSession) GetRegCode() (string, error)         { return m.regCode, nil }
+func (m mockSession) GetTransmissionID() *id.ID           { return m.transmissionID }
+func (m mockSession) GetTransmissionSalt() []byte         { return m.transmissionSalt }
+func (m mockSession) GetReceptionID() *id.ID              { return m.receptionID }
+func (m mockSession) GetReceptionSalt() []byte            { return m.receptionSalt }
+func (m mockSession) GetReceptionRSA() *rsa.PrivateKey    { return m.receptionRSA }
+func (m mockSession) GetTransmissionRSA() *rsa.PrivateKey { return m.transmissionRSA }
+func (m mockSession) GetTransmissionRegistrationValidationSignature() []byte {
+	return m.transmissionRegistrationValidationSignature
+}
+func (m mockSession) GetReceptionRegistrationValidationSignature() []byte {
+	return m.receptionRegistrationValidationSignature
+}
+func (m mockSession) GetRegistrationTimestamp() time.Time { return m.registrationTimestamp }
+
+// Adheres to the UserDiscovery interface.
+type mockUserDiscovery struct {
+	facts fact.FactList
+}
+
+func newMockUserDiscovery() *mockUserDiscovery {
+	return &mockUserDiscovery{facts: fact.FactList{
+		{"myUserName", fact.Username},
+		{"hello@example.com", fact.Email},
+		{"6175555212", fact.Phone},
+		{"name", fact.Nickname},
+	}}
+}
+func (m mockUserDiscovery) GetFacts() fact.FactList { return m.facts }
+
+const privKey = `-----BEGIN PRIVATE KEY-----
+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
+OaRA5ZbdE7g7vxKRV36jT3wvD7W+
+-----END PRIVATE KEY-----`
-- 
GitLab