diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c4dc10de3ec54dfff8c212fcf76c2e6aac6965aa..11649fc9a614ca8b5734552680536738e675fecb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,8 +8,9 @@ before_script:
   - mkdir -p ~/.ssh
   - chmod 700 ~/.ssh
   - ssh-keyscan -t rsa $GITLAB_SERVER > ~/.ssh/known_hosts
+  - rm -rf ~/.gitconfig
   - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://gitlab.com/"
-  - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://git.xx.network/"
+  - git config --global url."git@$GITLAB_SERVER:".insteadOf "https://git.xx.network/" --add
   - export PATH=$HOME/go/bin:$PATH
 
 stages:
@@ -51,6 +52,7 @@ build:
   except:
     - tags
   script:
+    - make version
     - mkdir -p release
 #    - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' ./...
     - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.linux64 main.go
@@ -80,6 +82,7 @@ bindings-ios:
     - ios
   script:
     - go get -u golang.org/x/mobile/cmd/gomobile@76c259c465ba39f84de7e2751a666612ddca556b
+    - gomobile init
     - gomobile bind -target ios gitlab.com/elixxir/client/bindings
     - ls
     - zip -r iOS.zip Bindings.xcframework
@@ -97,6 +100,7 @@ bindings-android:
     - export ANDROID_HOME=/android-sdk
     - export PATH=$PATH:/android-sdk/platform-tools/:/usr/local/go/bin
     - go get -u golang.org/x/mobile/cmd/gomobile
+    - gomobile init
     - gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings
   artifacts:
     paths:
diff --git a/README.md b/README.md
index ebf490f59a5ab4ac6c8a154785443cff0ed28310..a5041e876b23c787616d2f260830f2aca19de0d3 100644
--- a/README.md
+++ b/README.md
@@ -190,6 +190,9 @@ Flags:
       --unsafe-channel-creation   Turns off the user identity authenticated
                                   channel check, automatically approving
                                   authenticated channels
+      --verboseRoundTracking      Verbose round tracking, keeps track and prints 
+                                  all rounds the client was aware of while running. 
+                                  Defaults to false if not set.
   -v, --logLevel uint             Level of debugging to print (0 = info, 
                                   1 = debug, >1 = trace). (Default info)
       --waitTimeout uint          The number of seconds to wait for messages to
diff --git a/api/client.go b/api/client.go
index b1edca2841c9c349c1443cd6e1f903530265963b..012e980a5d946c5dd632b896a9d26037d9b308bd 100644
--- a/api/client.go
+++ b/api/client.go
@@ -75,7 +75,7 @@ type Client struct {
 func NewClient(ndfJSON, storageDir string, password []byte, registrationCode string) error {
 	jww.INFO.Printf("NewClient(dir: %s)", storageDir)
 	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
-	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG)
 
 	// Parse the NDF
 	def, err := parseNDF(ndfJSON)
@@ -106,7 +106,7 @@ func NewClient(ndfJSON, storageDir string, password []byte, registrationCode str
 func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password []byte) error {
 	jww.INFO.Printf("NewPrecannedClient()")
 	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
-	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG)
 	rngStream := rngStreamGen.GetStream()
 
 	// Parse the NDF
@@ -135,7 +135,7 @@ func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password [
 func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCode string, userIdPrefix string) error {
 	jww.INFO.Printf("NewVanityClient()")
 	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
-	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG)
 	rngStream := rngStreamGen.GetStream()
 
 	// Parse the NDF
@@ -161,7 +161,7 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, registrationCo
 func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) {
 	jww.INFO.Printf("OpenClient()")
 	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
-	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG)
 
 	// Get current client version
 	currentVersion, err := version.ParseVersion(SEMVER)
@@ -611,10 +611,20 @@ func (c *Client) GetPreferredBins(countryCode string) ([]string, error) {
 
 	// Add additional bins in special cases
 	switch bin {
-	case region.Africa:
-		bins = append(bins, region.WesternEurope.String())
+	case region.SouthAndCentralAmerica:
+		bins = append(bins, region.NorthAmerica.String())
 	case region.MiddleEast:
-		bins = append(bins, region.EasternEurope.String())
+		bins = append(bins, region.EasternEurope.String(), region.CentralEurope.String(), region.WesternAsia.String())
+	case region.NorthernAfrica:
+		bins = append(bins, region.WesternEurope.String(), region.CentralEurope.String())
+	case region.SouthernAfrica:
+		bins = append(bins, region.WesternEurope.String(), region.CentralEurope.String())
+	case region.EasternAsia:
+		bins = append(bins, region.WesternAsia.String(), region.Oceania.String(), region.NorthAmerica.String())
+	case region.WesternAsia:
+		bins = append(bins, region.EasternAsia.String(), region.Russia.String(), region.MiddleEast.String())
+	case region.Oceania:
+		bins = append(bins, region.EasternAsia.String(), region.NorthAmerica.String())
 	}
 
 	return bins, nil
diff --git a/api/mnemonic.go b/api/mnemonic.go
index fd3ce7259be248692591a48bc86f745fd0089e7c..9b262da82c81bebcc01fd185adbc5d60a196cb6b 100644
--- a/api/mnemonic.go
+++ b/api/mnemonic.go
@@ -10,10 +10,10 @@ package api
 import (
 	"github.com/pkg/errors"
 	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/crypto/chacha"
 	"gitlab.com/xx_network/crypto/csprng"
 	xxMnemonic "gitlab.com/xx_network/crypto/mnemonic"
 	"gitlab.com/xx_network/primitives/utils"
-	"golang.org/x/crypto/chacha20poly1305"
 	"path/filepath"
 	"strings"
 )
@@ -24,7 +24,7 @@ const mnemonicFile = ".recovery"
 // This encrypted data saved in storage.
 func StoreSecretWithMnemonic(secret []byte, path string) (string, error) {
 	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
-	rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG).GetStream()
+	rng := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG).GetStream()
 
 	// Ensure path is appended by filepath separator "/"
 	if !strings.HasSuffix(path, string(filepath.Separator)) {
@@ -44,7 +44,7 @@ func StoreSecretWithMnemonic(secret []byte, path string) (string, error) {
 	}
 
 	// Encrypt secret with mnemonic as key
-	ciphertext, err := encryptWithMnemonic(secret, decodedMnemonic, rng)
+	ciphertext, err := chacha.Encrypt(decodedMnemonic, secret, rng)
 	if err != nil {
 		return "", errors.Errorf("Failed to encrypt secret with mnemonic: %v", err)
 	}
@@ -87,47 +87,10 @@ func LoadSecretWithMnemonic(mnemonic, path string) (secret []byte, err error) {
 	}
 
 	// Decrypt the stored secret
-	secret, err = decryptWithMnemonic(data, decodedMnemonic)
+	secret, err = chacha.Decrypt(decodedMnemonic, data)
 	if err != nil {
 		return nil, errors.Errorf("Failed to decrypt secret: %v", err)
 	}
 
 	return secret, nil
 }
-
-// encryptWithMnemonic is a helper function which encrypts the given secret
-// using the mnemonic as the key.
-func encryptWithMnemonic(data, decodedMnemonic []byte,
-	rng csprng.Source) (ciphertext []byte, error error) {
-	chaCipher, err := chacha20poly1305.NewX(decodedMnemonic[:])
-	if err != nil {
-		return nil, errors.Errorf("Failed to initalize encryption algorithm: %v", err)
-	}
-
-	// Generate the nonce
-	nonce := make([]byte, chaCipher.NonceSize())
-	nonce, err = csprng.Generate(chaCipher.NonceSize(), rng)
-	if err != nil {
-		return nil, errors.Errorf("Failed to generate nonce: %v", err)
-	}
-
-	ciphertext = chaCipher.Seal(nonce, nonce, data, nil)
-	return ciphertext, nil
-}
-
-// decryptWithMnemonic is a helper function which decrypts the secret
-// from storage, using the mnemonic as the key.
-func decryptWithMnemonic(data, decodedMnemonic []byte) ([]byte, error) {
-	chaCipher, err := chacha20poly1305.NewX(decodedMnemonic[:])
-	if err != nil {
-		return nil, errors.Errorf("Failed to initalize encryption algorithm: %v", err)
-	}
-
-	nonceLen := chaCipher.NonceSize()
-	nonce, ciphertext := data[:nonceLen], data[nonceLen:]
-	plaintext, err := chaCipher.Open(nil, nonce, ciphertext, nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "Cannot decrypt with password!")
-	}
-	return plaintext, nil
-}
diff --git a/api/mnemonic_test.go b/api/mnemonic_test.go
index 66b2f13dfc8e3f297be051f1f58206c94d22c399..a56a73b1f524018f686af392478f89b7dc2d1dc9 100644
--- a/api/mnemonic_test.go
+++ b/api/mnemonic_test.go
@@ -38,41 +38,6 @@ func TestStoreSecretWithMnemonic(t *testing.T) {
 
 }
 
-func TestEncryptDecryptMnemonic(t *testing.T) {
-	prng := NewPrng(32)
-
-	// Generate a test mnemonic
-	testMnemonic, err := xxMnemonic.GenerateMnemonic(prng, 32)
-	if err != nil {
-		t.Fatalf("GenerateMnemonic error: %v", err)
-	}
-
-	decodedMnemonic, err := xxMnemonic.DecodeMnemonic(testMnemonic)
-	if err != nil {
-		t.Fatalf("DecodeMnemonic error: %v", err)
-	}
-
-	secret := []byte("test123")
-
-	// Encrypt the secret
-	ciphertext, err := encryptWithMnemonic(secret, decodedMnemonic, prng)
-	if err != nil {
-		t.Fatalf("encryptWithMnemonic error: %v", err)
-	}
-
-	// Decrypt the secret
-	received, err := decryptWithMnemonic(ciphertext, decodedMnemonic)
-	if err != nil {
-		t.Fatalf("decryptWithMnemonic error: %v", err)
-	}
-
-	// Test if secret matches decrypted data
-	if !bytes.Equal(received, secret) {
-		t.Fatalf("Decrypted data does not match original plaintext."+
-			"\n\tExpected: %v\n\tReceived: %v", secret, received)
-	}
-}
-
 func TestLoadSecretWithMnemonic(t *testing.T) {
 	secret := []byte("test123")
 	storageDir := "ignore.1"
diff --git a/api/utilsInterfaces_test.go b/api/utilsInterfaces_test.go
index 0997993aebfabf2a0bd4fa4ca2efb9c269112a1f..380a691a4b21e23534b28a836ca8c30cf475afdf 100644
--- a/api/utilsInterfaces_test.go
+++ b/api/utilsInterfaces_test.go
@@ -100,6 +100,11 @@ func (t *testNetworkManagerGeneric) Follow(report interfaces.ClientErrorReport)
 func (t *testNetworkManagerGeneric) CheckGarbledMessages() {
 	return
 }
+
+func (t *testNetworkManagerGeneric) GetVerboseRounds() string {
+	return ""
+}
+
 func (t *testNetworkManagerGeneric) SendE2E(message.Send, params.E2E, *stoppable.Single) (
 	[]id.Round, cE2e.MessageID, time.Time, error) {
 	rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
diff --git a/api/utils_test.go b/api/utils_test.go
index a20f1e0bf64a0da34935d467ac483caccd742dce..cff0d76979ea2a75e9d8db400de68bcc6e0f00c8 100644
--- a/api/utils_test.go
+++ b/api/utils_test.go
@@ -94,6 +94,7 @@ func getNDF(face interface{}) *ndf.NetworkDefinition {
 				ID:             nodeID.Bytes(),
 				Address:        "",
 				TlsCertificate: string(cert),
+				Status:         ndf.Active,
 			},
 		},
 		Gateways: []ndf.Gateway{
diff --git a/api/version_vars.go b/api/version_vars.go
index 4c7400d408c23d6a07c021ca80643bc7b362fc22..fbf2295851c49dc762e377b4b311e45e270d984d 100644
--- a/api/version_vars.go
+++ b/api/version_vars.go
@@ -1,10 +1,10 @@
 // Code generated by go generate; DO NOT EDIT.
 // This file was generated by robots at
-// 2021-07-27 14:13:01.428348 -0500 CDT m=+0.036560333
+// 2021-10-07 12:02:04.067615 -0500 CDT m=+0.027463409
 package api
 
-const GITVERSION = `758d1e91 Merge branch 'protoMainNet' into 'release'`
-const SEMVER = "2.8.0"
+const GITVERSION = `86c66226 update deps`
+const SEMVER = "2.10.0"
 const DEPENDENCIES = `module gitlab.com/elixxir/client
 
 go 1.13
@@ -24,16 +24,17 @@ 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.20210714201329-5efcbdfac3ca
-	gitlab.com/elixxir/crypto v0.0.7-0.20210714201100-45fb778a00fb
+	gitlab.com/elixxir/comms v0.0.4-0.20211006231929-cc2735ca43a6
+	gitlab.com/elixxir/crypto v0.0.7-0.20211006231624-44434504fff4
 	gitlab.com/elixxir/ekv v0.1.5
-	gitlab.com/elixxir/primitives v0.0.3-0.20210714200942-a908050c230c
-	gitlab.com/xx_network/comms v0.0.4-0.20210714165756-8e3b40d71db1
-	gitlab.com/xx_network/crypto v0.0.5-0.20210714165656-1ed326047ba9
-	gitlab.com/xx_network/primitives v0.0.4-0.20210727175935-dd746a0d73de
+	gitlab.com/elixxir/primitives v0.0.3-0.20210920180121-b85bca5212f4
+	gitlab.com/xx_network/comms v0.0.4-0.20211006231434-99dd38f025a7
+	gitlab.com/xx_network/crypto v0.0.5-0.20211006222352-8e0ac37b86b0
+	gitlab.com/xx_network/primitives v0.0.4-0.20210915220237-70cb4551d6f3
 	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
 	golang.org/x/net v0.0.0-20210525063256-abc453219eb5
 	google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
+	google.golang.org/grpc v1.38.0
 	google.golang.org/protobuf v1.26.0
 	gopkg.in/ini.v1 v1.62.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/bindings/client.go b/bindings/client.go
index f6fc9ef358c1a2000767f11d9f712f7ecd22fbc5..91293ba3d20f0b98a68f4691e3cb0e2ea589bdcd 100644
--- a/bindings/client.go
+++ b/bindings/client.go
@@ -22,6 +22,7 @@ import (
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/netTime"
+	"google.golang.org/grpc/grpclog"
 	"strings"
 	"sync"
 	"time"
@@ -161,6 +162,13 @@ func RegisterLogWriter(writer LogWriter) {
 	jww.SetLogOutput(&writerAdapter{lw: writer})
 }
 
+// EnableGrpcLogs sets GRPC trace logging
+func EnableGrpcLogs(writer LogWriter) {
+	logger := &writerAdapter{lw: writer}
+	grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(
+		logger, logger, logger, 99))
+}
+
 //Unmarshals a marshaled contact object, returns an error if it fails
 func UnmarshalContact(b []byte) (*Contact, error) {
 	c, err := contact.Unmarshal(b)
diff --git a/bindings/event.go b/bindings/event.go
index 7e28c72e025768a7dc454098e070d923c9832f2f..4f5aeacf907edfb09f23662e986607c9d820633c 100644
--- a/bindings/event.go
+++ b/bindings/event.go
@@ -7,16 +7,18 @@
 
 package bindings
 
-import (
-	"gitlab.com/elixxir/client/interfaces"
-)
+// EventCallbackFunctionObject bindings interface which contains function
+// that implements the EventCallbackFunction
+type EventCallbackFunctionObject interface {
+	ReportEvent(priority int, category, evtType, details string)
+}
 
 // RegisterEventCallback records the given function to receive
 // ReportableEvent objects. It returns the internal index
 // of the callback so that it can be deleted later.
 func (c *Client) RegisterEventCallback(name string,
-	myFunc interfaces.EventCallbackFunction) error {
-	return c.api.RegisterEventCallback(name, myFunc)
+	myObj EventCallbackFunctionObject) error {
+	return c.api.RegisterEventCallback(name, myObj.ReportEvent)
 }
 
 // UnregisterEventCallback deletes the callback identified by the
diff --git a/bindings/group.go b/bindings/group.go
index 00c45668e08563488884f68e5c9f76b2a0907687..2df75cdd1519618135db887d28cef16d14a12b29 100644
--- a/bindings/group.go
+++ b/bindings/group.go
@@ -23,66 +23,66 @@ type GroupChat struct {
 // GroupRequestFunc contains a function callback that is called when a group
 // request is received.
 type GroupRequestFunc interface {
-	GroupRequestCallback(g Group)
+	GroupRequestCallback(g *Group)
 }
 
 // GroupReceiveFunc contains a function callback that is called when a group
 // message is received.
 type GroupReceiveFunc interface {
-	GroupReceiveCallback(msg GroupMessageReceive)
+	GroupReceiveCallback(msg *GroupMessageReceive)
 }
 
 // NewGroupManager creates a new group chat manager.
 func NewGroupManager(client *Client, requestFunc GroupRequestFunc,
-	receiveFunc GroupReceiveFunc) (GroupChat, error) {
+	receiveFunc GroupReceiveFunc) (*GroupChat, error) {
 
 	requestCallback := func(g gs.Group) {
-		requestFunc.GroupRequestCallback(Group{g})
+		requestFunc.GroupRequestCallback(&Group{g})
 	}
 	receiveCallback := func(msg gc.MessageReceive) {
-		receiveFunc.GroupReceiveCallback(GroupMessageReceive{msg})
+		receiveFunc.GroupReceiveCallback(&GroupMessageReceive{msg})
 	}
 
 	// Create a new group chat manager
 	m, err := gc.NewManager(&client.api, requestCallback, receiveCallback)
 	if err != nil {
-		return GroupChat{}, err
+		return nil, err
 	}
 
 	// Start group request and message retrieval workers
 	err = client.api.AddService(m.StartProcesses)
 	if err != nil {
-		return GroupChat{}, err
+		return nil, err
 	}
 
-	return GroupChat{m}, nil
+	return &GroupChat{m}, nil
 }
 
 // MakeGroup creates a new group and sends a group request to all members in the
 // group. The ID of the new group, the rounds the requests were sent on, and the
 // status of the send are contained in NewGroupReport.
-func (g GroupChat) MakeGroup(membership IdList, name, message []byte) (NewGroupReport, error) {
+func (g *GroupChat) MakeGroup(membership *IdList, name, message []byte) (*NewGroupReport, error) {
 	grp, rounds, status, err := g.m.MakeGroup(membership.list, name, message)
-	return NewGroupReport{Group{grp}, rounds, status}, err
+	return &NewGroupReport{&Group{grp}, rounds, status}, err
 }
 
 // ResendRequest resends a group request to all members in the group. The rounds
 // they were sent on and the status of the send are contained in NewGroupReport.
-func (g GroupChat) ResendRequest(groupIdBytes []byte) (NewGroupReport, error) {
+func (g *GroupChat) ResendRequest(groupIdBytes []byte) (*NewGroupReport, error) {
 	groupID, err := id.Unmarshal(groupIdBytes)
 	if err != nil {
-		return NewGroupReport{},
+		return nil,
 			errors.Errorf("Failed to unmarshal group ID: %+v", err)
 	}
 
 	rounds, status, err := g.m.ResendRequest(groupID)
 
-	return NewGroupReport{Group{}, rounds, status}, nil
+	return &NewGroupReport{&Group{}, rounds, status}, nil
 }
 
 // JoinGroup allows a user to join a group when they receive a request. The
 // caller must pass in the serialized bytes of a Group.
-func (g GroupChat) JoinGroup(serializedGroupData []byte) error {
+func (g *GroupChat) JoinGroup(serializedGroupData []byte) error {
 	grp, err := gs.DeserializeGroup(serializedGroupData)
 	if err != nil {
 		return err
@@ -91,7 +91,7 @@ func (g GroupChat) JoinGroup(serializedGroupData []byte) error {
 }
 
 // LeaveGroup deletes a group so a user no longer has access.
-func (g GroupChat) LeaveGroup(groupIdBytes []byte) error {
+func (g *GroupChat) LeaveGroup(groupIdBytes []byte) error {
 	groupID, err := id.Unmarshal(groupIdBytes)
 	if err != nil {
 		return errors.Errorf("Failed to unmarshal group ID: %+v", err)
@@ -102,7 +102,7 @@ func (g GroupChat) LeaveGroup(groupIdBytes []byte) error {
 
 // Send sends the message to the specified group. Returns the round the messages
 // were sent on.
-func (g GroupChat) Send(groupIdBytes, message []byte) (int64, error) {
+func (g *GroupChat) Send(groupIdBytes, message []byte) (int64, error) {
 	groupID, err := id.Unmarshal(groupIdBytes)
 	if err != nil {
 		return 0, errors.Errorf("Failed to unmarshal group ID: %+v", err)
@@ -114,28 +114,28 @@ func (g GroupChat) Send(groupIdBytes, message []byte) (int64, error) {
 
 // GetGroups returns an IdList containing a list of group IDs that the user is a
 // part of.
-func (g GroupChat) GetGroups() IdList {
-	return IdList{g.m.GetGroups()}
+func (g *GroupChat) GetGroups() *IdList {
+	return &IdList{g.m.GetGroups()}
 }
 
 // GetGroup returns the group with the group ID. If no group exists, then the
 // error "failed to find group" is returned.
-func (g GroupChat) GetGroup(groupIdBytes []byte) (Group, error) {
+func (g *GroupChat) GetGroup(groupIdBytes []byte) (*Group, error) {
 	groupID, err := id.Unmarshal(groupIdBytes)
 	if err != nil {
-		return Group{}, errors.Errorf("Failed to unmarshal group ID: %+v", err)
+		return nil, errors.Errorf("Failed to unmarshal group ID: %+v", err)
 	}
 
 	grp, exists := g.m.GetGroup(groupID)
 	if !exists {
-		return Group{}, errors.New("failed to find group")
+		return nil, errors.New("failed to find group")
 	}
 
-	return Group{grp}, nil
+	return &Group{grp}, nil
 }
 
 // NumGroups returns the number of groups the user is a part of.
-func (g GroupChat) NumGroups() int {
+func (g *GroupChat) NumGroups() int {
 	return g.m.NumGroups()
 }
 
@@ -143,20 +143,20 @@ func (g GroupChat) NumGroups() int {
 // the group, a list of rounds that the group requests were sent on, and the
 // status of the send.
 type NewGroupReport struct {
-	group  Group
+	group  *Group
 	rounds []id.Round
 	status gc.RequestStatus
 }
 
 // GetGroup returns the Group.
-func (ngr NewGroupReport) GetGroup() Group {
+func (ngr *NewGroupReport) GetGroup() *Group {
 	return ngr.group
 }
 
 // GetRoundList returns the RoundList containing a list of rounds requests were
 // sent on.
-func (ngr NewGroupReport) GetRoundList() RoundList {
-	return RoundList{ngr.rounds}
+func (ngr *NewGroupReport) GetRoundList() *RoundList {
+	return &RoundList{ngr.rounds}
 }
 
 // GetStatus returns the status of the requests sent when creating a new group.
@@ -164,7 +164,7 @@ func (ngr NewGroupReport) GetRoundList() RoundList {
 //          1   all requests failed to send
 //          2   some request failed and some succeeded
 //          3,  all requests sent successfully
-func (ngr NewGroupReport) GetStatus() int {
+func (ngr *NewGroupReport) GetStatus() int {
 	return int(ngr.status)
 }
 
@@ -179,24 +179,24 @@ type Group struct {
 }
 
 // GetName returns the name set by the user for the group.
-func (g Group) GetName() []byte {
+func (g *Group) GetName() []byte {
 	return g.g.Name
 }
 
 // GetID return the 33-byte unique group ID.
-func (g Group) GetID() []byte {
+func (g *Group) GetID() []byte {
 	return g.g.ID.Bytes()
 }
 
 // GetMembership returns a list of contacts, one for each member in the group.
 // The list is in order; the first contact is the leader/creator of the group.
 // All subsequent members are ordered by their ID.
-func (g Group) GetMembership() GroupMembership {
-	return GroupMembership{g.g.Members}
+func (g *Group) GetMembership() *GroupMembership {
+	return &GroupMembership{g.g.Members}
 }
 
 // Serialize serializes the Group.
-func (g Group) Serialize() []byte {
+func (g *Group) Serialize() []byte {
 	return g.g.Serialize()
 }
 
@@ -211,18 +211,18 @@ type GroupMembership struct {
 }
 
 // Len returns the number of members in the group membership.
-func (gm GroupMembership) Len() int {
-	return gm.Len()
+func (gm *GroupMembership) Len() int {
+	return len(gm.m)
 }
 
 // Get returns the member at the index. The member at index 0 is always the
 // group leader. An error is returned if the index is out of range.
-func (gm GroupMembership) Get(i int) (GroupMember, error) {
-	if i < 0 || i > gm.Len() {
-		return GroupMember{}, errors.Errorf("ID list index must be between %d "+
+func (gm *GroupMembership) Get(i int) (*GroupMember, error) {
+	if i < 0 || i >= gm.Len() {
+		return nil, errors.Errorf("ID list index must be between %d "+
 			"and the last element %d.", 0, gm.Len())
 	}
-	return GroupMember{gm.m[i]}, nil
+	return &GroupMember{gm.m[i]}, nil
 }
 
 ////
@@ -240,7 +240,7 @@ func (gm GroupMember) GetID() []byte {
 
 // GetDhKey returns the byte representation of the public Diffie–Hellman key of
 // the member.
-func (gm GroupMember) GetDhKey() []byte {
+func (gm *GroupMember) GetDhKey() []byte {
 	return gm.DhKey.Bytes()
 }
 
@@ -255,47 +255,58 @@ type GroupMessageReceive struct {
 }
 
 // GetGroupID returns the 33-byte group ID.
-func (gmr GroupMessageReceive) GetGroupID() []byte {
+func (gmr *GroupMessageReceive) GetGroupID() []byte {
 	return gmr.GroupID.Bytes()
 }
 
 // GetMessageID returns the message ID.
-func (gmr GroupMessageReceive) GetMessageID() []byte {
+func (gmr *GroupMessageReceive) GetMessageID() []byte {
 	return gmr.ID.Bytes()
 }
 
 // GetPayload returns the message payload.
-func (gmr GroupMessageReceive) GetPayload() []byte {
+func (gmr *GroupMessageReceive) GetPayload() []byte {
 	return gmr.Payload
 }
 
 // GetSenderID returns the 33-byte user ID of the sender.
-func (gmr GroupMessageReceive) GetSenderID() []byte {
+func (gmr *GroupMessageReceive) GetSenderID() []byte {
 	return gmr.SenderID.Bytes()
 }
 
 // GetRecipientID returns the 33-byte user ID of the recipient.
-func (gmr GroupMessageReceive) GetRecipientID() []byte {
+func (gmr *GroupMessageReceive) GetRecipientID() []byte {
 	return gmr.RecipientID.Bytes()
 }
 
 // GetEphemeralID returns the ephemeral ID of the recipient.
-func (gmr GroupMessageReceive) GetEphemeralID() int64 {
+func (gmr *GroupMessageReceive) GetEphemeralID() int64 {
 	return gmr.EphemeralID.Int64()
 }
 
 // GetTimestampNano returns the message timestamp in nanoseconds.
-func (gmr GroupMessageReceive) GetTimestampNano() int64 {
+func (gmr *GroupMessageReceive) GetTimestampNano() int64 {
 	return gmr.Timestamp.UnixNano()
 }
 
+// GetTimestampMS returns the message timestamp in milliseconds.
+func (gmr *GroupMessageReceive) GetTimestampMS() int64 {
+	return gmr.Timestamp.UnixNano() / 1_000_000
+}
+
 // GetRoundID returns the ID of the round the message was sent on.
-func (gmr GroupMessageReceive) GetRoundID() int64 {
+func (gmr *GroupMessageReceive) GetRoundID() int64 {
 	return int64(gmr.RoundID)
 }
 
 // GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
 // message was sent on.
-func (gmr GroupMessageReceive) GetRoundTimestampNano() int64 {
+func (gmr *GroupMessageReceive) GetRoundTimestampNano() int64 {
 	return gmr.RoundTimestamp.UnixNano()
 }
+
+// GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
+// message was sent on.
+func (gmr *GroupMessageReceive) GetRoundTimestampMS() int64 {
+	return gmr.RoundTimestamp.UnixNano() / 1_000_000
+}
diff --git a/bindings/jsons.go b/bindings/jsons.go
new file mode 100644
index 0000000000000000000000000000000000000000..5405baf8bd9f74c63d236e913043be576479adf8
--- /dev/null
+++ b/bindings/jsons.go
@@ -0,0 +1,53 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2021 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"io/ioutil"
+	"net/http"
+)
+
+// Returns a []byte containing the JSON data describing client errors.
+// See https://git.xx.network/elixxir/client-error-database/
+func DownloadErrorDB() ([]byte, error) {
+	// Build a request for the file
+	resp, err := http.Get("https://elixxir-bins.s3-us-west-1.amazonaws.com/client/errors/clientErrors.json")
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	// Download the contents of the file
+	content, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	// Return it to the user
+	return content, nil
+}
+
+// Returns a []byte containing the JSON data describing registered dApps.
+// See https://git.xx.network/elixxir/registered-dapps
+func DownloadDAppRegistrationDB() ([]byte, error) {
+	// Build a request for the file
+	resp, err := http.Get("https://elixxir-bins.s3-us-west-1.amazonaws.com/client/dapps/appdb.json")
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	// Download the contents of the file
+	content, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	// Return it to the user
+	return content, nil
+}
diff --git a/bindings/jsons_test.go b/bindings/jsons_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a4405fd47fcd0908c0b8acff7a234b7732778e0f
--- /dev/null
+++ b/bindings/jsons_test.go
@@ -0,0 +1,29 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2021 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestDownloadErrorDB(t *testing.T) {
+	json, err := DownloadErrorDB()
+	if err != nil {
+		t.Errorf("DownloadErrorDB returned error: %s", err)
+	}
+	fmt.Printf("json: %s\n", string(json))
+}
+
+func TestDownloadDAppRegistrationDB(t *testing.T) {
+	json, err := DownloadDAppRegistrationDB()
+	if err != nil {
+		t.Errorf("DownloadDAppRegistrationDB returned error: %s", err)
+	}
+	fmt.Printf("json: %s\n", string(json))
+}
diff --git a/bindings/list.go b/bindings/list.go
index a97df24f299d994380d46de8ae9971ad9133c1e7..1331c378e2b2f31c457ef2f3d082c51e186f2fbb 100644
--- a/bindings/list.go
+++ b/bindings/list.go
@@ -123,17 +123,17 @@ type IdList struct {
 }
 
 // MakeIdList creates a new empty IdList.
-func MakeIdList() IdList {
-	return IdList{[]*id.ID{}}
+func MakeIdList() *IdList {
+	return &IdList{[]*id.ID{}}
 }
 
 // Len returns the number of IDs in the list.
-func (idl IdList) Len() int {
+func (idl *IdList) Len() int {
 	return len(idl.list)
 }
 
 // Add appends the ID bytes to the end of the list.
-func (idl IdList) Add(idBytes []byte) error {
+func (idl *IdList) Add(idBytes []byte) error {
 	newID, err := id.Unmarshal(idBytes)
 	if err != nil {
 		return err
@@ -145,7 +145,7 @@ func (idl IdList) Add(idBytes []byte) error {
 
 // Get returns the ID at the index. An error is returned if the index is out of
 // range.
-func (idl IdList) Get(i int) ([]byte, error) {
+func (idl *IdList) Get(i int) ([]byte, error) {
 	if i < 0 || i > len(idl.list) {
 		return nil, errors.Errorf("ID list index must be between %d and the "+
 			"last element %d.", 0, len(idl.list))
diff --git a/bindings/version.go b/bindings/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec517de07f5d262e8e761e85e39bcd68d83914b9
--- /dev/null
+++ b/bindings/version.go
@@ -0,0 +1,18 @@
+package bindings
+
+import "gitlab.com/elixxir/client/api"
+
+// GetVersion returns the api SEMVER
+func GetVersion() string {
+	return api.SEMVER
+}
+
+// GetGitVersion rturns the api GITVERSION
+func GetGitVersion() string {
+	return api.GITVERSION
+}
+
+// GetDependencies returns the api DEPENDENCIES
+func GetDependencies() string {
+	return api.DEPENDENCIES
+}
diff --git a/cmd/root.go b/cmd/root.go
index 41cd73e10c7b68e224776771c31e8cfb1323fbe7..036ff66cf9fef36c45b79b114ce85a5de5774b59 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -12,6 +12,7 @@ import (
 	"encoding/base64"
 	"encoding/binary"
 	"encoding/hex"
+	"errors"
 	"fmt"
 	"github.com/spf13/cobra"
 	jww "github.com/spf13/jwalterweatherman"
@@ -28,6 +29,7 @@ import (
 	"runtime/pprof"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -186,53 +188,63 @@ var rootCmd = &cobra.Command{
 		}
 		paramsE2E := params.GetDefaultE2E()
 		paramsUnsafe := params.GetDefaultUnsafe()
-
+		wg := &sync.WaitGroup{}
 		sendCnt := int(viper.GetUint("sendCount"))
-		sendDelay := time.Duration(viper.GetUint("sendDelay"))
-		for i := 0; i < sendCnt; i++ {
-			fmt.Printf("Sending to %s: %s\n", recipientID, msgBody)
-			var roundIDs []id.Round
-			var roundTimeout time.Duration
-			if unsafe {
-				roundIDs, err = client.SendUnsafe(msg,
-					paramsUnsafe)
-				roundTimeout = paramsUnsafe.Timeout
-			} else {
-				roundIDs, _, _, err = client.SendE2E(msg,
-					paramsE2E)
-				roundTimeout = paramsE2E.Timeout
-			}
-			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
-			}
-
-			// Construct the callback function which prints out the rounds' results
-			f := func(allRoundsSucceeded, timedOut bool,
-				rounds map[id.Round]api.RoundResult) {
-				printRoundResults(allRoundsSucceeded, timedOut, rounds, roundIDs, msg)
-			}
-
-			// Have the client report back the round results
-			err = client.GetRoundResults(roundIDs, roundTimeout, f)
-			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+		wg.Add(sendCnt)
+		go func() {
+			//sendDelay := time.Duration(viper.GetUint("sendDelay"))
+			for i := 0; i < sendCnt; i++ {
+				go func(i int) {
+					defer wg.Done()
+					fmt.Printf("Sending to %s: %s\n", recipientID, msgBody)
+					var roundIDs []id.Round
+					var roundTimeout time.Duration
+					if unsafe {
+						roundIDs, err = client.SendUnsafe(msg,
+							paramsUnsafe)
+						roundTimeout = paramsUnsafe.Timeout
+					} else {
+						roundIDs, _, _, err = client.SendE2E(msg,
+							paramsE2E)
+						roundTimeout = paramsE2E.Timeout
+					}
+					if err != nil {
+						jww.FATAL.Panicf("%+v", err)
+					}
+
+					// Construct the callback function which prints out the rounds' results
+					f := func(allRoundsSucceeded, timedOut bool,
+						rounds map[id.Round]api.RoundResult) {
+						printRoundResults(allRoundsSucceeded, timedOut, rounds, roundIDs, msg)
+					}
+
+					// Have the client report back the round results
+					err = errors.New("derp")
+					for j := 0; j < 5 && err != nil; j++ {
+						err = client.GetRoundResults(roundIDs, roundTimeout, f)
+					}
+
+					if err != nil {
+						jww.FATAL.Panicf("Message sending for send %d failed: %+v", i, err)
+					}
+				}(i)
 			}
-
-			time.Sleep(sendDelay * time.Millisecond)
-		}
+		}()
 
 		// Wait until message timeout or we receive enough then exit
 		// TODO: Actually check for how many messages we've received
 		expectedCnt := viper.GetUint("receiveCount")
 		receiveCnt := uint(0)
 		waitSecs := viper.GetUint("waitTimeout")
-		waitTimeout := time.Duration(waitSecs)
+		waitTimeout := time.Duration(waitSecs)* time.Second
 		done := false
+
 		for !done && expectedCnt != 0 {
-			timeoutTimer := time.NewTimer(waitTimeout * time.Second)
+			timeoutTimer := time.NewTimer(waitTimeout)
 			select {
 			case <-timeoutTimer.C:
 				fmt.Println("Timed out!")
+				jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout)
 				done = true
 				break
 			case m := <-recvCh:
@@ -242,12 +254,33 @@ var rootCmd = &cobra.Command{
 				receiveCnt++
 				if receiveCnt == expectedCnt {
 					done = true
+					break
 				}
+			}
+		}
+
+		//wait an extra 5 seconds to make sure no messages were missed
+		done = false
+		timer := time.NewTimer(5*time.Second)
+		for !done {
+			select {
+			case <-timer.C:
+				done = true
 				break
+			case m := <-recvCh:
+				fmt.Printf("Message received: %s\n", string(
+					m.Payload))
+				//fmt.Printf("%s", m.Timestamp)
+				receiveCnt++
 			}
 		}
-		fmt.Printf("Received %d\n", receiveCnt)
 
+		jww.INFO.Printf("Received %d/%d Messages!", receiveCnt, expectedCnt)
+		fmt.Printf("Received %d\n", receiveCnt)
+		if roundsNotepad != nil {
+			roundsNotepad.INFO.Printf("\n%s", client.GetNetworkInterface().GetVerboseRounds())
+		}
+		wg.Wait()
 		err = client.StopNetworkFollower()
 		if err != nil {
 			jww.WARN.Printf(
@@ -340,7 +373,8 @@ func printRoundResults(allRoundsSucceeded, timedOut bool,
 }
 
 func createClient() *api.Client {
-	initLog(viper.GetUint("logLevel"), viper.GetString("log"))
+	logLevel := viper.GetUint("logLevel")
+	initLog(logLevel, viper.GetString("log"))
 	jww.INFO.Printf(Version())
 
 	pass := viper.GetString("password")
@@ -383,6 +417,7 @@ func createClient() *api.Client {
 	netParams.ForceHistoricalRounds = viper.GetBool("forceHistoricalRounds")
 	netParams.FastPolling = !viper.GetBool("slowPolling")
 	netParams.ForceMessagePickupRetry = viper.GetBool("forceMessagePickupRetry")
+	netParams.VerboseRoundTracking = viper.GetBool("verboseRoundTracking")
 
 	client, err := api.OpenClient(storeDir, []byte(pass), netParams)
 	if err != nil {
@@ -410,6 +445,7 @@ func initClient() *api.Client {
 		jww.INFO.Printf("Setting Uncheck Round Period to %v", period)
 		netParams.UncheckRoundPeriod = period
 	}
+	netParams.VerboseRoundTracking = viper.GetBool("verboseRoundTracking")
 
 	//load the client
 	client, err := api.Login(storeDir, []byte(pass), netParams)
@@ -672,6 +708,10 @@ func initLog(threshold uint, logPath string) {
 		jww.SetStdoutThreshold(jww.LevelInfo)
 		jww.SetLogThreshold(jww.LevelInfo)
 	}
+
+	if viper.GetBool("verboseRoundTracking") {
+		initRoundLog(logPath)
+	}
 }
 
 func askToCreateChannel(recipientID *id.ID) bool {
@@ -690,6 +730,23 @@ func askToCreateChannel(recipientID *id.ID) bool {
 	}
 }
 
+// this the the nodepad used for round logging.
+var roundsNotepad *jww.Notepad
+
+// initRoundLog creates the log output for round tracking. In debug mode,
+// the client will keep track of all rounds it evaluates if it has
+// messages in, and then will dump them to this log on client exit
+func initRoundLog(logPath string) {
+	parts := strings.Split(logPath, ".")
+	path := parts[0] + "-rounds." + parts[1]
+	logOutput, err := os.OpenFile(path,
+		os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+	roundsNotepad = jww.NewNotepad(jww.LevelInfo, jww.LevelInfo, ioutil.Discard, logOutput, "", log.Ldate|log.Ltime)
+}
+
 // init is the initialization function for Cobra which defines commands
 // and flags.
 func init() {
@@ -706,6 +763,11 @@ func init() {
 		"Verbose mode for debugging")
 	viper.BindPFlag("logLevel", rootCmd.PersistentFlags().Lookup("logLevel"))
 
+	rootCmd.PersistentFlags().Bool("verboseRoundTracking", false,
+		"Verbose round tracking, keeps track and prints all rounds the "+
+			"client was aware of while running. Defaults to false if not set.")
+	viper.BindPFlag("verboseRoundTracking", rootCmd.PersistentFlags().Lookup("verboseRoundTracking"))
+
 	rootCmd.PersistentFlags().StringP("session", "s",
 		"", "Sets the initial storage directory for "+
 			"client session data")
diff --git a/cmd/single.go b/cmd/single.go
index 85124107e7ddf46a07eef113ce158b2a15b5303e..6600cbfbbf9b9358d79163c555096127609379d2 100644
--- a/cmd/single.go
+++ b/cmd/single.go
@@ -88,6 +88,17 @@ var singleCmd = &cobra.Command{
 		if err != nil {
 			jww.FATAL.Panicf("Could not add single use process: %+v", err)
 		}
+
+		for numReg, total := 1, 100; numReg < total; {
+			time.Sleep(1 * time.Second)
+			numReg, total, err = client.GetNodeRegistrationStatus()
+			if err != nil {
+				jww.FATAL.Panicf("%+v", err)
+			}
+			jww.INFO.Printf("Registering with nodes (%d/%d)...",
+				numReg, total)
+		}
+
 		timeout := viper.GetDuration("timeout")
 
 		// If the send flag is set, then send a message
diff --git a/cmd/version.go b/cmd/version.go
index f01e1f873e867f17b27ef7913ad1ead7e1d69315..51a4bb42caeb6f5ac9da3a38fb47eceabc6faf7f 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -18,7 +18,7 @@ import (
 )
 
 // Change this value to set the version for this build
-const currentVersion = "2.8.0"
+const currentVersion = "2.10.0"
 
 func Version() string {
 	out := fmt.Sprintf("Elixxir Client v%s -- %s\n\n", api.SEMVER,
diff --git a/go.mod b/go.mod
index ba23f84966c59216c9a53dc940e7a2a56da16e65..dbe4d79a58accf98ee34cc3b003b82eb7dcac69e 100644
--- a/go.mod
+++ b/go.mod
@@ -17,16 +17,17 @@ 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.20210914232530-b0e625b49552
-	gitlab.com/elixxir/crypto v0.0.7-0.20210914232212-42464d16fff3
+	gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970
+	gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e
 	gitlab.com/elixxir/ekv v0.1.5
-	gitlab.com/elixxir/primitives v0.0.3-0.20210914232041-6edc82b7e58e
-	gitlab.com/xx_network/comms v0.0.4-0.20210914232007-b82fc7baa23c
-	gitlab.com/xx_network/crypto v0.0.5-0.20210914231859-c309efac46c4
-	gitlab.com/xx_network/primitives v0.0.4-0.20210913211733-42dc24dd47df
-	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
+	gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576
+	gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae
+	gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686
+	gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/net v0.0.0-20210525063256-abc453219eb5
 	google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
+	google.golang.org/grpc v1.38.0
 	google.golang.org/protobuf v1.26.0
 	gopkg.in/ini.v1 v1.62.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 14da1adea1978dd4659c4b3ed18e93bc6c1f4ea7..37d23fd0792d608122530d1b2d168f77b572f356 100644
--- a/go.sum
+++ b/go.sum
@@ -253,32 +253,34 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0=
 github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA=
 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k=
-gitlab.com/elixxir/comms v0.0.4-0.20210914232530-b0e625b49552 h1:RgyEauSNIAlGo8Ynec01N3GKWWNNmemPLaMW899sCw0=
-gitlab.com/elixxir/comms v0.0.4-0.20210914232530-b0e625b49552/go.mod h1:aXUf9T/1ddQYZ1+/fyhqvqHHxzu2QWB0XdODygWqzi8=
+gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970 h1:mSf5KidH231esbVvL1rozvLXhgAHn8S+BV70k0oxlW8=
+gitlab.com/elixxir/comms v0.0.4-0.20211014164523-495493efb970/go.mod h1:L2fs1Me+L6SKyix7+Gyd9QKmBMjnyJPds/ikSPqdeNY=
 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.20210914232212-42464d16fff3 h1:IK6a8xloclNFxqYTzyCuiqP4cynmtxjzUuB8aNJtHy4=
-gitlab.com/elixxir/crypto v0.0.7-0.20210914232212-42464d16fff3/go.mod h1:3ZGqugc0m5Y236n6mDyfsEy6j8C1HRlqTRj9I7t8k/w=
+gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d h1:tI3YYoHVb/KViRhagzQM0XKdw0hJ7KcuSQXFIWhmtSE=
+gitlab.com/elixxir/crypto v0.0.7-0.20211014164205-95915de2ac0d/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg=
+gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e h1:JCYcXV9GBvOVRfYhm1e2b52AnvPQrEQLmOR9XbjzE8k=
+gitlab.com/elixxir/crypto v0.0.7-0.20211017232951-ba1a65ee7b6e/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg=
 gitlab.com/elixxir/ekv v0.1.5 h1:R8M1PA5zRU1HVnTyrtwybdABh7gUJSCvt1JZwUSeTzk=
 gitlab.com/elixxir/ekv v0.1.5/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
 gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8=
 gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc=
 gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE=
-gitlab.com/elixxir/primitives v0.0.3-0.20210914232041-6edc82b7e58e h1:h7i+Ld2pTlQ0oT2/KLsF6pVBI8D5ir5ZAP4RQBd/CzM=
-gitlab.com/elixxir/primitives v0.0.3-0.20210914232041-6edc82b7e58e/go.mod h1:vTeq2GfYvYydmjhiVC188XeA7GBuUzJ1EZYc1hRwLZs=
+gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 h1:sXX3/hewV4TQLxT2iKBfnfgW/A1eXoEfv5raJxTb79s=
+gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576/go.mod h1:zZy8AlOISFm5IG4G4sylypnz7xNBfZ5mpXiibqJT8+8=
 gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
-gitlab.com/xx_network/comms v0.0.4-0.20210914232007-b82fc7baa23c h1:qoSX9V9rdSIuXXUQgZfY7Hkim9bYsg8XORVaOI8XD5U=
-gitlab.com/xx_network/comms v0.0.4-0.20210914232007-b82fc7baa23c/go.mod h1:Api5Gu+sx1I43THNGKtZOXItJpoGgCLT8KoP7vnLLSc=
+gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae h1:jmZWmSm8eH40SX5B5uOw2XaYoHYqVn8daTfa6B80AOs=
+gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae/go.mod h1:wR9Vx0KZLrIs0g2Efcp0UwFPStjcDRWkg/DJLVQI2vw=
 gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE=
 gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk=
-gitlab.com/xx_network/crypto v0.0.5-0.20210914231859-c309efac46c4 h1:33a7mKkqsZUxzmG35hA1CaVZzyPZYe23if1GxQ99tpI=
-gitlab.com/xx_network/crypto v0.0.5-0.20210914231859-c309efac46c4/go.mod h1:g0Lr/aM0KZqneIvSsKEn7zfNmorhq0R7IQn8Yh8fqbs=
+gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686 h1:mEjKISxi9LrguYgz6evroFwsfxH78/hYmr32yws+WV0=
+gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686/go.mod h1:GeUUB5eMlu7G1u7LXpClfOyUYsSDxAhiZBf+RZeGftc=
 gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA=
 gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug=
 gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
-gitlab.com/xx_network/primitives v0.0.4-0.20210913211733-42dc24dd47df h1:ukBhRj5o0gkbYos9+7SblXaxnMaty3ugQhWqb2BDcSE=
-gitlab.com/xx_network/primitives v0.0.4-0.20210913211733-42dc24dd47df/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb h1:0K9dyxFpDYzH9jYLwzg3+bRj9a0uJjwjQkMeIdTxduQ=
+gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
 gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5 h1:FY+4Rh1Q2rgLyv10aKJjhWApuKRCR/054XhreudfAvw=
 gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -299,8 +301,8 @@ golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -367,8 +369,9 @@ golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/groupChat/groupStore/dhKeyList_test.go b/groupChat/groupStore/dhKeyList_test.go
index df8d9f1dd4923970d1b97375b9e1bf48591378be..c8a633d441cf57c8775d384d817e985e3de59ea5 100644
--- a/groupChat/groupStore/dhKeyList_test.go
+++ b/groupChat/groupStore/dhKeyList_test.go
@@ -74,7 +74,6 @@ func TestDeserializeDhKeyList_DhKeyBinaryDecodeError(t *testing.T) {
 func TestDhKeyList_GoString(t *testing.T) {
 	grp := createTestGroup(rand.New(rand.NewSource(42)), t)
 	expected := "{Grcjbkt1IWKQzyvrQsPKJzKFYPGqwGfOpui/RtSrK0YD: 6342989043... in GRP: 6SsQ/HAHUn..., QCxg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiAD: 2579328386... in GRP: 6SsQ/HAHUn..., invD4ElbVxL+/b4MECiH4QDazS2IX2kstgfaAKEcHHAD: 1688982497... in GRP: 6SsQ/HAHUn..., o54Okp0CSry8sWk5e7c05+8KbgHxhU3rX+Qk/vesIQgD: 5552242738... in GRP: 6SsQ/HAHUn..., wRYCP6iJdLrAyv2a0FaSsTYZ5ziWTf3Hno1TQ3NmHP0D: 2812078897... in GRP: 6SsQ/HAHUn..., 15ufnw07pVsMwNYUTIiFNYQay+BwmwdYCD9h03W8ArQD: 2588260662... in GRP: 6SsQ/HAHUn..., 3RqsBM4ux44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhYD: 4967151805... in GRP: 6SsQ/HAHUn..., 55ai4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ4D: 3187530437... in GRP: 6SsQ/HAHUn..., 9PkZKU50joHnnku9b+NM3LqEPujWPoxP/hzr6lRtj6wD: 4832738218... in GRP: 6SsQ/HAHUn...}"
-
 	if grp.DhKeys.GoString() != expected {
 		t.Errorf("GoString failed to return the expected string."+
 			"\nexpected: %s\nreceived: %s", expected, grp.DhKeys.GoString())
diff --git a/groupChat/makeGroup.go b/groupChat/makeGroup.go
index fa230fadcc99f61a4b4a44d0f62aa549ec9ec306..2c277b4384a04e187e722488d43b99357f1eb0e7 100644
--- a/groupChat/makeGroup.go
+++ b/groupChat/makeGroup.go
@@ -9,6 +9,7 @@ package groupChat
 
 import (
 	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
 	gs "gitlab.com/elixxir/client/groupChat/groupStore"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/fastRNG"
@@ -79,6 +80,9 @@ func (m Manager) MakeGroup(membership []*id.ID, name, msg []byte) (gs.Group,
 		return gs.Group{}, nil, NotSent, errors.Errorf(addGroupErr, err)
 	}
 
+	jww.DEBUG.Printf("Created new group %q with ID %s and members %s",
+		g.Name, g.ID, g.Members)
+
 	// Send all group requests
 	roundIDs, status, err := m.sendRequests(g)
 
diff --git a/groupChat/manager.go b/groupChat/manager.go
index 2e2d18fadee2e5e246cf3ea42951305e14053963..6691e54154e613678d3b50684aa333a63b9c1f64 100644
--- a/groupChat/manager.go
+++ b/groupChat/manager.go
@@ -9,6 +9,7 @@ package groupChat
 
 import (
 	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/api"
 	gs "gitlab.com/elixxir/client/groupChat/groupStore"
 	"gitlab.com/elixxir/client/interfaces"
@@ -127,6 +128,8 @@ func (m Manager) JoinGroup(g gs.Group) error {
 		return errors.Errorf(joinGroupErr, g.ID, err)
 	}
 
+	jww.DEBUG.Printf("Joined group %s.", g.ID)
+
 	return nil
 }
 
@@ -136,17 +139,21 @@ func (m Manager) LeaveGroup(groupID *id.ID) error {
 		return errors.Errorf(leaveGroupErr, groupID, err)
 	}
 
+	jww.DEBUG.Printf("Left group %s.", groupID)
+
 	return nil
 }
 
 // GetGroups returns a list of all registered groupChat IDs.
 func (m Manager) GetGroups() []*id.ID {
+	jww.DEBUG.Print("Getting list of all groups.")
 	return m.gs.GroupIDs()
 }
 
 // GetGroup returns the group with the matching ID or returns false if none
 // exist.
 func (m Manager) GetGroup(groupID *id.ID) (gs.Group, bool) {
+	jww.DEBUG.Printf("Getting group with ID %s.", groupID)
 	return m.gs.Get(groupID)
 }
 
diff --git a/groupChat/send.go b/groupChat/send.go
index f2baa0953555b13f335111af4d1dd7ae0e01731e..cdb40888ee41c3489fe23f026c1f7e5d05f3caf6 100644
--- a/groupChat/send.go
+++ b/groupChat/send.go
@@ -9,6 +9,7 @@ package groupChat
 
 import (
 	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
 	gs "gitlab.com/elixxir/client/groupChat/groupStore"
 	"gitlab.com/elixxir/client/interfaces/params"
 	"gitlab.com/elixxir/crypto/group"
@@ -48,6 +49,8 @@ func (m *Manager) Send(groupID *id.ID, message []byte) (id.Round, error) {
 		return 0, errors.Errorf(sendManyCmixErr, m.gs.GetUser().ID, groupID, err)
 	}
 
+	jww.DEBUG.Printf("Sent message to group %s.", groupID)
+
 	return rid, nil
 }
 
diff --git a/groupChat/sendRequests.go b/groupChat/sendRequests.go
index 70c5501ec7c6492a1b33f5a5309b2602c38ad06e..cd7913fcf9488d1a5927d4171f7677e99cf245c6 100644
--- a/groupChat/sendRequests.go
+++ b/groupChat/sendRequests.go
@@ -10,6 +10,7 @@ package groupChat
 import (
 	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
 	gs "gitlab.com/elixxir/client/groupChat/groupStore"
 	"gitlab.com/elixxir/client/interfaces/message"
 	"gitlab.com/elixxir/client/interfaces/params"
@@ -34,6 +35,8 @@ func (m Manager) ResendRequest(groupID *id.ID) ([]id.Round, RequestStatus, error
 		return nil, NotSent, errors.Errorf(resendGroupIdErr, groupID)
 	}
 
+	jww.DEBUG.Printf("Resending group requests for group %s.", groupID)
+
 	return m.sendRequests(g)
 }
 
diff --git a/groupChat/utils_test.go b/groupChat/utils_test.go
index df7fc4e35b68bad6d9a57fb7e2c34ef6f15613c0..d43ada0dc827fcff5fe253d9d89189abf588f778 100644
--- a/groupChat/utils_test.go
+++ b/groupChat/utils_test.go
@@ -261,6 +261,10 @@ func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Rou
 	return []id.Round{}, nil
 }
 
+func (tnm *testNetworkManager) GetVerboseRounds() string {
+	return ""
+}
+
 func (tnm *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) {
 	return 0, ephemeral.Id{}, nil
 }
diff --git a/interfaces/networkManager.go b/interfaces/networkManager.go
index 072306fddc18f53c7f07299dcffb52e64cdce853..b0d5351b70e290ec024d5d4d36e72c2eed2f4f1b 100644
--- a/interfaces/networkManager.go
+++ b/interfaces/networkManager.go
@@ -38,6 +38,9 @@ type NetworkManager interface {
 	// address size is known.
 	GetAddressSize() uint8
 
+	// GetVerboseRounds returns stringification of verbose round info
+	GetVerboseRounds() string
+
 	// RegisterAddressSizeNotification returns a channel that will trigger for
 	// every address space size update. The provided tag is the unique ID for
 	// the channel. Returns an error if the tag is already used.
diff --git a/interfaces/params/network.go b/interfaces/params/network.go
index d3ee2fd39f4dd528bcb62a972c135c9725eb9768..42d0e7ec45141cc105ab5e33a1b3ba2b77d6ae1e 100644
--- a/interfaces/params/network.go
+++ b/interfaces/params/network.go
@@ -31,6 +31,9 @@ type Network struct {
 	FastPolling bool
 	// Messages will not be sent to Rounds containing these Nodes
 	BlacklistedNodes []string
+	// Determines if the state of every round processed is tracked in ram.
+	// This is very memory intensive and is primarily used for debugging
+	VerboseRoundTracking bool
 
 	Rounds
 	Messages
@@ -46,10 +49,11 @@ func GetDefaultNetwork() Network {
 		RegNodesBufferLen:         500,
 		NetworkHealthTimeout:      30 * time.Second,
 		E2EParams:                 GetDefaultE2ESessionParams(),
-		ParallelNodeRegistrations: 8,
+		ParallelNodeRegistrations: 20,
 		KnownRoundsThreshold:      1500, //5 rounds/sec * 60 sec/min * 5 min
 		FastPolling:               true,
 		BlacklistedNodes:          make([]string, 0),
+		VerboseRoundTracking:      false,
 	}
 	n.Rounds = GetDefaultRounds()
 	n.Messages = GetDefaultMessage()
diff --git a/interfaces/utility/trackResults.go b/interfaces/utility/trackResults.go
index cd766624d475141edbad94731f3a3da092e4bcfd..a02f1e0e4d2442b7a9979dd50dd93c2976d02fe6 100644
--- a/interfaces/utility/trackResults.go
+++ b/interfaces/utility/trackResults.go
@@ -8,6 +8,7 @@
 package utility
 
 import (
+	jww "github.com/spf13/jwalterweatherman"
 	ds "gitlab.com/elixxir/comms/network/dataStructures"
 	"gitlab.com/elixxir/primitives/states"
 )
@@ -22,6 +23,7 @@ func TrackResults(resultsCh chan ds.EventReturn, numResults int) (bool, int, int
 		if er.TimedOut {
 			numTimeOut++
 		} else if states.Round(er.RoundInfo.State) == states.FAILED {
+			jww.ERROR.Printf("RoundInfo FAILED: %+v", er.RoundInfo)
 			numRoundFail++
 		}
 	}
diff --git a/keyExchange/trigger.go b/keyExchange/trigger.go
index e25da821f9a3485969608bd320a7f22534622255..0bdcbcc6e6ca80a28b58f53bbf32f22a2b0d0d41 100644
--- a/keyExchange/trigger.go
+++ b/keyExchange/trigger.go
@@ -150,13 +150,13 @@ func handleTrigger(sess *storage.Session, net interfaces.NetworkManager,
 			"transmit %v/%v paritions: %v round failures, %v timeouts",
 			session, numRoundFail+numTimeOut, len(rounds), numRoundFail,
 			numTimeOut)
-		sess.GetCriticalMessages().Failed(m)
+		sess.GetCriticalMessages().Failed(m, e2eParams)
 		return nil
 	}
 
 	// otherwise, the transmission is a success and this should be denoted
 	// in the session and the log
-	sess.GetCriticalMessages().Succeeded(m)
+	sess.GetCriticalMessages().Succeeded(m, e2eParams)
 	jww.INFO.Printf("Key Negotiation transmission for %s successfully",
 		session)
 
diff --git a/keyExchange/utils_test.go b/keyExchange/utils_test.go
index 0a1b0206bc8e72bf3ca25b4dd17e4ce02a5937d1..6acb4a73a6224a0230a32ec3a4b3ccabe3738814 100644
--- a/keyExchange/utils_test.go
+++ b/keyExchange/utils_test.go
@@ -79,6 +79,8 @@ func (t *testNetworkManagerGeneric) SendUnsafe(m message.Send, p params.Unsafe)
 	return nil, nil
 }
 
+func (t *testNetworkManagerGeneric) GetVerboseRounds() string { return "" }
+
 func (t *testNetworkManagerGeneric) SendCMIX(message format.Message, rid *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error) {
 
 	return id.Round(0), ephemeral.Id{}, nil
@@ -177,6 +179,8 @@ func (t *testNetworkManagerFullExchange) CheckGarbledMessages() {
 	return
 }
 
+func (t *testNetworkManagerFullExchange) GetVerboseRounds() string { return "" }
+
 // Intended for alice to send to bob. Trigger's Bob's confirmation, chaining the operation
 // together
 func (t *testNetworkManagerFullExchange) SendE2E(message.Send, params.E2E, *stoppable.Single) (
diff --git a/network/ephemeral/testutil.go b/network/ephemeral/testutil.go
index 1051e9c297d84a22436a08429f8f023deb953698..634518a1a6ba255fc55b47b092e1ed6b42efe1a7 100644
--- a/network/ephemeral/testutil.go
+++ b/network/ephemeral/testutil.go
@@ -96,7 +96,8 @@ func (t *testNetworkManager) GetSender() *gateway.Sender {
 	return nil
 }
 
-func (t *testNetworkManager) GetAddressSize() uint8 { return 15 }
+func (t *testNetworkManager) GetAddressSize() uint8    { return 15 }
+func (t *testNetworkManager) GetVerboseRounds() string { return "" }
 func (t *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) {
 	return nil, nil
 }
diff --git a/network/follow.go b/network/follow.go
index 2082a6ee4b5417a7de66b4ebc76209deff9072b7..ca0634be7f36a44e00c487046e488ee57525a55b 100644
--- a/network/follow.go
+++ b/network/follow.go
@@ -56,6 +56,13 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport,
 	TrackTicker := time.NewTicker(debugTrackPeriod)
 	rng := m.Rng.GetStream()
 
+	abandon := func(round id.Round) { return }
+	if m.verboseRounds != nil {
+		abandon = func(round id.Round) {
+			m.verboseRounds.denote(round, Abandoned)
+		}
+	}
+
 	for {
 		select {
 		case <-stop.Quit():
@@ -63,7 +70,7 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport,
 			stop.ToStopped()
 			return
 		case <-ticker.C:
-			m.follow(report, rng, m.Comms, stop)
+			m.follow(report, rng, m.Comms, stop, abandon)
 		case <-TrackTicker.C:
 			numPolls := atomic.SwapUint64(m.tracker, 0)
 			if m.numLatencies != 0 {
@@ -94,7 +101,7 @@ func (m *manager) followNetwork(report interfaces.ClientErrorReport,
 
 // executes each iteration of the follower
 func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
-	comms followNetworkComms, stop *stoppable.Single) {
+	comms followNetworkComms, stop *stoppable.Single, abandon func(round id.Round)) {
 
 	//Get the identity we will poll for
 	identity, err := m.Session.Reception().GetIdentity(rng, m.addrSpace.GetWithoutWait())
@@ -262,7 +269,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
 	}
 
 	if len(pollResp.Filters.Filters) == 0 {
-		jww.TRACE.Printf("No filters found for the passed ID %d (%s), "+
+		jww.WARN.Printf("No filters found for the passed ID %d (%s), "+
 			"skipping processing.", identity.EphId.Int64(), identity.Source)
 		return
 	}
@@ -272,6 +279,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
 
 	//check if there are any valid filters returned
 	if outOfBounds {
+		jww.WARN.Printf("No filters processed, none in valid range")
 		return
 	}
 
@@ -287,13 +295,25 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
 	// are messages waiting in rounds and then sends signals to the appropriate
 	// handling threads
 	roundChecker := func(rid id.Round) bool {
-		return rounds.Checker(rid, filterList, identity.CR)
+		hasMessage := rounds.Checker(rid, filterList, identity.CR)
+		if !hasMessage && m.verboseRounds != nil {
+			m.verboseRounds.denote(rid, RoundState(NoMessageAvailable))
+		}
+		return hasMessage
 	}
 
 	// move the earliest unknown round tracker forward to the earliest
 	// tracked round if it is behind
 	earliestTrackedRound := id.Round(pollResp.EarliestRound)
-	updated, _ := identity.ER.Set(earliestTrackedRound)
+	updated, old, _ := identity.ER.Set(earliestTrackedRound)
+	if old == 0 {
+		if gwRoundsState.GetLastChecked() > id.Round(m.param.KnownRoundsThreshold) {
+			updated = gwRoundsState.GetLastChecked() - id.Round(m.param.KnownRoundsThreshold)
+		} else {
+			updated = 1
+		}
+		identity.ER.Set(updated)
+	}
 
 	// loop through all rounds the client does not know about and the gateway
 	// does, checking the bloom filter for the user to see if there are
@@ -301,7 +321,8 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
 	//threshold is the earliest round that will not be excluded from earliest remaining
 	earliestRemaining, roundsWithMessages, roundsUnknown := gwRoundsState.RangeUnchecked(updated,
 		m.param.KnownRoundsThreshold, roundChecker)
-	_, changed := identity.ER.Set(earliestRemaining)
+
+	_, _, changed := identity.ER.Set(earliestRemaining)
 	if changed {
 		jww.TRACE.Printf("External returns of RangeUnchecked: %d, %v, %v", earliestRemaining, roundsWithMessages, roundsUnknown)
 		jww.DEBUG.Printf("New Earliest Remaining: %d", earliestRemaining)
@@ -312,9 +333,10 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
 			return rounds.Checker(rid, filterList, identity.CR)
 		}
 		return false
-	}, roundsUnknown)
+	}, roundsUnknown, abandon)
 
 	for _, rid := range roundsWithMessages {
+		//denote that the round has been looked at in the tracking store
 		if identity.CR.Check(rid) {
 			m.round.GetMessagesFromRound(rid, identity)
 		}
@@ -330,4 +352,32 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source,
 	for _, rid := range roundsWithMessages2 {
 		m.round.GetMessagesFromRound(rid, identity)
 	}
+
+	if m.verboseRounds != nil {
+		trackingStart := updated
+		if uint(earliestRemaining-updated) > m.param.KnownRoundsThreshold {
+			trackingStart = earliestRemaining - id.Round(m.param.KnownRoundsThreshold)
+		}
+		jww.DEBUG.Printf("Rounds tracked: %v to %v", trackingStart, earliestRemaining)
+		for i := trackingStart; i <= earliestRemaining; i++ {
+			state := Unchecked
+			for _, rid := range roundsWithMessages {
+				if rid == i {
+					state = MessageAvailable
+				}
+			}
+			for _, rid := range roundsWithMessages2 {
+				if rid == i {
+					state = MessageAvailable
+				}
+			}
+			for _, rid := range roundsUnknown {
+				if rid == i {
+					state = Unknown
+				}
+			}
+			m.verboseRounds.denote(i, RoundState(state))
+		}
+	}
+
 }
diff --git a/network/gateway/hostPool.go b/network/gateway/hostPool.go
index 52139d1a185b760c9d23cf408e65a86c7d2c67bd..5bb0b82044058f8c51f055482cdd00a1b6bf37d9 100644
--- a/network/gateway/hostPool.go
+++ b/network/gateway/hostPool.go
@@ -18,12 +18,16 @@ import (
 	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/comms/network"
 	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/crypto/shuffle"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/ndf"
 	"golang.org/x/net/context"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/balancer"
 	"io"
 	"math"
+	"sort"
 	"strings"
 	"sync"
 	"time"
@@ -31,8 +35,8 @@ import (
 
 // List of errors that initiate a Host replacement
 var errorsList = []string{context.DeadlineExceeded.Error(), "connection refused", "host disconnected",
-	"transport is closing", "all SubConns are in TransientFailure", "Last try to connect",
-	ndf.NO_NDF, "Host is in cool down"}
+	"transport is closing", balancer.ErrTransientFailure.Error(), "Last try to connect",
+	ndf.NO_NDF, "Host is in cool down", grpc.ErrClientConnClosing.Error()}
 
 // HostManager Interface allowing storage and retrieval of Host objects
 type HostManager interface {
@@ -70,10 +74,10 @@ type HostPool struct {
 
 // PoolParams Allows configuration of HostPool parameters
 type PoolParams struct {
-	MaxPoolSize uint32 // Maximum number of Hosts in the HostPool
-	PoolSize    uint32 // Allows override of HostPool size. Set to zero for dynamic size calculation
-	// TODO: Move up a layer
+	MaxPoolSize   uint32             // Maximum number of Hosts in the HostPool
+	PoolSize      uint32             // Allows override of HostPool size. Set to zero for dynamic size calculation
 	ProxyAttempts uint32             // How many proxies will be used in event of send failure
+	MaxPings      uint32             // How many gateways to concurrently test when initializing HostPool. Disabled if zero.
 	HostParams    connect.HostParams // Parameters for the creation of new Host objects
 }
 
@@ -83,6 +87,7 @@ func DefaultPoolParams() PoolParams {
 		MaxPoolSize:   30,
 		ProxyAttempts: 5,
 		PoolSize:      0,
+		MaxPings:      0,
 		HostParams:    connect.GetDefaultHostParams(),
 	}
 	p.HostParams.MaxRetries = 1
@@ -91,6 +96,7 @@ func DefaultPoolParams() PoolParams {
 	p.HostParams.NumSendsBeforeCoolOff = 1
 	p.HostParams.CoolOffTimeout = 5 * time.Minute
 	p.HostParams.SendTimeout = 2000 * time.Millisecond
+	p.HostParams.PingTimeout = 1 * time.Second
 	return p
 }
 
@@ -114,7 +120,7 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator,
 		hostMap:        make(map[id.ID]uint32),
 		hostList:       make([]*connect.Host, poolParams.PoolSize),
 		poolParams:     poolParams,
-		ndf:            netDef,
+		ndf:            netDef.DeepCopy(),
 		rng:            rng,
 		storage:        storage,
 		addGatewayChan: addGateway,
@@ -141,6 +147,9 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator,
 				jww.WARN.Printf("Unable to add stored host %s: %s", hid, err.Error())
 			} else {
 				numHostsAdded++
+				if numHostsAdded >= len(result.hostList) {
+					break
+				}
 			}
 		}
 	} else {
@@ -148,17 +157,151 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator,
 	}
 
 	// Build the initial HostPool and return
-	for i := numHostsAdded; i < len(result.hostList); i++ {
-		err := result.forceReplace(uint32(i))
+	if result.poolParams.MaxPings > 0 {
+		// If pinging enabled, select random performant Hosts
+		err = result.initialize(uint32(numHostsAdded))
 		if err != nil {
 			return nil, err
 		}
+	} else {
+		// Else, select random Hosts
+		for i := numHostsAdded; i < len(result.hostList); i++ {
+			err := result.replaceHost(result.selectGateway(), uint32(i))
+			if err != nil {
+				return nil, err
+			}
+		}
 	}
 
 	jww.INFO.Printf("Initialized HostPool with size: %d/%d", poolParams.PoolSize, len(netDef.Gateways))
 	return result, nil
 }
 
+// Initialize the HostPool with a performant set of Hosts
+func (h *HostPool) initialize(startIdx uint32) error {
+	// If HostPool is full, don't need to initialize
+	if startIdx == h.poolParams.PoolSize {
+		return nil
+	}
+
+	// Randomly shuffle gateways in NDF
+	randomGateways := make([]ndf.Gateway, len(h.ndf.Gateways))
+	copy(randomGateways, h.ndf.Gateways)
+	var rndBytes [32]byte
+	stream := h.rng.GetStream()
+	_, err := stream.Read(rndBytes[:])
+	stream.Close()
+	if err != nil {
+		return errors.Errorf("Failed to randomize shuffle for HostPool initialization: %+v", err)
+	}
+	shuffle.ShuffleSwap(rndBytes[:], len(randomGateways), func(i, j int) {
+		randomGateways[i], randomGateways[j] = randomGateways[j], randomGateways[i]
+	})
+
+	// Set constants
+	type gatewayDuration struct {
+		id      *id.ID
+		latency time.Duration
+	}
+	numGatewaysToTry := h.poolParams.MaxPings
+	numGateways := uint32(len(randomGateways))
+	if numGatewaysToTry > numGateways {
+		numGatewaysToTry = numGateways
+	}
+	resultList := make([]gatewayDuration, 0, numGatewaysToTry)
+
+	// Begin trying gateways
+	c := make(chan gatewayDuration, numGatewaysToTry)
+	exit := false
+	i := uint32(0)
+	for !exit {
+		for ; i < numGateways; i++ {
+			// Ran out of Hosts to try
+			if i >= numGateways {
+				exit = true
+				break
+			}
+
+			// Select a gateway not yet selected
+			gwId, err := randomGateways[i].GetGatewayId()
+			if err != nil {
+				jww.WARN.Printf("ID for gateway %s could not be retrieved", gwId)
+			}
+			// Skip if already in HostPool
+			if _, ok := h.hostMap[*gwId]; ok {
+				// Try another Host instead
+				numGatewaysToTry++
+				continue
+			}
+
+			go func() {
+				// Obtain that GwId's Host object
+				newHost, ok := h.manager.GetHost(gwId)
+				if !ok {
+					jww.WARN.Printf("Host for gateway %s could not be "+
+						"retrieved", gwId)
+					return
+				}
+
+				// Ping the Host latency and send the result
+				jww.DEBUG.Printf("Testing host %s...", gwId.String())
+				latency, _ := newHost.IsOnline()
+				c <- gatewayDuration{gwId, latency}
+			}()
+		}
+
+		// Collect ping results
+		timer := time.NewTimer(2 * h.poolParams.HostParams.PingTimeout)
+	innerLoop:
+		for {
+			select {
+			case gw := <-c:
+				// Only add successful pings
+				if gw.latency > 0 {
+					resultList = append(resultList, gw)
+					jww.DEBUG.Printf("Adding HostPool result %d/%d: %s: %d",
+						len(resultList), numGatewaysToTry, gw.id.String(), gw.latency)
+				}
+
+				// Break if we have all needed slots
+				if uint32(len(resultList)) == numGatewaysToTry {
+					exit = true
+					break innerLoop
+				}
+			case <-timer.C:
+				jww.INFO.Printf("HostPool initialization timed out!")
+				break innerLoop
+			}
+		}
+	}
+
+	// Sort the resultList by lowest latency
+	sort.Slice(resultList, func(i, j int) bool {
+		return resultList[i].latency < resultList[j].latency
+	})
+	jww.DEBUG.Printf("Gateway pool results: %+v", resultList)
+
+	// Process ping results
+	for _, result := range resultList {
+		err = h.replaceHost(result.id, startIdx)
+		if err != nil {
+			jww.WARN.Printf("Unable to replaceHost: %+v", err)
+			continue
+		}
+		startIdx++
+		if startIdx >= h.poolParams.PoolSize {
+			break
+		}
+	}
+
+	// Ran out of Hosts to try
+	if startIdx < h.poolParams.PoolSize {
+		return errors.Errorf("Unable to initialize enough viable hosts for HostPool")
+	}
+
+	return nil
+}
+
 // UpdateNdf Mutates internal ndf to the given ndf
 func (h *HostPool) UpdateNdf(ndf *ndf.NetworkDefinition) {
 	if len(ndf.Gateways) == 0 {
@@ -167,7 +310,7 @@ func (h *HostPool) UpdateNdf(ndf *ndf.NetworkDefinition) {
 	}
 
 	h.ndfMux.Lock()
-	h.ndf = ndf
+	h.ndf = ndf.DeepCopy()
 
 	h.hostMux.Lock()
 	err := h.updateConns()
@@ -186,6 +329,26 @@ func (h *HostPool) SetFilter(f Filter) {
 	h.filter = f
 }
 
+// GetHostParams returns a copy of the host parameters struct
+func (h *HostPool) GetHostParams() connect.HostParams {
+	hp := h.poolParams.HostParams
+	hpCopy := connect.HostParams{
+		MaxRetries:            hp.MaxRetries,
+		AuthEnabled:           hp.AuthEnabled,
+		EnableCoolOff:         hp.EnableCoolOff,
+		NumSendsBeforeCoolOff: hp.NumSendsBeforeCoolOff,
+		CoolOffTimeout:        hp.CoolOffTimeout,
+		SendTimeout:           hp.SendTimeout,
+		EnableMetrics:         hp.EnableMetrics,
+		ExcludeMetricErrors:   make([]string, len(hp.ExcludeMetricErrors)),
+		KaClientOpts:          hp.KaClientOpts,
+	}
+	for i := 0; i < len(hp.ExcludeMetricErrors); i++ {
+		hpCopy.ExcludeMetricErrors[i] = hp.ExcludeMetricErrors[i]
+	}
+	return hpCopy
+}
+
 // getFilter returns the filter used to filter gateways from the ID map.
 func (h *HostPool) getFilter() Filter {
 	h.filterMux.Lock()
@@ -281,7 +444,7 @@ func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) (bool, error) {
 	if hostErr != nil {
 		for _, errString := range errorsList {
 			if strings.Contains(hostErr.Error(), errString) {
-				// Host needs replaced, flag and continue
+				// Host needs to be replaced, flag and continue
 				doReplace = true
 				break
 			}
@@ -294,7 +457,7 @@ func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) (bool, error) {
 		if oldPoolIndex, ok := h.hostMap[*hostId]; ok {
 			// Replace it
 			h.ndfMux.RLock()
-			err = h.forceReplace(oldPoolIndex)
+			err = h.replaceHost(h.selectGateway(), oldPoolIndex)
 			h.ndfMux.RUnlock()
 		}
 		h.hostMux.Unlock()
@@ -302,8 +465,8 @@ func (h *HostPool) checkReplace(hostId *id.ID, hostErr error) (bool, error) {
 	return doReplace, err
 }
 
-// Replace given Host index with a new, randomly-selected Host from the NDF
-func (h *HostPool) forceReplace(oldPoolIndex uint32) error {
+// Select a viable HostPool candidate from the NDF
+func (h *HostPool) selectGateway() *id.ID {
 	rng := h.rng.GetStream()
 	defer rng.Close()
 
@@ -311,18 +474,27 @@ func (h *HostPool) forceReplace(oldPoolIndex uint32) error {
 	for {
 		// Randomly select a new Gw by index in the NDF
 		ndfIdx := readRangeUint32(0, uint32(len(h.ndf.Gateways)), rng)
-		jww.DEBUG.Printf("Attempting to replace Host at HostPool %d with Host at NDF %d...", oldPoolIndex, ndfIdx)
 
 		// Use the random ndfIdx to obtain a GwId from the NDF
 		gwId, err := id.Unmarshal(h.ndf.Gateways[ndfIdx].ID)
 		if err != nil {
-			return errors.WithMessage(err, "failed to get Gateway for pruning")
+			jww.WARN.Printf("Unable to unmarshal gateway: %+v", err)
+			continue
+		}
+
+		// Verify the Gateway's Node is not Stale before adding to HostPool
+		nodeId := gwId.DeepCopy()
+		nodeId.SetType(id.Node)
+		nodeNdfIdx := h.ndfMap[*nodeId]
+		isNodeStale := h.ndf.Nodes[nodeNdfIdx].Status == ndf.Stale
+		if isNodeStale {
+			jww.DEBUG.Printf("Ignoring stale node: %s", nodeId.String())
+			continue
 		}
 
 		// Verify the new GwId is not already in the hostMap
 		if _, ok := h.hostMap[*gwId]; !ok {
-			// If it is a new GwId, replace the old Host with the new Host
-			return h.replaceHost(gwId, oldPoolIndex)
+			return gwId
 		}
 	}
 }
@@ -335,7 +507,7 @@ func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error {
 		return err
 	}
 
-	// Convert list of of non-nil and non-zero hosts to ID list
+	// Convert list of non-nil and non-zero hosts to ID list
 	idList := make([]*id.ID, 0, len(h.hostList))
 	for _, host := range h.hostList {
 		if host.GetId() != nil && !host.GetId().Cmp(&id.ID{}) {
@@ -348,7 +520,7 @@ func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error {
 }
 
 // replaceHostNoStore replaces the given slot in the HostPool with a new Gateway
-// with the specified ID.
+// with the specified ID without saving changes to storage
 func (h *HostPool) replaceHostNoStore(newId *id.ID, oldPoolIndex uint32) error {
 	// Obtain that GwId's Host object
 	newHost, ok := h.manager.GetHost(newId)
@@ -367,11 +539,14 @@ func (h *HostPool) replaceHostNoStore(newId *id.ID, oldPoolIndex uint32) error {
 	h.hostMap[*newId] = oldPoolIndex
 
 	// Clean up and move onto next Host
+	oldHostIDStr := "unknown"
 	if oldHost != nil {
+		oldHostIDStr = oldHost.GetId().String()
 		delete(h.hostMap, *oldHost.GetId())
 		go oldHost.Disconnect()
 	}
-	jww.DEBUG.Printf("Replaced Host at %d with new Host %s", oldPoolIndex,
+
+	jww.DEBUG.Printf("Replaced Host at %d [%s] with new Host %s", oldPoolIndex, oldHostIDStr,
 		newId.String())
 
 	return nil
@@ -406,24 +581,27 @@ func (h *HostPool) updateConns() error {
 	// Filter out gateway IDs
 	newMap = h.getFilter()(newMap, h.ndf)
 
+	// Keep track of the old NDF set
+	oldMap := h.ndfMap
+	// Update the internal NDF set
+	h.ndfMap = newMap
+
 	// Handle adding Gateways
 	for gwId, ndfIdx := range newMap {
-		if _, ok := h.ndfMap[gwId]; !ok {
+		if _, ok := oldMap[gwId]; !ok {
 			// If GwId in newMap is not in ndfMap, add the Gateway
 			h.addGateway(gwId.DeepCopy(), ndfIdx)
 		}
 	}
 
 	// Handle removing Gateways
-	for gwId := range h.ndfMap {
+	for gwId := range oldMap {
 		if _, ok := newMap[gwId]; !ok {
 			// If GwId in ndfMap is not in newMap, remove the Gateway
 			h.removeGateway(gwId.DeepCopy())
 		}
 	}
 
-	// Update the internal NDF set
-	h.ndfMap = newMap
 	return nil
 }
 
@@ -434,7 +612,8 @@ func convertNdfToMap(ndf *ndf.NetworkDefinition) (map[id.ID]int, error) {
 		return result, nil
 	}
 
-	// Process gateway Id's into set
+	// Process Node and Gateway Ids into set
+	// NOTE: We expect len(ndf.Gateways) == len(ndf.Nodes)
 	for i := range ndf.Gateways {
 		gw := ndf.Gateways[i]
 		gwId, err := id.Unmarshal(gw.ID)
@@ -442,6 +621,13 @@ func convertNdfToMap(ndf *ndf.NetworkDefinition) (map[id.ID]int, error) {
 			return nil, err
 		}
 		result[*gwId] = i
+
+		node := ndf.Nodes[i]
+		nodeId, err := id.Unmarshal(node.ID)
+		if err != nil {
+			return nil, err
+		}
+		result[*nodeId] = i
 	}
 
 	return result, nil
@@ -452,7 +638,7 @@ func (h *HostPool) removeGateway(gwId *id.ID) {
 	h.manager.RemoveHost(gwId)
 	// If needed, replace the removed Gateway in the HostPool with a new one
 	if poolIndex, ok := h.hostMap[*gwId]; ok {
-		err := h.forceReplace(poolIndex)
+		err := h.replaceHost(h.selectGateway(), poolIndex)
 		if err != nil {
 			jww.ERROR.Printf("Unable to removeGateway: %+v", err)
 		}
diff --git a/network/gateway/hostpool_test.go b/network/gateway/hostpool_test.go
index e06d49dab9f88673e8a1fea3c4a364e7697c4190..919d3faedbada3445219de4886fc84ba2362cdeb 100644
--- a/network/gateway/hostpool_test.go
+++ b/network/gateway/hostpool_test.go
@@ -143,7 +143,7 @@ func TestHostPool_ManageHostPool(t *testing.T) {
 		// Construct nodes
 		nodeId := gwId.DeepCopy()
 		nodeId.SetType(id.Node)
-		newNodes[i] = ndf.Node{ID: nodeId.Bytes()}
+		newNodes[i] = ndf.Node{ID: nodeId.Bytes(), Status: ndf.Active}
 
 	}
 
@@ -319,6 +319,17 @@ func TestHostPool_ForceReplace(t *testing.T) {
 	params := DefaultPoolParams()
 	params.PoolSize = uint32(len(testNdf.Gateways))
 
+	// Add a stale node
+	newGateway := ndf.Gateway{
+		ID: id.NewIdFromUInt(27, id.Gateway, t).Bytes(),
+	}
+	newNode := ndf.Node{
+		ID:     id.NewIdFromUInt(27, id.Node, t).Bytes(),
+		Status: ndf.Stale,
+	}
+	testNdf.Gateways = append(testNdf.Gateways, newGateway)
+	testNdf.Nodes = append(testNdf.Nodes, newNode)
+
 	// Pull all gateways from ndf into host manager
 	for _, gw := range testNdf.Gateways {
 
@@ -343,13 +354,14 @@ func TestHostPool_ForceReplace(t *testing.T) {
 	}
 
 	// Add all gateways to hostPool's map
-	for index, gw := range testNdf.Gateways {
+	for i := uint32(0); i < params.PoolSize; i++ {
+		gw := testNdf.Gateways[i]
 		gwId, err := id.Unmarshal(gw.ID)
 		if err != nil {
 			t.Fatalf("Failed to unmarshal ID in mock ndf: %v", err)
 		}
 
-		err = testPool.replaceHost(gwId, uint32(index))
+		err = testPool.replaceHost(gwId, i)
 		if err != nil {
 			t.Fatalf("Failed to replace host in set-up: %v", err)
 		}
@@ -359,7 +371,7 @@ func TestHostPool_ForceReplace(t *testing.T) {
 	oldHost := testPool.hostList[oldGatewayIndex]
 
 	// Force replace the gateway at a given index
-	err = testPool.forceReplace(uint32(oldGatewayIndex))
+	err = testPool.replaceHost(testPool.selectGateway(), uint32(oldGatewayIndex))
 	if err != nil {
 		t.Errorf("Failed to force replace: %v", err)
 	}
@@ -490,7 +502,7 @@ func TestHostPool_UpdateNdf(t *testing.T) {
 	hostPool.UpdateNdf(newNdf)
 
 	// Check that the host pool's ndf has been modified properly
-	if !reflect.DeepEqual(newNdf, hostPool.ndf) {
+	if len(newNdf.Nodes) != len(hostPool.ndf.Nodes) || len(newNdf.Gateways) != len(hostPool.ndf.Gateways) {
 		t.Errorf("Host pool ndf not updated to new ndf.")
 	}
 }
@@ -792,7 +804,7 @@ func TestHostPool_UpdateConns_RemoveGateways(t *testing.T) {
 		// Construct nodes
 		nodeId := gwId.DeepCopy()
 		nodeId.SetType(id.Node)
-		newNodes[i] = ndf.Node{ID: nodeId.Bytes()}
+		newNodes[i] = ndf.Node{ID: nodeId.Bytes(), Status: ndf.Active}
 
 	}
 
diff --git a/network/gateway/sender.go b/network/gateway/sender.go
index ddd41478cc7845347bd5adde640794766cf9cc8f..9d2f3d3ad9fa96f3dee1ea987ef4e5b9f55635dd 100644
--- a/network/gateway/sender.go
+++ b/network/gateway/sender.go
@@ -18,6 +18,7 @@ import (
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/ndf"
+	"strings"
 )
 
 // Sender Object used for sending that wraps the HostPool for providing destinations
@@ -25,6 +26,8 @@ type Sender struct {
 	*HostPool
 }
 
+const RetryableError = "Nonfatal error occurred, please retry"
+
 // NewSender Create a new Sender object wrapping a HostPool object
 func NewSender(poolParams PoolParams, rng *fastRNG.StreamGenerator, ndf *ndf.NetworkDefinition, getter HostManager,
 	storage *storage.Session, addGateway chan network.NodeGateway) (*Sender, error) {
@@ -40,18 +43,32 @@ func NewSender(poolParams PoolParams, rng *fastRNG.StreamGenerator, ndf *ndf.Net
 func (s *Sender) SendToAny(sendFunc func(host *connect.Host) (interface{}, error), stop *stoppable.Single) (interface{}, error) {
 
 	proxies := s.getAny(s.poolParams.ProxyAttempts, nil)
-	for i := range proxies {
-		result, err := sendFunc(proxies[i])
+	for proxy := range proxies {
+		result, err := sendFunc(proxies[proxy])
 		if stop != nil && !stop.IsRunning() {
 			return nil, errors.Errorf(stoppable.ErrMsg, stop.Name(), "SendToAny")
 		} else if err == nil {
 			return result, nil
-		} else {
-			jww.WARN.Printf("Unable to SendToAny %s: %s", proxies[i].GetId().String(), err)
-			_, err = s.checkReplace(proxies[i].GetId(), err)
-			if err != nil {
-				jww.ERROR.Printf("Unable to checkReplace: %+v", err)
+		} else if strings.Contains(err.Error(), RetryableError) {
+			// Retry of the proxy could not communicate
+			jww.INFO.Printf("Unable to SendToAny via %s: non-fatal error received, retrying: %s",
+				proxies[proxy].GetId().String(), err)
+		} else if strings.Contains(err.Error(), "unable to connect to target host") {
+			// Retry of the proxy could not communicate
+			jww.WARN.Printf("Unable to SendToAny via %s: %s,"+
+				" proxy could not contact requested host",
+				proxies[proxy].GetId(), err)
+			continue
+		} else if replaced, checkReplaceErr := s.checkReplace(proxies[proxy].GetId(), err); replaced {
+			if checkReplaceErr != nil {
+				jww.WARN.Printf("Unable to SendToAny, replaced a proxy %s with error %s",
+					proxies[proxy].GetId().String(), checkReplaceErr)
+			} else {
+				jww.WARN.Printf("Unable to SendToAny, replaced a proxy %s",
+					proxies[proxy].GetId().String())
 			}
+		} else {
+			return nil, errors.WithMessage(err, "Received error with SendToAny")
 		}
 	}
 
@@ -60,7 +77,7 @@ func (s *Sender) SendToAny(sendFunc func(host *connect.Host) (interface{}, error
 
 // SendToPreferred Call given sendFunc to any Host in the HostPool, attempting with up to numProxies destinations
 func (s *Sender) SendToPreferred(targets []*id.ID,
-	sendFunc func(host *connect.Host, target *id.ID) (interface{}, bool, error),
+	sendFunc func(host *connect.Host, target *id.ID) (interface{}, error),
 	stop *stoppable.Single) (interface{}, error) {
 
 	// Get the hosts and shuffle randomly
@@ -68,22 +85,38 @@ func (s *Sender) SendToPreferred(targets []*id.ID,
 
 	// Attempt to send directly to targets if they are in the HostPool
 	for i := range targetHosts {
-		result, didAbort, err := sendFunc(targetHosts[i], targets[i])
+		result, err := sendFunc(targetHosts[i], targets[i])
 		if stop != nil && !stop.IsRunning() {
 			return nil, errors.Errorf(stoppable.ErrMsg, stop.Name(), "SendToPreferred")
 		} else if err == nil {
 			return result, nil
-		} else {
-			if didAbort {
-				return nil, errors.WithMessagef(err, "Aborted SendToPreferred gateway %s",
-					targetHosts[i].GetId().String())
-			}
-			jww.WARN.Printf("Unable to SendToPreferred %s via %s: %s",
+		} else if strings.Contains(err.Error(), RetryableError) {
+			// Retry of the proxy could not communicate
+			jww.INFO.Printf("Unable to to SendToPreferred first pass %s via %s: non-fatal error received, retrying: %s",
+				targets[i], targetHosts[i].GetId(), err)
+		} else if strings.Contains(err.Error(), "unable to connect to target host") {
+			// Retry of the proxy could not communicate
+			jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, "+
+				"proxy could not contact requested host",
 				targets[i], targetHosts[i].GetId(), err)
-			_, err = s.checkReplace(targetHosts[i].GetId(), err)
-			if err != nil {
-				jww.ERROR.Printf("Unable to checkReplace: %+v", err)
+			continue
+		} else if replaced, checkReplaceErr := s.checkReplace(targetHosts[i].GetId(), err); replaced {
+			if checkReplaceErr != nil {
+				jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s, "+
+					"proxy failed, was replaced with error: %s",
+					targets[i], targetHosts[i].GetId(), checkReplaceErr)
+			} else {
+				jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s, "+
+					"proxy failed, was replaced",
+					targets[i], targetHosts[i].GetId())
 			}
+			jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, proxy failed, was replaced",
+				targets[i], targetHosts[i].GetId(), checkReplaceErr)
+			continue
+		} else {
+			jww.WARN.Printf("Unable to SendToPreferred first pass %s via %s: %s, comm returned an error",
+				targets[i], targetHosts[i].GetId(), err)
+			return result, err
 		}
 	}
 
@@ -115,26 +148,37 @@ func (s *Sender) SendToPreferred(targets []*id.ID,
 				continue
 			}
 
-			result, didAbort, err := sendFunc(targetProxies[proxyIdx], target)
+			result, err := sendFunc(proxy, target)
 			if stop != nil && !stop.IsRunning() {
 				return nil, errors.Errorf(stoppable.ErrMsg, stop.Name(), "SendToPreferred")
 			} else if err == nil {
 				return result, nil
-			} else {
-				if didAbort {
-					return nil, errors.WithMessagef(err, "Aborted SendToPreferred gateway proxy %s",
-						proxy.GetId().String())
-				}
-				jww.WARN.Printf("Unable to SendToPreferred %s via proxy "+
-					"%s: %s", target, proxy.GetId(), err)
-				wasReplaced, err := s.checkReplace(proxy.GetId(), err)
-				if err != nil {
-					jww.ERROR.Printf("Unable to checkReplace: %+v", err)
-				}
-				// If the proxy was replaced, add as a bad proxy
-				if wasReplaced {
-					badProxies[proxy.String()] = nil
+			} else if strings.Contains(err.Error(), RetryableError) {
+				// Retry of the proxy could not communicate
+				jww.INFO.Printf("Unable to SendToPreferred second pass %s via %s: non-fatal error received, retrying: %s",
+					target, proxy, err)
+			} else if strings.Contains(err.Error(), "unable to connect to target host") {
+				// Retry of the proxy could not communicate
+				jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s: %s,"+
+					" proxy could not contact requested host",
+					target, proxy, err)
+				continue
+			} else if replaced, checkReplaceErr := s.checkReplace(proxy.GetId(), err); replaced {
+				if checkReplaceErr != nil {
+					jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s,"+
+						"proxy failed, was replaced with error: %s", target, proxy.GetId(),
+						checkReplaceErr)
+				} else {
+					jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s, "+
+						"proxy failed, was replaced", target, proxy.GetId())
 				}
+
+				badProxies[proxy.String()] = nil
+				continue
+			} else {
+				jww.WARN.Printf("Unable to SendToPreferred second pass %s via %s: %s, comm returned an error",
+					target, proxy.GetId(), err)
+				return result, err
 			}
 		}
 	}
diff --git a/network/gateway/utils_test.go b/network/gateway/utils_test.go
index 9f75ace1429e947ef7c8a399f3f088e2583777de..09081da17cc895becdd069ec7b794d53a11bc8e3 100644
--- a/network/gateway/utils_test.go
+++ b/network/gateway/utils_test.go
@@ -90,55 +90,67 @@ func getTestNdf(face interface{}) *ndf.NetworkDefinition {
 		Nodes: []ndf.Node{{
 			ID:      id.NewIdFromUInt(0, id.Node, face)[:],
 			Address: "0.0.0.1",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(1, id.Node, face)[:],
 			Address: "0.0.0.2",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(2, id.Node, face)[:],
 			Address: "0.0.0.3",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(3, id.Node, face)[:],
 			Address: "0.0.0.1",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(4, id.Node, face)[:],
 			Address: "0.0.0.2",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(5, id.Node, face)[:],
 			Address: "0.0.0.3",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(6, id.Node, face)[:],
 			Address: "0.0.0.1",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(7, id.Node, face)[:],
 			Address: "0.0.0.2",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(8, id.Node, face)[:],
 			Address: "0.0.0.3",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(9, id.Node, face)[:],
 			Address: "0.0.0.1",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(10, id.Node, face)[:],
 			Address: "0.0.0.2",
+			Status:  ndf.Active,
 		}, {
 			ID:      id.NewIdFromUInt(11, id.Node, face)[:],
 			Address: "0.0.0.3",
+			Status:  ndf.Active,
 		}},
 	}
 }
 
 const happyPathReturn = "happyPathReturn"
 
-func SendToPreferred_HappyPath(host *connect.Host, target *id.ID) (interface{}, bool, error) {
-	return happyPathReturn, false, nil
+func SendToPreferred_HappyPath(host *connect.Host, target *id.ID) (interface{}, error) {
+	return happyPathReturn, nil
 }
 
-func SendToPreferred_KnownError(host *connect.Host, target *id.ID) (interface{}, bool, error) {
-	return nil, false, fmt.Errorf(errorsList[0])
+func SendToPreferred_KnownError(host *connect.Host, target *id.ID) (interface{}, error) {
+	return nil, fmt.Errorf(errorsList[0])
 }
 
-func SendToPreferred_UnknownError(host *connect.Host, target *id.ID) (interface{}, bool, error) {
-	return nil, false, fmt.Errorf("Unexpected error: Oopsie")
+func SendToPreferred_UnknownError(host *connect.Host, target *id.ID) (interface{}, error) {
+	return nil, fmt.Errorf("Unexpected error: Oopsie")
 }
 
 func SendToAny_HappyPath(host *connect.Host) (interface{}, error) {
diff --git a/network/health/tracker.go b/network/health/tracker.go
index 7d5b6a7d9c36edd2723685276c385c114b20bc13..8be8c05ff98b7a37f9fad79e3b6572c846bf9f61 100644
--- a/network/health/tracker.go
+++ b/network/health/tracker.go
@@ -144,6 +144,7 @@ func (t *Tracker) setHealth(h bool) {
 func (t *Tracker) Start() (stoppable.Stoppable, error) {
 	t.mux.Lock()
 	if t.running {
+		t.mux.Unlock()
 		return nil, errors.New("cannot start Health tracker threads, " +
 			"they are already running")
 	}
@@ -162,8 +163,6 @@ func (t *Tracker) Start() (stoppable.Stoppable, error) {
 // start starts a long-running thread used to monitor and report on network
 // health.
 func (t *Tracker) start(stop *stoppable.Single) {
-	timer := time.NewTimer(t.timeout)
-
 	for {
 		var heartbeat network.Heartbeat
 		select {
@@ -178,19 +177,13 @@ func (t *Tracker) start(stop *stoppable.Single) {
 
 			return
 		case heartbeat = <-t.heartbeat:
+			// FIXME: There's no transition to unhealthy here
+			// and there needs to be after some number of bad
+			// polls
 			if healthy(heartbeat) {
-				// Stop and reset timer
-				if !timer.Stop() {
-					select {
-					// per docs explicitly drain
-					case <-timer.C:
-					default:
-					}
-				}
-				timer.Reset(t.timeout)
 				t.setHealth(true)
 			}
-		case <-timer.C:
+		case <-time.After(t.timeout):
 			if !t.isHealthy {
 				jww.WARN.Printf("Network health tracker timed out, network is no longer healthy...")
 			}
diff --git a/network/manager.go b/network/manager.go
index e26c455b39c25b594780ef7695f84bf207414b9b..35f3c668247586618ea3d52e442b3800bc2efd76 100644
--- a/network/manager.go
+++ b/network/manager.go
@@ -34,7 +34,7 @@ import (
 )
 
 // Manager implements the NetworkManager interface inside context. It
-// controls access to network resources and implements all of the communications
+// controls access to network resources and implements all the communications
 // functions used by the client.
 type manager struct {
 	// parameters of the network
@@ -50,9 +50,10 @@ type manager struct {
 	message *message.Manager
 
 	//number of polls done in a period of time
-	tracker      *uint64
-	latencySum   uint64
-	numLatencies uint64
+	tracker       *uint64
+	latencySum    uint64
+	numLatencies  uint64
+	verboseRounds *RoundTracker
 
 	// Address space size
 	addrSpace *ephemeral.AddressSpace
@@ -89,6 +90,10 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard,
 		events:    events,
 	}
 
+	if params.VerboseRoundTracking {
+		m.verboseRounds = NewRoundTracker()
+	}
+
 	m.Internal = internal.Internal{
 		Session:          session,
 		Switchboard:      switchboard,
@@ -102,10 +107,15 @@ func NewManager(session *storage.Session, switchboard *switchboard.Switchboard,
 		Events:           events,
 	}
 
+	// Set up node registration chan for network instance
+	m.Instance.SetAddGatewayChan(m.NodeRegistration)
+
 	// Set up gateway.Sender
 	poolParams := gateway.DefaultPoolParams()
 	// Client will not send KeepAlive packets
 	poolParams.HostParams.KaClientOpts.Time = time.Duration(math.MaxInt64)
+	// Enable optimized HostPool initialization
+	poolParams.MaxPings = 50
 	m.sender, err = gateway.NewSender(poolParams, rng,
 		ndf, comms, session, m.NodeRegistration)
 	if err != nil {
@@ -223,3 +233,11 @@ func (m *manager) UnregisterAddressSizeNotification(tag string) {
 func (m *manager) SetPoolFilter(f gateway.Filter) {
 	m.sender.SetFilter(f)
 }
+
+// GetVerboseRounds returns verbose round information
+func (m *manager) GetVerboseRounds() string {
+	if m.verboseRounds == nil {
+		return "Verbose Round tracking not enabled"
+	}
+	return m.verboseRounds.String()
+}
diff --git a/network/message/critical.go b/network/message/critical.go
index 9c5b6bff7c824a925c969459ddb683e38a1faa1d..1714e193b27f959c581c316ffb0c4c1efc496bab 100644
--- a/network/message/critical.go
+++ b/network/message/critical.go
@@ -51,8 +51,9 @@ func (m *Manager) criticalMessages(stop *stoppable.Single) {
 	//critical messages
 	for msg, param, has := critMsgs.Next(); has; msg, param, has = critMsgs.Next() {
 		go func(msg message.Send, param params.E2E) {
-			jww.INFO.Printf("Resending critical message to %s ",
-				msg.Recipient)
+
+			jww.INFO.Printf("Resending critical message to %s with params %#v",
+				msg.Recipient, param)
 			//send the message
 			rounds, _, _, err := m.SendE2E(msg, param, stop)
 			//if the message fail to send, notify the buffer so it can be handled
@@ -61,7 +62,7 @@ func (m *Manager) criticalMessages(stop *stoppable.Single) {
 				jww.ERROR.Printf("Failed to send critical message to %s "+
 					" on notification of healthy network: %+v", msg.Recipient,
 					err)
-				critMsgs.Failed(msg)
+				critMsgs.Failed(msg, param)
 				return
 			}
 			//wait on the results to make sure the rounds were successful
@@ -77,13 +78,13 @@ func (m *Manager) criticalMessages(stop *stoppable.Single) {
 					"to transmit transmit %v/%v paritions on rounds %d: %v "+
 					"round failures, %v timeouts", msg.Recipient,
 					numRoundFail+numTimeOut, len(rounds), rounds, numRoundFail, numTimeOut)
-				critMsgs.Failed(msg)
+				critMsgs.Failed(msg, param)
 				return
 			}
 
 			jww.INFO.Printf("Successful resend of critical message "+
 				"to %s on rounds %d", msg.Recipient, rounds)
-			critMsgs.Succeeded(msg)
+			critMsgs.Succeeded(msg, param)
 		}(msg, param)
 	}
 
diff --git a/network/message/handler.go b/network/message/handler.go
index 08ced621adaddc0b74772e82dfd02862ece95b5b..ed6891a755a885f186cb7352b60c588b61eb6d13 100644
--- a/network/message/handler.go
+++ b/network/message/handler.go
@@ -108,8 +108,8 @@ func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle) {
 			RoundId:        id.Round(bundle.RoundInfo.ID),
 			RoundTimestamp: time.Unix(0, int64(bundle.RoundInfo.Timestamps[states.QUEUED])),
 		}
-		im := fmt.Sprintf("Garbled/RAW Message: keyFP: %v, "+
-			"msgDigest: %s", msg.GetKeyFP(), msg.Digest())
+		im := fmt.Sprintf("Garbled/RAW Message: keyFP: %v, round: %d"+
+			"msgDigest: %s", msg.GetKeyFP(), bundle.Round, msg.Digest())
 		jww.INFO.Print(im)
 		m.Internal.Events.Report(1, "MessageReception", "Garbled", im)
 		m.Session.GetGarbledMessages().Add(msg)
@@ -117,8 +117,8 @@ func (m *Manager) handleMessage(ecrMsg format.Message, bundle Bundle) {
 		return
 	}
 
-	im := fmt.Sprintf("Received message of type %s from %s,"+
-		" msgDigest: %s", encTy, sender, msgDigest)
+	im := fmt.Sprintf("Received message of type %s from %s in round %d,"+
+		" msgDigest: %s", encTy, sender, bundle.Round, msgDigest)
 	jww.INFO.Print(im)
 	m.Internal.Events.Report(2, "MessageReception", "MessagePart", im)
 
diff --git a/network/message/sendCmix.go b/network/message/sendCmix.go
index 2028e435692425f6df01d2bdcbc9d03087c37250..bfb57a56903cb9f4f23e46cae2c59657c0d06da1 100644
--- a/network/message/sendCmix.go
+++ b/network/message/sendCmix.go
@@ -21,11 +21,12 @@ import (
 	"gitlab.com/elixxir/comms/network"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
 	"gitlab.com/xx_network/primitives/netTime"
-	"strings"
+	"time"
 )
 
 // WARNING: Potentially Unsafe
@@ -40,6 +41,23 @@ func (m *Manager) SendCMIX(sender *gateway.Sender, msg format.Message,
 		m.TransmissionID, m.Comms, stop)
 }
 
+func calculateSendTimeout(best *pb.RoundInfo, max time.Duration) time.Duration {
+	RoundStartTime := time.Unix(0,
+		int64(best.Timestamps[states.QUEUED]))
+	// 250ms AFTER the round starts to hear the response.
+	timeout := RoundStartTime.Sub(
+		netTime.Now().Add(250 * time.Millisecond))
+	if timeout > max {
+		timeout = max
+	}
+	// time.Duration is a signed int, so check for negative
+	if timeout < 0 {
+		// TODO: should this produce a warning?
+		timeout = 100 * time.Millisecond
+	}
+	return timeout
+}
+
 // Helper function for sendCmix
 // NOTE: Payloads send are not End to End encrypted, MetaData is NOT protected with
 // this call, see SendE2E for End to End encryption and full privacy protection
@@ -58,6 +76,7 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message,
 
 	timeStart := netTime.Now()
 	attempted := set.New()
+	maxTimeout := sender.GetHostParams().SendTimeout
 
 	jww.INFO.Printf("Looking for round to send cMix message to %s "+
 		"(msgDigest: %s)", recipient, msg.Digest())
@@ -84,6 +103,7 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message,
 			jww.WARN.Printf("Failed to GetUpcomingRealtime (msgDigest: %s): %+v", msg.Digest(), err)
 		}
 		if bestRound == nil {
+			jww.WARN.Printf("Best round on send is nil")
 			continue
 		}
 
@@ -128,19 +148,19 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message,
 			encMsg.Digest(), firstGateway.String())
 
 		// Send the payload
-		sendFunc := func(host *connect.Host, target *id.ID) (interface{}, bool, error) {
+		sendFunc := func(host *connect.Host, target *id.ID) (interface{}, error) {
 			wrappedMsg.Target = target.Marshal()
-			result, err := comms.SendPutMessage(host, wrappedMsg)
+
+			timeout := calculateSendTimeout(bestRound, maxTimeout)
+			result, err := comms.SendPutMessage(host, wrappedMsg,
+				timeout)
 			if err != nil {
 				// fixme: should we provide as a slice the whole topology?
-				warn, err := handlePutMessageError(firstGateway, instance, session, nodeRegistration, recipient.String(), bestRound, err)
-				if warn {
-					jww.WARN.Printf("SendCmix Failed: %+v", err)
-				} else {
-					return result, true, errors.WithMessagef(err, "SendCmix %s", unrecoverableError)
-				}
+				err := handlePutMessageError(firstGateway, instance, session, nodeRegistration, recipient.String(), bestRound, err)
+				return result, errors.WithMessagef(err, "SendCmix %s", unrecoverableError)
+
 			}
-			return result, false, err
+			return result, err
 		}
 		result, err := sender.SendToPreferred([]*id.ID{firstGateway}, sendFunc, stop)
 
@@ -151,14 +171,10 @@ func sendCmixHelper(sender *gateway.Sender, msg format.Message,
 
 		//if the comm errors or the message fails to send, continue retrying.
 		if err != nil {
-			if !strings.Contains(err.Error(), unrecoverableError) {
-				jww.ERROR.Printf("SendCmix failed to send to EphID %d (%s) on "+
-					"round %d, trying a new round: %+v", ephID.Int64(), recipient,
-					bestRound.ID, err)
-				continue
-			}
-
-			return 0, ephemeral.Id{}, err
+			jww.ERROR.Printf("SendCmix failed to send to EphID %d (%s) on "+
+				"round %d, trying a new round: %+v", ephID.Int64(), recipient,
+				bestRound.ID, err)
+			continue
 		}
 
 		// Return if it sends properly
diff --git a/network/message/sendCmixUtils.go b/network/message/sendCmixUtils.go
index 28917e297a1d0f86e27c9dc2837567196bf1cfbd..4e71f5731f775386d9ab3afa1d10316214d67b32 100644
--- a/network/message/sendCmixUtils.go
+++ b/network/message/sendCmixUtils.go
@@ -28,8 +28,14 @@ import (
 
 // Interface for SendCMIX comms; allows mocking this in testing.
 type sendCmixCommsInterface interface {
-	SendPutMessage(host *connect.Host, message *pb.GatewaySlot) (*pb.GatewaySlotResponse, error)
-	SendPutManyMessages(host *connect.Host, messages *pb.GatewaySlots) (*pb.GatewaySlotResponse, error)
+	// SendPutMessage places a cMix message on the gateway to be
+	// sent through cMix.
+	SendPutMessage(host *connect.Host, message *pb.GatewaySlot,
+		timeout time.Duration) (*pb.GatewaySlotResponse, error)
+	// SendPutManyMessages places a list of cMix messages on the gateway
+	// to be sent through cMix.
+	SendPutManyMessages(host *connect.Host, messages *pb.GatewaySlots,
+		timeout time.Duration) (*pb.GatewaySlotResponse, error)
 }
 
 // how much in the future a round needs to be to send to it
@@ -41,15 +47,16 @@ const unrecoverableError = "failed with an unrecoverable error"
 // context. If the error is not among recoverable errors, then the recoverable
 // boolean will be returned false. If the error is among recoverable errors,
 // then the boolean will return true.
+// recoverable means we should try resending to the round
 func handlePutMessageError(firstGateway *id.ID, instance *network.Instance,
 	session *storage.Session, nodeRegistration chan network.NodeGateway,
 	recipientString string, bestRound *pb.RoundInfo,
-	err error) (recoverable bool, returnErr error) {
+	err error) (returnErr error) {
 
 	// If the comm errors or the message fails to send, then continue retrying;
 	// otherwise, return if it sends properly
 	if strings.Contains(err.Error(), "try a different round.") {
-		return false, errors.WithMessagef(err, "Failed to send to [%s] due to "+
+		return errors.WithMessagef(err, "Failed to send to [%s] due to "+
 			"round error with round %d, bailing...",
 			recipientString, bestRound.ID)
 	} else if strings.Contains(err.Error(), "Could not authenticate client. "+
@@ -65,12 +72,12 @@ func handlePutMessageError(firstGateway *id.ID, instance *network.Instance,
 		// Trigger
 		go handleMissingNodeKeys(instance, nodeRegistration, []*id.ID{nodeID})
 
-		return true, errors.WithMessagef(err, "Failed to send to [%s] via %s "+
+		return errors.WithMessagef(err, "Failed to send to [%s] via %s "+
 			"due to failed authentication, retrying...",
 			recipientString, firstGateway)
 	}
 
-	return false, errors.WithMessage(err, "Failed to put cmix message")
+	return errors.WithMessage(err, "Failed to put cmix message")
 
 }
 
diff --git a/network/message/sendCmix_test.go b/network/message/sendCmix_test.go
index d3d7e116bd4eb5337628adccc87d422b0ae4b327..984e5cd2d01d4f4e687b72f6e8217c9440c980cd 100644
--- a/network/message/sendCmix_test.go
+++ b/network/message/sendCmix_test.go
@@ -56,9 +56,9 @@ func Test_attemptSendCmix(t *testing.T) {
 	nid2 := id.NewIdFromString("jakexx360", id.Node, t)
 	nid3 := id.NewIdFromString("westparkhome", id.Node, t)
 	grp := cyclic.NewGroup(large.NewInt(7), large.NewInt(13))
-	sess1.Cmix().Add(nid1, grp.NewInt(1))
-	sess1.Cmix().Add(nid2, grp.NewInt(2))
-	sess1.Cmix().Add(nid3, grp.NewInt(3))
+	sess1.Cmix().Add(nid1, grp.NewInt(1), 0, nil)
+	sess1.Cmix().Add(nid2, grp.NewInt(2), 0, nil)
+	sess1.Cmix().Add(nid3, grp.NewInt(3), 0, nil)
 
 	timestamps := []uint64{
 		uint64(now.Add(-30 * time.Second).UnixNano()), //PENDING
diff --git a/network/message/sendE2E.go b/network/message/sendE2E.go
index e26dcb6de4ee21a8a72bcebf2dbf7ea26220453d..5f42729f651de44afa707d10b52d2d5c83caf428 100644
--- a/network/message/sendE2E.go
+++ b/network/message/sendE2E.go
@@ -90,8 +90,8 @@ func (m *Manager) SendE2E(msg message.Send, param params.E2E,
 		//end to end encrypt the cmix message
 		msgEnc := key.Encrypt(msgCmix)
 
-		jww.INFO.Printf("E2E sending %d/%d to %s with msgDigest: %s",
-			i+i, len(partitions), msg.Recipient, msgEnc.Digest())
+		jww.INFO.Printf("E2E sending %d/%d to %s with msgDigest: %s, key fp: %s",
+			i+i, len(partitions), msg.Recipient, msgEnc.Digest(), key.Fingerprint())
 
 		//send the cmix message, each partition in its own thread
 		wg.Add(1)
diff --git a/network/message/sendManyCmix.go b/network/message/sendManyCmix.go
index d9e098715a5032c90ed2bef7438f15ce2714f890..3c212f745846c2b60747517d3759a0d019e5528d 100644
--- a/network/message/sendManyCmix.go
+++ b/network/message/sendManyCmix.go
@@ -65,6 +65,8 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message,
 	stream := rng.GetStream()
 	defer stream.Close()
 
+	maxTimeout := sender.GetHostParams().SendTimeout
+
 	recipientString, msgDigests := messageMapToStrings(msgs)
 
 	jww.INFO.Printf("Looking for round to send cMix messages to [%s] "+
@@ -137,20 +139,19 @@ func sendManyCmixHelper(sender *gateway.Sender, msgs map[id.ID]format.Message,
 		}
 
 		// Send the payload
-		sendFunc := func(host *connect.Host, target *id.ID) (interface{}, bool, error) {
+		sendFunc := func(host *connect.Host, target *id.ID) (interface{}, error) {
 			wrappedMessage.Target = target.Marshal()
-			result, err := comms.SendPutManyMessages(host, wrappedMessage)
+			timeout := calculateSendTimeout(bestRound, maxTimeout)
+			result, err := comms.SendPutManyMessages(host,
+				wrappedMessage, timeout)
 			if err != nil {
-				warn, err := handlePutMessageError(firstGateway, instance,
+				err := handlePutMessageError(firstGateway, instance,
 					session, nodeRegistration, recipientString, bestRound, err)
-				if warn {
-					jww.WARN.Printf("SendManyCMIX Failed: %+v", err)
-				} else {
-					return result, false, errors.WithMessagef(err,
-						"SendManyCMIX %s", unrecoverableError)
-				}
+				return result, errors.WithMessagef(err,
+					"SendManyCMIX %s", unrecoverableError)
+
 			}
-			return result, false, err
+			return result, err
 		}
 		result, err := sender.SendToPreferred([]*id.ID{firstGateway}, sendFunc, nil)
 
diff --git a/network/message/sendManyCmix_test.go b/network/message/sendManyCmix_test.go
index 1b0da6c083739765496e111628c5c3a74bdce05a..b787b3abc4445f5266a2cea51ced2429cdddba40 100644
--- a/network/message/sendManyCmix_test.go
+++ b/network/message/sendManyCmix_test.go
@@ -55,9 +55,9 @@ func Test_attemptSendManyCmix(t *testing.T) {
 	nid2 := id.NewIdFromString("jakexx360", id.Node, t)
 	nid3 := id.NewIdFromString("westparkhome", id.Node, t)
 	grp := cyclic.NewGroup(large.NewInt(7), large.NewInt(13))
-	sess1.Cmix().Add(nid1, grp.NewInt(1))
-	sess1.Cmix().Add(nid2, grp.NewInt(2))
-	sess1.Cmix().Add(nid3, grp.NewInt(3))
+	sess1.Cmix().Add(nid1, grp.NewInt(1), 0, nil)
+	sess1.Cmix().Add(nid2, grp.NewInt(2), 0, nil)
+	sess1.Cmix().Add(nid3, grp.NewInt(3), 0, nil)
 
 	timestamps := []uint64{
 		uint64(now.Add(-30 * time.Second).UnixNano()), // PENDING
diff --git a/network/message/utils_test.go b/network/message/utils_test.go
index 18284ad0bb6b6693db062f2fb5b048b5e18ca08f..80b31c9a911a49babe5862078fc864498f3fdb32 100644
--- a/network/message/utils_test.go
+++ b/network/message/utils_test.go
@@ -6,6 +6,7 @@ import (
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/ndf"
 	"testing"
+	"time"
 )
 
 type MockSendCMIXComms struct {
@@ -32,14 +33,14 @@ func (mc *MockSendCMIXComms) RemoveHost(*id.ID) {
 
 }
 
-func (mc *MockSendCMIXComms) SendPutMessage(*connect.Host, *mixmessages.GatewaySlot) (*mixmessages.GatewaySlotResponse, error) {
+func (mc *MockSendCMIXComms) SendPutMessage(*connect.Host, *mixmessages.GatewaySlot, time.Duration) (*mixmessages.GatewaySlotResponse, error) {
 	return &mixmessages.GatewaySlotResponse{
 		Accepted: true,
 		RoundID:  3,
 	}, nil
 }
 
-func (mc *MockSendCMIXComms) SendPutManyMessages(*connect.Host, *mixmessages.GatewaySlots) (*mixmessages.GatewaySlotResponse, error) {
+func (mc *MockSendCMIXComms) SendPutManyMessages(*connect.Host, *mixmessages.GatewaySlots, time.Duration) (*mixmessages.GatewaySlotResponse, error) {
 	return &mixmessages.GatewaySlotResponse{
 		Accepted: true,
 		RoundID:  3,
@@ -100,6 +101,7 @@ func getNDF() *ndf.NetworkDefinition {
 				ID:             nodeId.Marshal(),
 				Address:        "0.0.0.0",
 				TlsCertificate: "",
+				Status:         ndf.Active,
 			},
 		},
 	}
diff --git a/network/node/register.go b/network/node/register.go
index 2aa7a7d203a23cd800b202075f6ee169b1e2592e..a4424eb18b16b98f2b1766f68101fcf7c8fe11b4 100644
--- a/network/node/register.go
+++ b/network/node/register.go
@@ -8,9 +8,9 @@
 package node
 
 import (
-	"crypto/rand"
 	"crypto/sha256"
 	"fmt"
+	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/network/gateway"
@@ -26,18 +26,20 @@ import (
 	"gitlab.com/elixxir/crypto/registration"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/comms/messages"
+	"gitlab.com/xx_network/crypto/chacha"
 	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/crypto/tls"
 	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
 	"strconv"
+	"sync"
 	"time"
 )
 
 type RegisterNodeCommsInterface interface {
-	SendRequestNonceMessage(host *connect.Host,
-		message *pb.NonceRequest) (*pb.Nonce, error)
-	SendConfirmNonceMessage(host *connect.Host,
-		message *pb.RequestRegistrationConfirmation) (*pb.RegistrationConfirmation, error)
+	SendRequestClientKeyMessage(host *connect.Host,
+		message *pb.SignedClientKeyRequest) (*pb.SignedKeyResponse, error)
 }
 
 func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface,
@@ -45,10 +47,12 @@ func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen
 
 	multi := stoppable.NewMulti("NodeRegistrations")
 
+	inProgess := &sync.Map{}
+
 	for i := uint(0); i < numParallel; i++ {
 		stop := stoppable.NewSingle(fmt.Sprintf("NodeRegistration %d", i))
 
-		go registerNodes(sender, session, rngGen, comms, stop, c)
+		go registerNodes(sender, session, rngGen, comms, stop, c, inProgess)
 		multi.Add(stop)
 	}
 
@@ -57,7 +61,7 @@ func StartRegistration(sender *gateway.Sender, session *storage.Session, rngGen
 
 func registerNodes(sender *gateway.Sender, session *storage.Session,
 	rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface,
-	stop *stoppable.Single, c chan network.NodeGateway) {
+	stop *stoppable.Single, c chan network.NodeGateway, inProgress *sync.Map) {
 	u := session.User()
 	regSignature := u.GetTransmissionRegistrationValidationSignature()
 	// Timestamp in which user has registered with registration
@@ -75,8 +79,13 @@ func registerNodes(sender *gateway.Sender, session *storage.Session,
 			stop.ToStopped()
 			return
 		case gw := <-c:
+			nidStr := fmt.Sprintf("%x", gw.Node.ID)
+			if _, operating := inProgress.LoadOrStore(nidStr, struct{}{}); operating {
+				continue
+			}
 			err := registerWithNode(sender, comms, gw, regSignature,
 				regTimestamp, uci, cmix, rng, stop)
+			inProgress.Delete(nidStr)
 			if err != nil {
 				jww.ERROR.Printf("Failed to register node: %+v", err)
 			}
@@ -98,13 +107,6 @@ func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface,
 		return err
 	}
 
-	gwid := ngw.Gateway.ID
-	gatewayID, err := id.Unmarshal(gwid)
-	if err != nil {
-		jww.ERROR.Println("registerWithNode() failed to decode gatewayID")
-		return err
-	}
-
 	if store.IsRegistered(nodeID) {
 		return nil
 	}
@@ -112,6 +114,8 @@ func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface,
 	jww.INFO.Printf("registerWithNode() begin registration with node: %s", nodeID)
 
 	var transmissionKey *cyclic.Int
+	var validUntil uint64
+	var keyId []byte
 	// TODO: should move this to a precanned user initialization
 	if uci.IsPrecanned() {
 		userNum := int(uci.GetTransmissionID().Bytes()[7])
@@ -122,142 +126,159 @@ func registerWithNode(sender *gateway.Sender, comms RegisterNodeCommsInterface,
 		transmissionKey = store.GetGroup().NewIntFromBytes(h.Sum(nil))
 		jww.INFO.Printf("transmissionKey: %v", transmissionKey.Bytes())
 	} else {
-		// Initialise blake2b hash for transmission keys and reception
-		// keys
-		transmissionHash, _ := hash.NewCMixHash()
-
-		nonce, dhPub, err := requestNonce(sender, comms, gatewayID, regSig,
+		// Request key from server
+		transmissionKey, keyId, validUntil, err = requestKey(sender, comms, ngw, regSig,
 			registrationTimestampNano, uci, store, rng, stop)
 
 		if err != nil {
-			return errors.Errorf("Failed to request nonce: %+v", err)
+			return errors.Errorf("Failed to request key: %+v", err)
 		}
 
-		// Load server DH pubkey
-		serverPubDH := store.GetGroup().NewIntFromBytes(dhPub)
-
-		// Confirm received nonce
-		jww.INFO.Printf("Register: Confirming received nonce from node %s", nodeID.String())
-		err = confirmNonce(sender, comms, uci.GetTransmissionID().Bytes(),
-			nonce, uci.GetTransmissionRSA(), gatewayID, stop)
-
-		if err != nil {
-			errMsg := fmt.Sprintf("Register: Unable to confirm nonce: %v", err)
-			return errors.New(errMsg)
-		}
-		transmissionKey = registration.GenerateBaseKey(store.GetGroup(),
-			serverPubDH, store.GetDHPrivateKey(), transmissionHash)
 	}
 
-	store.Add(nodeID, transmissionKey)
+	store.Add(nodeID, transmissionKey, validUntil, keyId)
 
 	jww.INFO.Printf("Completed registration with node %s", nodeID)
 
 	return nil
 }
 
-func requestNonce(sender *gateway.Sender, comms RegisterNodeCommsInterface, gwId *id.ID,
-	regSig []byte, registrationTimestampNano int64, uci *user.CryptographicIdentity,
-	store *cmix.Store, rng csprng.Source, stop *stoppable.Single) ([]byte, []byte, error) {
+func requestKey(sender *gateway.Sender, comms RegisterNodeCommsInterface,
+	ngw network.NodeGateway, regSig []byte, registrationTimestampNano int64,
+	uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source,
+	stop *stoppable.Single) (*cyclic.Int, []byte, uint64, error) {
 
 	dhPub := store.GetDHPublicKey().Bytes()
+
+	// Reconstruct client confirmation message
+	userPubKeyRSA := rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic())
+	confirmation := &pb.ClientRegistrationConfirmation{RSAPubKey: string(userPubKeyRSA), Timestamp: registrationTimestampNano}
+	confirmationSerialized, err := proto.Marshal(confirmation)
+	if err != nil {
+		return nil, nil, 0, err
+	}
+
+	keyRequest := &pb.ClientKeyRequest{
+		Salt: uci.GetTransmissionSalt(),
+		ClientTransmissionConfirmation: &pb.SignedRegistrationConfirmation{
+			RegistrarSignature:             &messages.RSASignature{Signature: regSig},
+			ClientRegistrationConfirmation: confirmationSerialized,
+		},
+		ClientDHPubKey:        dhPub,
+		RegistrationTimestamp: registrationTimestampNano,
+		RequestTimestamp:      netTime.Now().UnixNano(),
+	}
+
+	serializedMessage, err := proto.Marshal(keyRequest)
+	if err != nil {
+		return nil, nil, 0, err
+	}
+
 	opts := rsa.NewDefaultOptions()
 	opts.Hash = hash.CMixHash
-	h, _ := hash.NewCMixHash()
-	h.Write(dhPub)
+	h := opts.Hash.New()
+	h.Write(serializedMessage)
 	data := h.Sum(nil)
 
 	// Sign DH pubkey
 	clientSig, err := rsa.Sign(rng, uci.GetTransmissionRSA(), opts.Hash,
 		data, opts)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, 0, err
+	}
+
+	gwid := ngw.Gateway.ID
+	gatewayID, err := id.Unmarshal(gwid)
+	if err != nil {
+		jww.ERROR.Println("registerWithNode() failed to decode gatewayID")
+		return nil, nil, 0, err
 	}
 
 	// Request nonce message from gateway
-	jww.INFO.Printf("Register: Requesting nonce from gateway %v", gwId.String())
+	jww.INFO.Printf("Register: Requesting client key from gateway %v", gatewayID.String())
 
 	result, err := sender.SendToAny(func(host *connect.Host) (interface{}, error) {
-		nonceResponse, err := comms.SendRequestNonceMessage(host,
-			&pb.NonceRequest{
-				Salt:            uci.GetTransmissionSalt(),
-				ClientRSAPubKey: string(rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic())),
-				ClientSignedByServer: &messages.RSASignature{
-					Signature: regSig,
-				},
-				ClientDHPubKey: dhPub,
-				RequestSignature: &messages.RSASignature{
-					Signature: clientSig,
-				},
-				Target: gwId.Marshal(),
-				// Timestamp in which user has registered with registration
-				TimeStamp: registrationTimestampNano,
+		keyResponse, err := comms.SendRequestClientKeyMessage(host,
+			&pb.SignedClientKeyRequest{
+				ClientKeyRequest:          serializedMessage,
+				ClientKeyRequestSignature: &messages.RSASignature{Signature: clientSig},
+				Target:                    gatewayID.Bytes(),
 			})
 		if err != nil {
-			errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err)
-			return nil, errors.New(errMsg)
+			return nil, errors.WithMessage(err, "Register: Failed requesting client key from gateway")
 		}
-		if nonceResponse.Error != "" {
-			err := errors.New(fmt.Sprintf("requestNonce: nonceResponse error: %s", nonceResponse.Error))
-			return nil, err
+		if keyResponse.Error != "" {
+			return nil, errors.WithMessage(err, "requestKey: clientKeyResponse error")
 		}
-		return nonceResponse, nil
+		return keyResponse, nil
 	}, stop)
 
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, 0, err
 	}
-	nonceResponse := result.(*pb.Nonce)
 
-	// Use Client keypair to sign Server nonce
-	return nonceResponse.Nonce, nonceResponse.DHPubKey, nil
-}
+	signedKeyResponse := result.(*pb.SignedKeyResponse)
+	if signedKeyResponse.Error != "" {
+		return nil, nil, 0, errors.New(signedKeyResponse.Error)
+	}
 
-// confirmNonce is a helper for the Register function
-// It signs a nonce and sends it for confirmation
-// Returns nil if successful, error otherwise
-func confirmNonce(sender *gateway.Sender, comms RegisterNodeCommsInterface, UID,
-	nonce []byte, privateKeyRSA *rsa.PrivateKey, gwID *id.ID,
-	stop *stoppable.Single) error {
-	opts := rsa.NewDefaultOptions()
-	opts.Hash = hash.CMixHash
-	h, _ := hash.NewCMixHash()
-	h.Write(nonce)
-	// Hash the ID of the node we are sending to
-	nodeId := gwID.DeepCopy()
-	nodeId.SetType(id.Node)
-	h.Write(nodeId.Bytes())
-	data := h.Sum(nil)
+	// Hash the response
+	h.Reset()
+	h.Write(signedKeyResponse.KeyResponse)
+	hashedResponse := h.Sum(nil)
 
-	// Hash nonce & sign
-	sig, err := rsa.Sign(rand.Reader, privateKeyRSA, opts.Hash, data, opts)
+	// Load node certificate
+	gatewayCert, err := tls.LoadCertificate(ngw.Gateway.TlsCertificate)
 	if err != nil {
-		jww.ERROR.Printf(
-			"Register: Unable to sign nonce! %s", err)
-		return err
+		return nil, nil, 0, errors.WithMessagef(err, "Unable to load node's certificate")
 	}
 
-	// Send signed nonce to Server
-	// TODO: This returns a receipt that can be used to speed up registration
-	msg := &pb.RequestRegistrationConfirmation{
-		UserID: UID,
-		NonceSignedByClient: &messages.RSASignature{
-			Signature: sig,
-		},
-		Target: gwID.Marshal(),
+	// Extract public key
+	nodePubKey, err := tls.ExtractPublicKey(gatewayCert)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessagef(err, "Unable to load node's public key")
 	}
 
-	_, err = sender.SendToAny(func(host *connect.Host) (interface{}, error) {
-		confirmResponse, err := comms.SendConfirmNonceMessage(host, msg)
-		if err != nil {
-			return nil, err
-		} else if confirmResponse.Error != "" {
-			err := errors.New(fmt.Sprintf(
-				"confirmNonce: Error confirming nonce: %s", confirmResponse.Error))
-			return nil, err
-		}
-		return confirmResponse, nil
-	}, stop)
+	// Verify the response signature
+	err = rsa.Verify(nodePubKey, opts.Hash, hashedResponse,
+		signedKeyResponse.KeyResponseSignedByGateway.Signature, opts)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessagef(err, "Could not verify node's signature")
+	}
+
+	// Unmarshal the response
+	keyResponse := &pb.ClientKeyResponse{}
+	err = proto.Unmarshal(signedKeyResponse.KeyResponse, keyResponse)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessagef(err, "Failed to unmarshal client key response")
+	}
+
+	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)
 
-	return err
+	// Verify the HMAC
+	h.Reset()
+	if !registration.VerifyClientHMAC(sessionKey.Bytes(), keyResponse.EncryptedClientKey,
+		h, keyResponse.EncryptedClientKeyHMAC) {
+		return nil, nil, 0, errors.WithMessagef(err, "Failed to verify client HMAC")
+	}
+
+	// Decrypt the client key
+	clientKey, err := chacha.Decrypt(sessionKey.Bytes(), keyResponse.EncryptedClientKey)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessagef(err, "Failed to decrypt client key")
+	}
+
+	// Construct the transmission key from the client key
+	transmissionKey := store.GetGroup().NewIntFromBytes(clientKey)
+
+	// Use Client keypair to sign Server nonce
+	return transmissionKey, keyResponse.KeyID, keyResponse.ValidUntil, nil
 }
diff --git a/network/node/register_test.go b/network/node/register_test.go
index 95d0765bfdfa47d2443ff45245d770d1eafaeb8b..7f89f4ed9cb4f849f41bd84bed1b4671234e8e84 100644
--- a/network/node/register_test.go
+++ b/network/node/register_test.go
@@ -39,23 +39,13 @@ func NewMockClientComms() *MockClientComms {
 func (mcc *MockClientComms) GetHost(hostId *id.ID) (*connect.Host, bool) {
 	return &connect.Host{}, true
 }
-func (mcc *MockClientComms) SendRequestNonceMessage(host *connect.Host,
-	message *pb.NonceRequest) (*pb.Nonce, error) {
+func (mcc *MockClientComms) SendRequestClientKeyMessage(host *connect.Host,
+	message *pb.SignedClientKeyRequest) (*pb.SignedKeyResponse, error) {
 	// Use this channel to confirm that request nonce was called
 	mcc.request <- true
-	return &pb.Nonce{
-		Nonce:    []byte("nonce"),
-		DHPubKey: []byte("dhpub"),
-	}, nil
-}
-func (mcc *MockClientComms) SendConfirmNonceMessage(host *connect.Host,
-	message *pb.RequestRegistrationConfirmation) (*pb.RegistrationConfirmation, error) {
-	// Use this channel to confirm that confirm nonce was called
-	mcc.confirm <- true
-	return &pb.RegistrationConfirmation{
-		ClientSignedByServer: nil,
-		ClientGatewayKey:     nil,
-		Error:                "",
+	return &pb.SignedKeyResponse{
+		KeyResponse:      []byte("nonce"),
+		ClientGatewayKey: []byte("dhpub"),
 	}, nil
 }
 
diff --git a/network/roundTracking.go b/network/roundTracking.go
new file mode 100644
index 0000000000000000000000000000000000000000..b0cf1bf9cfc61fccad3bb02f7aed7488c29bbbbe
--- /dev/null
+++ b/network/roundTracking.go
@@ -0,0 +1,91 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2021 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// this is an in memory track of rounds that have been processed in this run of the
+// xxdk. It only is enabled when loglevel is debug or higher. It will accumulate all
+// rounds and then dump on exist. Is only enabled when run though the command line
+// interface unless enabled explicitly in code.
+
+package network
+
+import (
+	"fmt"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/xx_network/primitives/id"
+	"sort"
+	"sync"
+)
+
+type RoundState uint8
+
+const (
+	Unchecked = iota
+	Unknown
+	NoMessageAvailable
+	MessageAvailable
+	Abandoned
+)
+
+func (rs RoundState) String() string {
+	switch rs {
+	case Unchecked:
+		return "Unchecked"
+	case Unknown:
+		return "Unknown"
+	case MessageAvailable:
+		return "Message Available"
+	case NoMessageAvailable:
+		return "No Message Available"
+	case Abandoned:
+		return "Abandoned"
+	default:
+		return fmt.Sprintf("Unregistered Round State: %d", rs)
+	}
+}
+
+type RoundTracker struct {
+	state map[id.Round]RoundState
+	mux   sync.Mutex
+}
+
+func NewRoundTracker() *RoundTracker {
+	return &RoundTracker{
+		state: make(map[id.Round]RoundState),
+	}
+}
+
+func (rt *RoundTracker) denote(rid id.Round, state RoundState) {
+	rt.mux.Lock()
+	defer rt.mux.Unlock()
+	// this ensures a lower state will not overwrite a higher state.
+	// eg. Unchecked does not overwrite MessageAvailable
+	if storedState, exists := rt.state[rid]; exists && storedState > state {
+		jww.TRACE.Printf("did not denote round %d because "+
+			"stored state of %s (%d) > passed state %s (%d)",
+			rid, storedState, storedState, state, state)
+		return
+	}
+	rt.state[rid] = state
+}
+
+func (rt *RoundTracker) String() string {
+	rt.mux.Lock()
+	defer rt.mux.Unlock()
+	jww.DEBUG.Printf("Debug Printing status of %d rounds", len(rt.state))
+	keys := make([]int, 0, len(rt.state))
+	for key := range rt.state {
+		keys = append(keys, int(key))
+	}
+
+	sort.Ints(keys)
+
+	stringification := ""
+	for _, key := range keys {
+		stringification += fmt.Sprintf("Round: %d, state:%s \n", key, rt.state[id.Round(key)])
+	}
+
+	return stringification
+}
diff --git a/network/rounds/check.go b/network/rounds/check.go
index 03cd83718495b71ef5455e9112dfe95fdda89fe3..3b9eb2d2849400d2fea2227373839cbc0def44e6 100644
--- a/network/rounds/check.go
+++ b/network/rounds/check.go
@@ -63,6 +63,12 @@ func (m *Manager) GetMessagesFromRound(roundID id.Round, identity reception.Iden
 		jww.INFO.Printf("Messages found in round %d for %d (%s), looking "+
 			"up messages via historical lookup", roundID, identity.EphId.Int64(),
 			identity.Source)
+		//store the round as an unretreived round
+		err = m.Session.UncheckedRounds().AddRound(roundID, nil,
+			identity.Source, identity.EphId)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", roundID)
+		}
 		// If we didn't find it, send to Historical Rounds Retrieval
 		m.historicalRounds <- historicalRoundRequest{
 			rid:         roundID,
@@ -73,6 +79,12 @@ func (m *Manager) GetMessagesFromRound(roundID id.Round, identity reception.Iden
 		jww.INFO.Printf("Messages found in round %d for %d (%s), looking "+
 			"up messages via in ram lookup", roundID, identity.EphId.Int64(),
 			identity.Source)
+		//store the round as an unretreived round
+		err = m.Session.UncheckedRounds().AddRound(roundID, ri,
+			identity.Source, identity.EphId)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", roundID)
+		}
 		// If found, send to Message Retrieval Workers
 		m.lookupRoundMessages <- roundLookup{
 			roundInfo: ri,
diff --git a/network/rounds/remoteFilters_test.go b/network/rounds/remoteFilters_test.go
index 73ce8476be59b4c814cb1a3d42ec8d6d8791db3d..6addedec8555e5bf7a55dec115813d8af0ac7993 100644
--- a/network/rounds/remoteFilters_test.go
+++ b/network/rounds/remoteFilters_test.go
@@ -118,7 +118,7 @@ func TestValidFilterRange(t *testing.T) {
 		Identity: reception.Identity{
 			EphId:      expectedEphID,
 			Source:     requestGateway,
-			StartValid: time.Now().Add(12 * time.Hour),
+			StartValid: time.Now().Add(-12 * time.Hour),
 			EndValid:   time.Now().Add(24 * time.Hour),
 		},
 	}
diff --git a/network/rounds/retrieve.go b/network/rounds/retrieve.go
index caf5ce0e70d2b688a2045feb8c0dd21bcecdee6a..67809eb876ec94b3774ae530918f3ca23f487972 100644
--- a/network/rounds/retrieve.go
+++ b/network/rounds/retrieve.go
@@ -11,6 +11,7 @@ import (
 	"encoding/binary"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/network/gateway"
 	"gitlab.com/elixxir/client/network/message"
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/client/storage/reception"
@@ -48,11 +49,10 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms,
 		case rl := <-m.lookupRoundMessages:
 			ri := rl.roundInfo
 			jww.DEBUG.Printf("Checking for messages in round %d", ri.ID)
-			err := m.Session.UncheckedRounds().AddRound(rl.roundInfo,
-				rl.identity.EphId, rl.identity.Source)
+			err := m.Session.UncheckedRounds().AddRound(id.Round(ri.ID), nil,
+				rl.identity.Source, rl.identity.EphId)
 			if err != nil {
-				jww.ERROR.Printf("Could not add round %d in unchecked rounds store: %v",
-					rl.roundInfo.ID, err)
+				jww.FATAL.Panicf("Failed to denote Unchecked Round for round %d", id.Round(ri.ID))
 			}
 
 			// Convert gateways in round to proper ID format
@@ -120,7 +120,7 @@ func (m *Manager) processMessageRetrieval(comms messageRetrievalComms,
 
 			if len(bundle.Messages) != 0 {
 				jww.DEBUG.Printf("Removing round %d from unchecked store", ri.ID)
-				err = m.Session.UncheckedRounds().Remove(id.Round(ri.ID))
+				err = m.Session.UncheckedRounds().Remove(id.Round(ri.ID), rl.identity.Source, rl.identity.EphId)
 				if err != nil {
 					jww.ERROR.Printf("Could not remove round %d "+
 						"from unchecked rounds store: %v", ri.ID, err)
@@ -143,7 +143,7 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round,
 	stop *stoppable.Single) (message.Bundle, error) {
 	start := time.Now()
 	// Send to the gateways using backup proxies
-	result, err := m.sender.SendToPreferred(gwIds, func(host *connect.Host, target *id.ID) (interface{}, bool, error) {
+	result, err := m.sender.SendToPreferred(gwIds, func(host *connect.Host, target *id.ID) (interface{}, error) {
 		jww.DEBUG.Printf("Trying to get messages for round %v for ephemeralID %d (%v)  "+
 			"via Gateway: %s", roundID, identity.EphId.Int64(), identity.Source.String(), host.GetId())
 
@@ -156,12 +156,18 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round,
 
 		// If the gateway doesnt have the round, return an error
 		msgResp, err := comms.RequestMessages(host, msgReq)
-		if err == nil && !msgResp.GetHasRound() {
-			jww.INFO.Printf("No round error for round %d received from %s", roundID, target)
-			return message.Bundle{}, false, errors.Errorf(noRoundError, roundID)
+
+		if err != nil {
+			//you need to default to a retryable errors because otherwise we cannot enumerate all errors
+			return nil, errors.WithMessage(err, gateway.RetryableError)
+		}
+
+		if !msgResp.GetHasRound() {
+			errRtn := errors.Errorf(noRoundError, roundID)
+			return message.Bundle{}, errors.WithMessage(errRtn, gateway.RetryableError)
 		}
 
-		return msgResp, false, err
+		return msgResp, nil
 	}, stop)
 	jww.INFO.Printf("Received message for round %d, processing...", roundID)
 	// Fail the round if an error occurs so it can be tried again later
@@ -172,13 +178,19 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round,
 	msgResp := result.(*pb.GetMessagesResponse)
 
 	// If there are no messages print a warning. Due to the probabilistic nature
-	// of the bloom filters, false positives will happen some times
+	// of the bloom filters, false positives will happen sometimes
 	msgs := msgResp.GetMessages()
 	if msgs == nil || len(msgs) == 0 {
 		jww.WARN.Printf("no messages for client %s "+
 			" in round %d. This happening every once in a while is normal,"+
 			" but can be indicative of a problem if it is consistent",
 			m.TransmissionID, roundID)
+
+		err = m.Session.UncheckedRounds().Remove(roundID, identity.Source, identity.EphId)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to remove round %d: %+v", roundID, err)
+		}
+
 		return message.Bundle{}, nil
 	}
 
@@ -210,7 +222,7 @@ func (m *Manager) getMessagesFromGateway(roundID id.Round,
 func (m *Manager) forceMessagePickupRetry(ri *pb.RoundInfo, rl roundLookup,
 	comms messageRetrievalComms, gwIds []*id.ID,
 	stop *stoppable.Single) (bundle message.Bundle, err error) {
-	rnd, _ := m.Session.UncheckedRounds().GetRound(id.Round(ri.ID))
+	rnd, _ := m.Session.UncheckedRounds().GetRound(id.Round(ri.ID), rl.identity.Source, rl.identity.EphId)
 	if rnd.NumChecks == 0 {
 		// Flip a coin to determine whether to pick up message
 		stream := m.Rng.GetStream()
diff --git a/network/rounds/unchecked.go b/network/rounds/unchecked.go
index e62bff0c6885d71080b4544cf6602ec684fa2a9a..0618a95c75d07faa2bdc030348bb0c27b68fca84 100644
--- a/network/rounds/unchecked.go
+++ b/network/rounds/unchecked.go
@@ -11,6 +11,8 @@ import (
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/client/storage/rounds"
+	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/netTime"
 	"time"
 )
@@ -47,13 +49,33 @@ func (m *Manager) processUncheckedRounds(checkInterval time.Duration, backoffTab
 			return
 
 		case <-ticker.C:
-			// Pull and iterate through uncheckedRound list
-			roundList := m.Session.UncheckedRounds().GetList()
-			for rid, rnd := range roundList {
+			iterator := func(rid id.Round, rnd rounds.UncheckedRound) {
+				jww.DEBUG.Printf("checking if %d due for a message lookup", rid)
 				// If this round is due for a round check, send the round over
 				// to the retrieval thread. If not due, check next round.
-				if isRoundCheckDue(rnd.NumChecks, rnd.LastCheck, backoffTable) {
-					jww.INFO.Printf("Round %d due for a message lookup, retrying...", rid)
+				if !isRoundCheckDue(rnd.NumChecks, rnd.LastCheck, backoffTable) {
+					return
+				}
+				jww.INFO.Printf("Round %d due for a message lookup, retrying...", rid)
+				//check if it needs to be processed by historical Rounds
+				if rnd.Info == nil {
+					jww.INFO.Printf("Messages in round %d for %d (%s) loaded from unchecked rounds, looking "+
+						"up messages via historical lookup", rnd.Id, rnd.EpdId.Int64(),
+						rnd.Source)
+					// If we didn't find it, send to Historical Rounds Retrieval
+					m.historicalRounds <- historicalRoundRequest{
+						rid: rnd.Id,
+						identity: reception.IdentityUse{
+							Identity: reception.Identity{
+								EphId:  rnd.EpdId,
+								Source: rnd.Source,
+							},
+						},
+						numAttempts: 0,
+					}
+					return
+				} else {
+
 					// Construct roundLookup object to send
 					rl := roundLookup{
 						roundInfo: rnd.Info,
@@ -73,15 +95,16 @@ func (m *Manager) processUncheckedRounds(checkInterval time.Duration, backoffTab
 					}
 
 					// Update the state of the round for next look-up (if needed)
-					err := uncheckedRoundStore.IncrementCheck(rid)
+					err := uncheckedRoundStore.IncrementCheck(rid, rnd.Source, rnd.EpdId)
 					if err != nil {
 						jww.ERROR.Printf("processUncheckedRounds error: Could not "+
 							"increment check attempts for round %d: %v", rid, err)
 					}
 
 				}
-
 			}
+			// Pull and iterate through uncheckedRound list
+			m.Session.UncheckedRounds().IterateOverList(iterator)
 		}
 	}
 }
diff --git a/network/rounds/unchecked_test.go b/network/rounds/unchecked_test.go
index 980ca1e6157a583cc239cab4332e358c5b04e84a..646af6e71598d835d03dd29ae168e12cb5bd8ac8 100644
--- a/network/rounds/unchecked_test.go
+++ b/network/rounds/unchecked_test.go
@@ -63,7 +63,7 @@ func TestUncheckedRoundScheduler(t *testing.T) {
 	}
 
 	// Add round ot check
-	err := testManager.Session.UncheckedRounds().AddRound(roundInfo, expectedEphID, requestGateway)
+	err := testManager.Session.UncheckedRounds().AddRound(roundId, roundInfo, requestGateway, expectedEphID)
 	if err != nil {
 		t.Fatalf("Could not add round to session: %v", err)
 	}
@@ -96,7 +96,8 @@ func TestUncheckedRoundScheduler(t *testing.T) {
 			"\n\tReceived: %v", expectedEphID, testBundle.Identity.EphId)
 	}
 
-	_, exists := testManager.Session.UncheckedRounds().GetRound(roundId)
+	_, exists := testManager.Session.UncheckedRounds().GetRound(
+		roundId, testBundle.Identity.Source, testBundle.Identity.EphId)
 	if exists {
 		t.Fatalf("Expected round %d to be removed after being processed", roundId)
 	}
diff --git a/network/rounds/utils_test.go b/network/rounds/utils_test.go
index e4c41bbf3fe5c69e76ac2aeda3d8766a7f4aaf82..f323c266aa5744034fc369637ba1dcce217caa08 100644
--- a/network/rounds/utils_test.go
+++ b/network/rounds/utils_test.go
@@ -13,11 +13,13 @@ import (
 	"gitlab.com/elixxir/client/network/message"
 	"gitlab.com/elixxir/client/storage"
 	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/testkeys"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/utils"
 	"testing"
 	"time"
 )
@@ -125,7 +127,24 @@ func newTestBackoffTable(face interface{}) [cappedTries]time.Duration {
 }
 
 func getNDF() *ndf.NetworkDefinition {
+	cert, _ := utils.ReadFile(testkeys.GetNodeCertPath())
+	nodeID := id.NewIdFromBytes([]byte("gateway"), &testing.T{})
 	return &ndf.NetworkDefinition{
+		Nodes: []ndf.Node{
+			{
+				ID:             nodeID.Bytes(),
+				Address:        "",
+				TlsCertificate: string(cert),
+				Status:         ndf.Active,
+			},
+		},
+		Gateways: []ndf.Gateway{
+			{
+				ID:             nodeID.Bytes(),
+				Address:        "",
+				TlsCertificate: string(cert),
+			},
+		},
 		E2E: ndf.Group{
 			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
 				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
diff --git a/registration/register.go b/registration/register.go
index 5a21fd97267ab0ae4880ceb9a996ddbdfb09dbd8..57b6cf8f2003b0323d96d9b53aff9dc6e0f1ac2c 100644
--- a/registration/register.go
+++ b/registration/register.go
@@ -8,42 +8,89 @@
 package registration
 
 import (
+	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/crypto/registration"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/crypto/signature/rsa"
 )
 
 func (perm *Registration) Register(transmissionPublicKey, receptionPublicKey *rsa.PublicKey,
-	registrationCode string) ([]byte, []byte, int64, error) {
+	registrationCode string) (transmissionSig []byte, receptionSig []byte, regTimestamp int64, err error) {
 	return register(perm.comms, perm.host, transmissionPublicKey, receptionPublicKey, registrationCode)
 }
 
 // client.Comms should implement this interface
 type registrationMessageSender interface {
-	SendRegistrationMessage(host *connect.Host, message *pb.UserRegistration) (*pb.UserRegistrationConfirmation, error)
+	SendRegistrationMessage(host *connect.Host, message *pb.ClientRegistration) (*pb.SignedClientRegistrationConfirmations, error)
 }
 
 //register registers the user with optional registration code
 // Returns an error if registration fails.
 func register(comms registrationMessageSender, host *connect.Host,
-	transmissionPublicKey, receptionPublicKey *rsa.PublicKey, registrationCode string) ([]byte, []byte, int64, error) {
+	transmissionPublicKey, receptionPublicKey *rsa.PublicKey,
+	registrationCode string) (
+	transmissionSig []byte, receptionSig []byte, regTimestamp int64, err error) {
 
+	// Send the message
+	transmissionPem := string(rsa.CreatePublicKeyPem(transmissionPublicKey))
+	receptionPem := string(rsa.CreatePublicKeyPem(receptionPublicKey))
 	response, err := comms.
 		SendRegistrationMessage(host,
-			&pb.UserRegistration{
-				RegistrationCode:         registrationCode,
-				ClientRSAPubKey:          string(rsa.CreatePublicKeyPem(transmissionPublicKey)),
-				ClientReceptionRSAPubKey: string(rsa.CreatePublicKeyPem(receptionPublicKey)),
+			&pb.ClientRegistration{
+				RegistrationCode:            registrationCode,
+				ClientTransmissionRSAPubKey: transmissionPem,
+				ClientReceptionRSAPubKey:    receptionPem,
 			})
 	if err != nil {
-		err = errors.Wrap(err, "sendRegistrationMessage: Unable to contact Identity Server!")
+		err = errors.Wrap(err, "sendRegistrationMessage: Unable to "+
+			"contact Identity Server!")
 		return nil, nil, 0, err
 	}
 	if response.Error != "" {
-		return nil, nil, 0, errors.Errorf("sendRegistrationMessage: error handling message: %s", response.Error)
+		return nil, nil, 0, errors.Errorf("sendRegistrationMessage: "+
+			"error handling message: %s", response.Error)
 	}
 
-	return response.ClientSignedByServer.Signature,
-		response.ClientReceptionSignedByServer.Signature, response.Timestamp, nil
+	// Unmarshal reception confirmation
+	receptionConfirmation := &pb.ClientRegistrationConfirmation{}
+	err = proto.Unmarshal(response.GetClientReceptionConfirmation().
+		ClientRegistrationConfirmation, receptionConfirmation)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessage(err, "Failed to unmarshal "+
+			"reception confirmation message")
+	}
+
+	transmissionConfirmation := &pb.ClientRegistrationConfirmation{}
+	err = proto.Unmarshal(response.GetClientReceptionConfirmation().
+		ClientRegistrationConfirmation, transmissionConfirmation)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessage(err, "Failed to unmarshal "+
+			"transmission confirmation message")
+	}
+
+	// Verify reception signature
+	receptionSignature := response.GetClientReceptionConfirmation().
+		GetRegistrarSignature().Signature
+	err = registration.VerifyWithTimestamp(host.GetPubKey(),
+		receptionConfirmation.Timestamp, receptionPem,
+		receptionSignature)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessage(err, "Failed to verify reception signature")
+	}
+
+	// Verify transmission signature
+	transmissionSignature := response.GetClientTransmissionConfirmation().
+		GetRegistrarSignature().Signature
+	err = registration.VerifyWithTimestamp(host.GetPubKey(),
+		transmissionConfirmation.Timestamp, transmissionPem,
+		transmissionSignature)
+	if err != nil {
+		return nil, nil, 0, errors.WithMessage(err, "Failed to verify transmission signature")
+	}
+
+	return transmissionSignature,
+		receptionSignature,
+		receptionConfirmation.Timestamp, nil
 }
diff --git a/registration/register_test.go b/registration/register_test.go
index 9840187ce77010144c0c0ccf8674f62a2c766354..d7b95baf4e4581ca96f56605e43eb043a628e46e 100644
--- a/registration/register_test.go
+++ b/registration/register_test.go
@@ -8,104 +8,198 @@
 package registration
 
 import (
+	"bytes"
+	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/testkeys"
+	"gitlab.com/elixxir/crypto/registration"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/comms/messages"
-	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/crypto/signature/rsa"
 	"gitlab.com/xx_network/primitives/id"
-	"reflect"
+	"gitlab.com/xx_network/primitives/utils"
 	"testing"
+	"time"
 )
 
+var expectedSignatureOne = []byte{7, 15, 58, 201, 193, 112, 205, 247, 7, 200, 21, 185, 22, 82, 81, 114, 245, 179, 56, 157, 67, 209, 153, 59, 232, 119, 40, 84, 70, 246, 63, 211, 175, 190, 184, 152, 218, 74, 190, 232, 234, 106, 44, 249, 6, 86, 133, 191, 252, 74, 162, 114, 85, 211, 145, 41, 182, 33, 101, 86, 214, 106, 192, 8, 137, 153, 4, 17, 81, 202, 163, 117, 185, 75, 41, 5, 174, 50, 111, 234, 0, 94, 234, 105, 222, 74, 70, 225, 71, 81, 66, 203, 160, 128, 217, 93, 47, 132, 50, 40, 86, 115, 223, 200, 207, 103, 197, 35, 49, 82, 144, 142, 161, 104, 209, 163, 59, 19, 30, 132, 38, 91, 96, 21, 116, 200, 71, 108, 193, 68, 12, 33, 143, 146, 21, 6, 208, 222, 58, 91, 178, 217, 224, 168, 18, 222, 149, 165, 195, 1, 220, 63, 109, 153, 51, 151, 229, 158, 82, 172, 26, 67, 60, 128, 157, 64, 104, 131, 255, 88, 16, 208, 175, 211, 2, 221, 140, 200, 120, 169, 70, 142, 95, 183, 3, 213, 23, 125, 37, 157, 167, 88, 80, 25, 209, 184, 156, 91, 21, 242, 140, 250, 116, 227, 114, 214, 49, 98, 196, 58, 194, 9, 177, 223, 62, 88, 123, 14, 196, 224, 118, 247, 245, 103, 42, 239, 16, 170, 62, 255, 246, 244, 228, 1, 149, 146, 205, 47, 169, 21, 105, 0, 148, 137, 158, 170, 45, 16, 239, 179, 180, 120, 90, 131, 105, 16}
+var expectedSignatureTwo = []byte{97, 206, 133, 26, 212, 226, 126, 58, 99, 225, 29, 219, 143, 47, 86, 153, 2, 43, 151, 157, 37, 150, 30, 81, 206, 141, 255, 164, 203, 254, 173, 35, 77, 150, 7, 208, 79, 82, 39, 163, 81, 230, 188, 149, 161, 54, 113, 241, 80, 97, 198, 225, 93, 130, 169, 46, 76, 115, 202, 101, 219, 201, 233, 60, 85, 181, 153, 153, 192, 56, 41, 119, 7, 211, 202, 245, 95, 150, 186, 162, 48, 77, 15, 192, 15, 196, 29, 68, 169, 212, 47, 46, 115, 242, 171, 86, 57, 170, 127, 23, 166, 36, 42, 174, 70, 73, 65, 255, 254, 199, 16, 165, 57, 77, 91, 145, 132, 180, 211, 123, 210, 161, 7, 114, 180, 130, 242, 52, 27, 211, 138, 163, 38, 233, 122, 102, 172, 217, 40, 99, 203, 255, 239, 147, 20, 249, 52, 109, 45, 106, 16, 41, 221, 45, 29, 125, 197, 42, 80, 167, 165, 82, 10, 54, 19, 114, 240, 127, 212, 126, 86, 125, 35, 142, 130, 172, 144, 7, 238, 215, 29, 105, 70, 171, 217, 161, 214, 26, 30, 201, 119, 191, 77, 81, 86, 118, 15, 180, 185, 20, 220, 236, 183, 67, 242, 255, 93, 16, 1, 31, 177, 211, 189, 231, 125, 83, 213, 65, 3, 209, 186, 70, 76, 51, 109, 153, 24, 81, 200, 57, 43, 8, 91, 24, 64, 118, 108, 237, 8, 204, 206, 95, 215, 72, 160, 42, 214, 133, 140, 86, 206, 0, 152, 139, 67, 234}
+
+func NewMockRegSender(key, cert []byte) (*MockRegistrationSender, error) {
+	privKey, err := rsa.LoadPrivateKeyFromPem(key)
+	if err != nil {
+		return nil, err
+	}
+
+	// Generate a pre-canned time for consistent testing
+	testTime, err := time.Parse(time.RFC3339,
+		"2012-12-21T22:08:41+00:00")
+	if err != nil {
+		return nil, errors.Errorf("SignVerify error: "+
+			"Could not parse precanned time: %v", err.Error())
+	}
+
+	h, err := connect.NewHost(&id.ClientRegistration, "address", cert,
+		connect.GetDefaultHostParams())
+	if err != nil {
+		return nil, err
+	}
+
+	return &MockRegistrationSender{
+		privKey: privKey,
+		getHost: h,
+		prngOne: &CountingReader{count: 0},
+		prngTwo: &CountingReader{count: 5},
+
+		mockTS: testTime,
+	}, nil
+}
+
 type MockRegistrationSender struct {
-	reg *pb.UserRegistration
+	reg *pb.ClientRegistration
 	// param passed to SendRegistrationMessage
-	host *connect.Host
+	host    *connect.Host
+	privKey *rsa.PrivateKey
+	prngOne *CountingReader
+	prngTwo *CountingReader
+
+	mockTS time.Time
 	// original host returned from GetHost
 	getHost             *connect.Host
-	succeedGetHost      bool
 	errSendRegistration error
 	errInReply          string
 }
 
-func (s *MockRegistrationSender) SendRegistrationMessage(host *connect.Host, message *pb.UserRegistration) (*pb.UserRegistrationConfirmation, error) {
-	s.reg = message
-	s.host = host
-	return &pb.UserRegistrationConfirmation{
-		ClientSignedByServer: &messages.RSASignature{
-			Nonce:     []byte("nonce"),
-			Signature: []byte("sig"),
+func (s *MockRegistrationSender) SendRegistrationMessage(host *connect.Host, message *pb.ClientRegistration) (*pb.SignedClientRegistrationConfirmations, error) {
+	transSig, err := registration.SignWithTimestamp(s.prngOne, s.privKey,
+		s.mockTS.UnixNano(), message.ClientTransmissionRSAPubKey)
+	if err != nil {
+		return nil, errors.Errorf("Failed to sign transmission: %v", err)
+	}
+
+	receptionSig, err := registration.SignWithTimestamp(s.prngTwo, s.privKey,
+		s.mockTS.UnixNano(), message.ClientReceptionRSAPubKey)
+	if err != nil {
+		return nil, errors.Errorf("Failed to sign reception: %v", err)
+	}
+
+	transConfirmation := &pb.ClientRegistrationConfirmation{
+		Timestamp: s.mockTS.UnixNano(),
+		RSAPubKey: message.ClientTransmissionRSAPubKey,
+	}
+
+	receptionConfirmation := &pb.ClientRegistrationConfirmation{
+		Timestamp: s.mockTS.UnixNano(),
+		RSAPubKey: message.ClientReceptionRSAPubKey,
+	}
+
+	transConfirmationData, err := proto.Marshal(transConfirmation)
+	if err != nil {
+		return nil, err
+	}
+
+	receptionConfirmationData, err := proto.Marshal(receptionConfirmation)
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.SignedClientRegistrationConfirmations{
+		ClientTransmissionConfirmation: &pb.SignedRegistrationConfirmation{
+			RegistrarSignature: &messages.RSASignature{
+				Signature: transSig,
+			},
+			ClientRegistrationConfirmation: transConfirmationData,
 		},
-		ClientReceptionSignedByServer: &messages.RSASignature{
-			Nonce:     []byte("receptionnonce"),
-			Signature: []byte("receptionsig"),
+		ClientReceptionConfirmation: &pb.SignedRegistrationConfirmation{
+			RegistrarSignature: &messages.RSASignature{
+				Signature: receptionSig,
+			},
+			ClientRegistrationConfirmation: receptionConfirmationData,
 		},
 		Error: s.errInReply,
 	}, s.errSendRegistration
 }
 
 func (s *MockRegistrationSender) GetHost(*id.ID) (*connect.Host, bool) {
-	return s.getHost, s.succeedGetHost
+	return s.getHost, true
 }
 
 // Shows that we get expected result from happy path
 // Shows that registration gets RPCs with the correct parameters
 func TestRegisterWithPermissioning(t *testing.T) {
-	rng := csprng.NewSystemRNG()
-	key, err := rsa.GenerateKey(rng, 256)
+
+	certData, err := utils.ReadFile(testkeys.GetNodeCertPath())
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Could not load certificate: %v", err)
 	}
 
-	var sender MockRegistrationSender
-	sender.succeedGetHost = true
-	sender.getHost, err = connect.NewHost(&id.Permissioning, "address", nil,
-		connect.GetDefaultHostParams())
+	keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath())
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Could not load private key: %v", err)
 	}
 
-	regCode := "flooble doodle"
-	sig1, sig2, _, err := register(&sender, sender.getHost, key.GetPublic(), key.GetPublic(), regCode)
+	key, err := rsa.LoadPrivateKeyFromPem(keyData)
 	if err != nil {
-		t.Error(err)
+		t.Fatalf("Could not load public key")
 	}
-	if string(sig1) != "sig" {
-		t.Error("expected signature to be 'sig'")
-	}
-	if string(sig2) != "receptionsig" {
-		t.Error("expected signature to be 'receptionsig'")
-	}
-	if sender.host.String() != sender.getHost.String() {
-		t.Errorf("hosts differed. expected %v, got %v", sender.host, sender.getHost)
+
+	sender, err := NewMockRegSender(keyData, certData)
+	if err != nil {
+		t.Fatalf("Failed to create mock sender: %v", err)
 	}
-	passedPub, err := rsa.LoadPublicKeyFromPem([]byte(sender.reg.ClientRSAPubKey))
+
+	regCode := "flooble doodle"
+	sig1, sig2, regTimestamp, err := register(sender, sender.getHost, key.GetPublic(), key.GetPublic(), regCode)
 	if err != nil {
-		t.Error("failed to decode passed public key")
 		t.Error(err)
 	}
-	if !reflect.DeepEqual(passedPub, key.GetPublic()) {
-		t.Error("public keys different from expected")
+
+	if regTimestamp != sender.mockTS.UnixNano() {
+		t.Fatalf("Unexpected timestamp returned from register: "+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", sender.mockTS.UnixNano(), regTimestamp)
+	}
+
+	if !bytes.Equal(expectedSignatureOne, sig1) {
+		t.Fatalf("Unexpected signature one."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", expectedSignatureOne, sig1)
 	}
-	if sender.reg.RegistrationCode != regCode {
-		t.Error("passed regcode different from expected")
+
+	if !bytes.Equal(expectedSignatureTwo, sig2) {
+		t.Fatalf("Unexpected signature two."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", expectedSignatureTwo, sig2)
 	}
+
 }
 
 // Shows that returning an error from the registration server results in an
 // error from register
 func TestRegisterWithPermissioning_ResponseErr(t *testing.T) {
-	rng := csprng.NewSystemRNG()
-	key, err := rsa.GenerateKey(rng, 256)
+	certData, err := utils.ReadFile(testkeys.GetNodeCertPath())
+	if err != nil {
+		t.Fatalf("Could not load certificate: %v", err)
+	}
+
+	keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath())
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Could not load private key: %v", err)
 	}
-	var sender MockRegistrationSender
-	sender.succeedGetHost = true
+
+	key, err := rsa.LoadPrivateKeyFromPem(keyData)
+	if err != nil {
+		t.Fatalf("Could not load public key")
+	}
+
+	sender, err := NewMockRegSender(keyData, certData)
+	if err != nil {
+		t.Fatalf("Failed to create mock sender: %v", err)
+	}
+
 	sender.errInReply = "failure occurred on registration"
-	_, _, _, err = register(&sender, nil, key.GetPublic(), key.GetPublic(), "")
+	_, _, _, err = register(sender, nil, key.GetPublic(), key.GetPublic(), "")
 	if err == nil {
 		t.Error("no error if registration fails on registration")
 	}
@@ -114,16 +208,41 @@ func TestRegisterWithPermissioning_ResponseErr(t *testing.T) {
 // Shows that returning an error from the RPC (e.g. context deadline exceeded)
 // results in an error from register
 func TestRegisterWithPermissioning_ConnectionErr(t *testing.T) {
-	rng := csprng.NewSystemRNG()
-	key, err := rsa.GenerateKey(rng, 256)
+	certData, err := utils.ReadFile(testkeys.GetNodeCertPath())
+	if err != nil {
+		t.Fatalf("Could not load certificate: %v", err)
+	}
+
+	keyData, err := utils.ReadFile(testkeys.GetNodeKeyPath())
+	if err != nil {
+		t.Fatalf("Could not load private key: %v", err)
+	}
+
+	key, err := rsa.LoadPrivateKeyFromPem(keyData)
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("Could not load public key")
+	}
+
+	sender, err := NewMockRegSender(keyData, certData)
+	if err != nil {
+		t.Fatalf("Failed to create mock sender: %v", err)
 	}
-	var sender MockRegistrationSender
-	sender.succeedGetHost = true
 	sender.errSendRegistration = errors.New("connection problem")
-	_, _, _, err = register(&sender, nil, key.GetPublic(), key.GetPublic(), "")
+	_, _, _, err = register(sender, nil, key.GetPublic(), key.GetPublic(), "")
 	if err == nil {
 		t.Error("no error if e.g. context deadline exceeded")
 	}
 }
+
+type CountingReader struct {
+	count uint8
+}
+
+// Read just counts until 254 then starts over again
+func (c *CountingReader) Read(b []byte) (int, error) {
+	for i := 0; i < len(b); i++ {
+		c.count = (c.count + 1) % 255
+		b[i] = c.count
+	}
+	return len(b), nil
+}
diff --git a/single/manager_test.go b/single/manager_test.go
index ac5dd4253820f4fa8f2f50d5cdba079f0c94147c..a1658a0b733bb21df35e17deef53072a76f7cf21 100644
--- a/single/manager_test.go
+++ b/single/manager_test.go
@@ -186,6 +186,11 @@ func TestManager_StartProcesses_Stop(t *testing.T) {
 		t.Errorf("Failed to close: %+v", err)
 	}
 
+	// Wait for the stoppable to close
+	for !stop.IsStopped() {
+		time.Sleep(10 * time.Millisecond)
+	}
+
 	m.swb.(*switchboard.Switchboard).Speak(receiveMsg)
 
 	timer := time.NewTimer(50 * time.Millisecond)
@@ -287,6 +292,10 @@ func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Sing
 	return nil, e2e.MessageID{}, time.Time{}, nil
 }
 
+func (tnm *testNetworkManager) GetVerboseRounds() string {
+	return ""
+}
+
 func (tnm *testNetworkManager) SendUnsafe(_ message.Send, _ params.Unsafe) ([]id.Round, error) {
 	return []id.Round{}, nil
 }
diff --git a/single/receiveResponse.go b/single/receiveResponse.go
index 831b25b358196ddc4806991610b51a0548ab56eb..8f09d8c8d7a98ff142eddaaef40e0bc5bd0a3369 100644
--- a/single/receiveResponse.go
+++ b/single/receiveResponse.go
@@ -18,6 +18,7 @@ import (
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"strings"
 )
 
 // receiveResponseHandler handles the reception of single-use response messages.
@@ -40,10 +41,13 @@ func (m *Manager) receiveResponseHandler(rawMessages chan message.Receive,
 			if err != nil {
 				em := fmt.Sprintf("Failed to read single-use "+
 					"CMIX message response: %+v", err)
-				jww.WARN.Print(em)
-				if m.client != nil {
-					m.client.ReportEvent(9, "SingleUse",
-						"Error", em)
+				if strings.Contains(err.Error(), "no state exists for the reception ID") {
+					jww.TRACE.Print(em)
+				} else {
+					if m.client != nil {
+						m.client.ReportEvent(9, "SingleUse",
+							"Error", em)
+					}
 				}
 			}
 		}
diff --git a/single/reception.go b/single/reception.go
index 23ca6f8bef891b8ae4426fe24172d4a88c2510c5..eca3bca2d2d5b093b0774d8e51b06ec9a763f4ed 100644
--- a/single/reception.go
+++ b/single/reception.go
@@ -108,5 +108,11 @@ func (m *Manager) processTransmission(msg format.Message,
 	c := NewContact(payload.GetRID(transmitMsg.GetPubKey(grp)),
 		transmitMsg.GetPubKey(grp), dhKey, payload.GetTagFP(), payload.GetMaxParts())
 
+	jww.INFO.Printf("generated by singe use receiver reception id for single use: %s, "+
+		"ephId: %v, pubkey: %x, msg: %s",
+		c.partner, "unknown:",
+		transmitMsg.GetPubKey(grp).Bytes(),
+		msg)
+
 	return payload.GetContents(), c, nil
 }
diff --git a/single/response.go b/single/response.go
index e847847b0f5f8206952550dc84d39b05c7e2be24..a5ab3cdb1baee704b214ff694b783c10182471ac 100644
--- a/single/response.go
+++ b/single/response.go
@@ -76,7 +76,7 @@ func (m *Manager) respondSingleUse(partner Contact, payload []byte,
 			// Send Message
 			round, ephID, err := m.net.SendCMIX(cmixMsgFunc, partner.partner, params.GetDefaultCMIX())
 			if err != nil {
-				jww.ERROR.Print("Failed to send single-use response CMIX "+
+				jww.ERROR.Printf("Failed to send single-use response CMIX "+
 					"message part %d: %+v", j, err)
 			}
 			jww.DEBUG.Printf("Sending single-use response CMIX message part "+
diff --git a/single/transmission.go b/single/transmission.go
index 25cd73276fb76eb2226ce7f44d1140cc0b9134b7..1b8bfddc05864b1ae790ba5af0193158f882bc8b 100644
--- a/single/transmission.go
+++ b/single/transmission.go
@@ -289,5 +289,8 @@ func makeIDs(msg *transmitMessagePayload, publicKey *cyclic.Int,
 		jww.DEBUG.Printf("ephemeral.GetId(%s, %d, %d) = %d", rid, addressSize, timeNow.UnixNano(), ephID.Int64())
 	}
 
+	jww.INFO.Printf("generated by singe use sender reception id for single use: %s, "+
+		"ephId: %d, pubkey: %x, msg: %s", rid, ephID.Int64(), publicKey.Bytes(), msg)
+
 	return rid, ephID, nil
 }
diff --git a/single/transmitMessage.go b/single/transmitMessage.go
index 768c47e2697dfe38c731ae0fbd2ffb7759442755..b3915fbe8ae43a87c3e0b7ce5fcff32e9c7adf12 100644
--- a/single/transmitMessage.go
+++ b/single/transmitMessage.go
@@ -9,6 +9,7 @@ package single
 
 import (
 	"encoding/binary"
+	"fmt"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/crypto/cyclic"
@@ -246,3 +247,11 @@ func (mp transmitMessagePayload) SetContents(contents []byte) {
 
 	copy(mp.contents, contents)
 }
+
+// String returns the contents for printing adhering to the stringer interface.
+func (mp transmitMessagePayload) String() string {
+	return fmt.Sprintf("Data: %x [tagFP: %x, nonce: %x, "+
+		"maxParts: %x, size: %x, content: %x]", mp.data, mp.tagFP,
+		mp.nonce, mp.maxParts, mp.size, mp.contents)
+
+}
diff --git a/storage/cmix/key.go b/storage/cmix/key.go
index 3ab6fc084cff007e879cd541afc44dbf910ea10d..6c2954ac4fa65f30b50cd771f73652357b7c19bb 100644
--- a/storage/cmix/key.go
+++ b/storage/cmix/key.go
@@ -8,6 +8,8 @@
 package cmix
 
 import (
+	"bytes"
+	"encoding/binary"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/cyclic"
@@ -18,17 +20,20 @@ import (
 const currentKeyVersion = 0
 
 type key struct {
-	kv *versioned.KV
-	k  *cyclic.Int
-
-	storeKey string
+	kv         *versioned.KV
+	k          *cyclic.Int
+	keyId      []byte
+	validUntil uint64
+	storeKey   string
 }
 
-func newKey(kv *versioned.KV, k *cyclic.Int, id *id.ID) *key {
+func newKey(kv *versioned.KV, k *cyclic.Int, id *id.ID, validUntil uint64, keyId []byte) *key {
 	nk := &key{
-		kv:       kv,
-		k:        k,
-		storeKey: keyKey(id),
+		kv:         kv,
+		k:          k,
+		keyId:      keyId,
+		validUntil: validUntil,
+		storeKey:   keyKey(id),
 	}
 
 	if err := nk.save(); err != nil {
@@ -89,15 +94,59 @@ func (k *key) delete(kv *versioned.KV, id *id.ID) {
 	}
 }
 
-// makes a binary representation of the given key in the keystore
+// makes a binary representation of the given key and key values
+// in the keystore
 func (k *key) marshal() ([]byte, error) {
-	return k.k.GobEncode()
+	buff := bytes.NewBuffer(nil)
+	keyBytes, err := k.k.GobEncode()
+	if err != nil {
+		return nil, err
+	}
+
+	// Write key length
+	b := make([]byte, 8)
+	binary.LittleEndian.PutUint64(b, uint64(len(keyBytes)))
+	buff.Write(b)
+
+	// Write key
+	buff.Write(keyBytes)
+
+	// Write the keyId length
+	binary.LittleEndian.PutUint64(b, uint64(len(k.keyId)))
+	buff.Write(b)
+
+	// Write keyID
+	buff.Write(k.keyId)
+
+	// Write valid until
+	binary.LittleEndian.PutUint64(b, k.validUntil)
+	buff.Write(b)
+
+	return buff.Bytes(), nil
 }
 
 // resets the data of the key from the binary representation of the key passed in
 func (k *key) unmarshal(b []byte) error {
+	buff := bytes.NewBuffer(b)
+
+	// Get the key length
+	keyLen := int(binary.LittleEndian.Uint64(buff.Next(8)))
+
+	// Decode the key length
 	k.k = &cyclic.Int{}
-	return k.k.GobDecode(b)
+	err := k.k.GobDecode(buff.Next(keyLen))
+	if err != nil {
+		return err
+	}
+
+	// Get the keyID length
+	keyIDLen := int(binary.LittleEndian.Uint64(buff.Next(8)))
+	k.keyId = buff.Next(keyIDLen)
+
+	// Get the valid until value
+	k.validUntil = binary.LittleEndian.Uint64(buff.Next(8))
+
+	return nil
 }
 
 // Adheres to the stringer interface
diff --git a/storage/cmix/store.go b/storage/cmix/store.go
index 95a67d7789c3f8f35642e96ef9d81cc864f4b0cb..32b7d506c7de63b4fa901773ff6ba5551747d293 100644
--- a/storage/cmix/store.go
+++ b/storage/cmix/store.go
@@ -34,10 +34,11 @@ type Store struct {
 	nodes        map[id.ID]*key
 	dhPrivateKey *cyclic.Int
 	dhPublicKey  *cyclic.Int
-
-	grp *cyclic.Group
-	kv  *versioned.KV
-	mux sync.RWMutex
+	validUntil   uint64
+	keyId        []byte
+	grp          *cyclic.Group
+	kv           *versioned.KV
+	mux          sync.RWMutex
 }
 
 // NewStore returns a new cMix storage object.
@@ -98,11 +99,12 @@ func LoadStore(kv *versioned.KV) (*Store, error) {
 
 // Add adds the key for a round to the cMix storage object. Saves the updated
 // list of nodes and the key to disk.
-func (s *Store) Add(nid *id.ID, k *cyclic.Int) {
+func (s *Store) Add(nid *id.ID, k *cyclic.Int,
+	validUntil uint64, keyId []byte) {
 	s.mux.Lock()
 	defer s.mux.Unlock()
 
-	nodeKey := newKey(s.kv, k, nid)
+	nodeKey := newKey(s.kv, k, nid, validUntil, keyId)
 
 	s.nodes[*nid] = nodeKey
 	if err := s.save(); err != nil {
diff --git a/storage/cmix/store_test.go b/storage/cmix/store_test.go
index 63329b769b1706dc4a341988a03e28fe84a37642..d84b2dff66723020d5c0b16f7b7d2b0d059911d1 100644
--- a/storage/cmix/store_test.go
+++ b/storage/cmix/store_test.go
@@ -8,6 +8,7 @@
 package cmix
 
 import (
+	"bytes"
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
@@ -16,6 +17,7 @@ import (
 	"gitlab.com/xx_network/crypto/large"
 	"gitlab.com/xx_network/primitives/id"
 	"testing"
+	"time"
 )
 
 // Happy path
@@ -57,9 +59,9 @@ func TestStore_AddRemove(t *testing.T) {
 	testStore, _ := makeTestStore()
 
 	nodeId := id.NewIdFromString("test", id.Node, t)
-	key := testStore.grp.NewInt(5)
-
-	testStore.Add(nodeId, key)
+	k := testStore.grp.NewInt(5)
+	keyId := []byte("keyId")
+	testStore.Add(nodeId, k, 0, keyId)
 	if _, exists := testStore.nodes[*nodeId]; !exists {
 		t.Fatal("Failed to add node key")
 	}
@@ -80,7 +82,7 @@ func TestStore_AddHas(t *testing.T) {
 	nodeId := id.NewIdFromString("test", id.Node, t)
 	key := testStore.grp.NewInt(5)
 
-	testStore.Add(nodeId, key)
+	testStore.Add(nodeId, key, 0, nil)
 	if _, exists := testStore.nodes[*nodeId]; !exists {
 		t.Fatal("Failed to add node key")
 	}
@@ -113,9 +115,17 @@ func TestLoadStore(t *testing.T) {
 
 	// Add a test node key
 	nodeId := id.NewIdFromString("test", id.Node, t)
-	key := testStore.grp.NewInt(5)
+	k := testStore.grp.NewInt(5)
+	testTime, err := time.Parse(time.RFC3339,
+		"2012-12-21T22:08:41+00:00")
+	if err != nil {
+		t.Fatalf("Could not parse precanned time: %v", err.Error())
+	}
+	expectedValid := uint64(testTime.UnixNano())
 
-	testStore.Add(nodeId, key)
+	expectedKeyId := []byte("expectedKeyID")
+
+	testStore.Add(nodeId, k, uint64(expectedValid), expectedKeyId)
 
 	// Load the store and check its attributes
 	store, err := LoadStore(kv)
@@ -131,6 +141,18 @@ func TestLoadStore(t *testing.T) {
 	if len(store.nodes) != len(testStore.nodes) {
 		t.Errorf("LoadStore failed to load node keys")
 	}
+
+	circuit := connect.NewCircuit([]*id.ID{nodeId})
+	keys, _ := store.GetRoundKeys(circuit)
+	if keys.keys[0].validUntil != expectedValid {
+		t.Errorf("Unexpected valid until value loaded from store."+
+			"\n\tExpected: %v\n\tReceived: %v", expectedValid, keys.keys[0].validUntil)
+	}
+	if !bytes.Equal(keys.keys[0].keyId, expectedKeyId) {
+		t.Errorf("Unexpected keyID value loaded from store."+
+			"\n\tExpected: %v\n\tReceived: %v", expectedKeyId, keys.keys[0].keyId)
+	}
+
 }
 
 // Happy path
@@ -145,7 +167,7 @@ func TestStore_GetRoundKeys(t *testing.T) {
 	for i := 0; i < numIds; i++ {
 		nodeIds[i] = id.NewIdFromUInt(uint64(i)+1, id.Node, t)
 		key := testStore.grp.NewInt(int64(i) + 1)
-		testStore.Add(nodeIds[i], key)
+		testStore.Add(nodeIds[i], key, 0, nil)
 
 		// This is wack but it cleans up after the test
 		defer testStore.Remove(nodeIds[i])
@@ -176,8 +198,8 @@ func TestStore_GetRoundKeys_Missing(t *testing.T) {
 
 		// Only add every other node so there are missing nodes
 		if i%2 == 0 {
-			testStore.Add(nodeIds[i], key)
-			testStore.Add(nodeIds[i], key)
+			testStore.Add(nodeIds[i], key, 0, nil)
+			testStore.Add(nodeIds[i], key, 0, nil)
 
 			// This is wack but it cleans up after the test
 			defer testStore.Remove(nodeIds[i])
@@ -211,7 +233,7 @@ func TestStore_Count(t *testing.T) {
 
 	count := 50
 	for i := 0; i < count; i++ {
-		store.Add(id.NewIdFromUInt(uint64(i), id.Node, t), grp.NewInt(int64(42+i)))
+		store.Add(id.NewIdFromUInt(uint64(i), id.Node, t), grp.NewInt(int64(42+i)), 0, nil)
 	}
 
 	if store.Count() != count {
diff --git a/storage/e2e/stateVector.go b/storage/e2e/stateVector.go
index 10306ae562bde79a5d6382d167f83b823ccbc8df..fea42c68cec3475ba27d1e6a16f884c7b8b73463 100644
--- a/storage/e2e/stateVector.go
+++ b/storage/e2e/stateVector.go
@@ -212,17 +212,16 @@ func (sv *stateVector) Delete() error {
 // execute a store and a store must be executed after.
 func (sv *stateVector) nextAvailable() {
 
-	block := (sv.firstAvailable + 1) / 64
-	pos := (sv.firstAvailable + 1) % 64
+	//plus one so we start at the next one
+	pos := sv.firstAvailable + 1
+	block := pos / 64
 
-	for ; block < uint32(len(sv.vect)) && (sv.vect[block]>>pos)&1 == 1; pos++ {
-		if pos == 63 {
-			pos = 0
-			block++
-		}
+	for block < uint32(len(sv.vect)) && (sv.vect[block]>>(pos%64))&1 == 1 {
+		pos++
+		block = pos / 64
 	}
 
-	sv.firstAvailable = block*64 + pos
+	sv.firstAvailable = pos
 }
 
 //ekv functions
diff --git a/storage/e2e/stateVector_test.go b/storage/e2e/stateVector_test.go
index 99fd5cf606088b3d55ddda3f3e6045a04effe9af..e67d251c3c07c4f6ef3014080dee93a88a7846e4 100644
--- a/storage/e2e/stateVector_test.go
+++ b/storage/e2e/stateVector_test.go
@@ -11,6 +11,7 @@ import (
 	"fmt"
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/ekv"
+	"math"
 	"math/bits"
 	"reflect"
 	"testing"
@@ -110,6 +111,31 @@ func TestStateVector_Next(t *testing.T) {
 	}
 }
 
+// Shows that Next mutates vector state as expected
+// Shows that Next can find key indexes all throughout the bitfield
+// A bug was found when the next avalible was in the first index of a word, this tests that case
+func TestStateVector_Next_EdgeCase(t *testing.T) {
+	// Expected results: all keynums, and beyond the last key
+	//expectedFirstAvail := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995}
+
+	const numKeys = 1000
+	sv, err := newStateVector(versioned.NewKV(make(ekv.Memstore)), "key", numKeys)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Set a few clean bits randomly
+	sv.vect[0] = math.MaxUint64
+
+	sv.firstAvailable = 0
+	sv.nextAvailable()
+
+	// firstAvailable should now be beyond the end of the bitfield
+	if sv.firstAvailable != 64 {
+		t.Errorf("Next avalivle skiped the first of the next word, should be 64, is %d", sv.firstAvailable)
+	}
+}
+
 // Shows that Use() mutates the state vector itself
 func TestStateVector_Use(t *testing.T) {
 	// These keyNums will be set to dirty with Use
diff --git a/storage/rounds/checkedRounds.go b/storage/rounds/checkedRounds.go
index a7e3ebdecd425a3ba97902a2197fd3189b7b6f59..df17faa3ad38c7ea84d67e44aee01feb92230925 100644
--- a/storage/rounds/checkedRounds.go
+++ b/storage/rounds/checkedRounds.go
@@ -52,7 +52,7 @@ func newCheckedRounds(maxRounds int, store *utility.BlockStore) *CheckedRounds {
 	return &CheckedRounds{
 		m:         make(map[id.Round]interface{}),
 		l:         list.New(),
-		recent:    []id.Round{},
+		recent:    make([]id.Round, 0, maxRounds),
 		store:     store,
 		maxRounds: maxRounds,
 	}
diff --git a/storage/rounds/earliestRound.go b/storage/rounds/earliestRound.go
index 456e11c146e1998e45c3e9b814d5f718ddf99807..a072419f389d84544ff6029062d5839f0e2b0294 100644
--- a/storage/rounds/earliestRound.go
+++ b/storage/rounds/earliestRound.go
@@ -71,16 +71,19 @@ func (ur *EarliestRound) save() {
 	}
 }
 
-func (ur *EarliestRound) Set(rid id.Round) (id.Round, bool) {
+// Set returns the updated earliest round, the old earliest round, and if they are changed.
+// Updates the earliest round if it is newer than stored one
+func (ur *EarliestRound) Set(rid id.Round) (id.Round, id.Round, bool) {
 	ur.mux.Lock()
 	defer ur.mux.Unlock()
 	changed := false
+	old := ur.rid
 	if rid > ur.rid {
 		changed = true
 		ur.rid = rid
 		ur.save()
 	}
-	return ur.rid, changed
+	return ur.rid, old, changed
 }
 
 func (ur *EarliestRound) Get() id.Round {
diff --git a/storage/rounds/roundIdentity.go b/storage/rounds/roundIdentity.go
new file mode 100644
index 0000000000000000000000000000000000000000..4148ec014eaa238a2d40af1ee51602f7453aa910
--- /dev/null
+++ b/storage/rounds/roundIdentity.go
@@ -0,0 +1,50 @@
+package rounds
+
+import (
+	"encoding/base64"
+	"encoding/binary"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+// roundIdentitySize is the size of a roundIdentity.
+const roundIdentitySize = 32
+
+// roundIdentity uniquely identifies a round ID for a specific identity.
+type roundIdentity [roundIdentitySize]byte
+
+// newRoundIdentity generates a new unique round identifier for the round ID,
+// recipient ID, and ephemeral ID.
+func newRoundIdentity(rid id.Round, recipient *id.ID, ephID ephemeral.Id) roundIdentity {
+	h, _ := hash.NewCMixHash()
+	ridBytes := make([]byte, 8)
+	binary.BigEndian.PutUint64(ridBytes, uint64(rid))
+	h.Write(ridBytes)
+	h.Write(recipient[:])
+	h.Write(ephID[:])
+	riBytes := h.Sum(nil)
+
+	ri := unmarshalRoundIdentity(riBytes)
+
+	return ri
+}
+
+// String prints a base 64 string representation of roundIdentity. This function
+// satisfies the fmt.Stringer interface.
+func (ri roundIdentity) String() string {
+	return base64.StdEncoding.EncodeToString(ri[:])
+}
+
+// Marshal returns the roundIdentity as a byte slice.
+func (ri roundIdentity) Marshal() []byte {
+	return ri[:]
+}
+
+// unmarshalRoundIdentity unmarshalls the byte slice into a roundIdentity.
+func unmarshalRoundIdentity(b []byte) roundIdentity {
+	var ri roundIdentity
+	copy(ri[:], b)
+
+	return ri
+}
diff --git a/storage/rounds/uncheckedRounds.go b/storage/rounds/uncheckedRounds.go
index 898222b71556ef86146d996e23c9804bff356f85..aa5e57497d36c21f970d3dcb599708ba78bf5650 100644
--- a/storage/rounds/uncheckedRounds.go
+++ b/storage/rounds/uncheckedRounds.go
@@ -18,34 +18,40 @@ import (
 	"gitlab.com/xx_network/primitives/id/ephemeral"
 	"gitlab.com/xx_network/primitives/netTime"
 	"sync"
+	"testing"
 	"time"
 )
 
 const (
 	uncheckedRoundVersion = 0
+	roundInfoVersion      = 0
 	uncheckedRoundPrefix  = "uncheckedRoundPrefix"
+	roundKeyPrefix        = "roundInfo:"
+
 	// Key to store rounds
 	uncheckedRoundKey = "uncheckRounds"
-	// Key to store individual round
+
 	// Housekeeping constant (used for serializing uint64 ie id.Round)
 	uint64Size = 8
-	// Maximum checks that can be performed on a round. Intended so that
-	// a round is checked no more than 1 week approximately (network/rounds.cappedTries + 7)
+
+	// Maximum checks that can be performed on a round. Intended so that a round
+	// is checked no more than 1 week approximately (network/rounds.cappedTries + 7)
 	maxChecks = 14
 )
 
-// Round identity information used in message retrieval
-// Derived from reception.Identity saving data needed
-// for message retrieval
+// Identity contains round identity information used in message retrieval.
+// Derived from reception.Identity saving data needed for message retrieval.
 type Identity struct {
 	EpdId  ephemeral.Id
 	Source *id.ID
 }
 
-// Unchecked round structure is rounds which failed on message retrieval
-// These rounds are stored for retry of message retrieval
+// UncheckedRound contains rounds that failed on message retrieval. These rounds
+// are stored for retry of message retrieval.
 type UncheckedRound struct {
 	Info *pb.RoundInfo
+	Id   id.Round
+
 	Identity
 	// Timestamp in which round has last been checked
 	LastCheck time.Time
@@ -53,17 +59,21 @@ type UncheckedRound struct {
 	NumChecks uint64
 }
 
-// marshal serializes UncheckedRound r into a byte slice
-func (r UncheckedRound) marshal() ([]byte, error) {
+// marshal serializes UncheckedRound r into a byte slice.
+func (r UncheckedRound) marshal(kv *versioned.KV) ([]byte, error) {
 	buf := bytes.NewBuffer(nil)
-	// Write the round info
+	// Store teh round info
+	if r.Info != nil {
+		if err := storeRoundInfo(kv, r.Info, r.Source, r.EpdId); err != nil {
+			return nil, errors.WithMessagef(err,
+				"failed to marshal unchecked rounds")
+		}
+	}
+
+	// Marshal the round ID
 	b := make([]byte, uint64Size)
-	infoBytes, err := proto.Marshal(r.Info)
-	binary.LittleEndian.PutUint64(b, uint64(len(infoBytes)))
+	binary.LittleEndian.PutUint64(b, uint64(r.Id))
 	buf.Write(b)
-	buf.Write(infoBytes)
-
-	b = make([]byte, uint64Size)
 
 	// Write the round identity info
 	buf.Write(r.Identity.EpdId[:])
@@ -91,60 +101,57 @@ func (r UncheckedRound) marshal() ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
-// unmarshal deserializes round data from buff into UncheckedRound r
-func (r *UncheckedRound) unmarshal(buff *bytes.Buffer) error {
+// unmarshal deserializes round data from buff into UncheckedRound r.
+func (r *UncheckedRound) unmarshal(kv *versioned.KV, buff *bytes.Buffer) error {
 	// Deserialize the roundInfo
-	roundInfoLen := binary.LittleEndian.Uint64(buff.Next(uint64Size))
-	roundInfoBytes := buff.Next(int(roundInfoLen))
-	ri := &pb.RoundInfo{}
-	if err := proto.Unmarshal(roundInfoBytes, ri); err != nil {
-		return errors.WithMessagef(err, "Failed to unmarshal roundInfo")
-	}
-	r.Info = ri
+	r.Id = id.Round(binary.LittleEndian.Uint64(buff.Next(uint64Size)))
 
 	// Deserialize the round identity information
 	copy(r.EpdId[:], buff.Next(uint64Size))
 
 	sourceId, err := id.Unmarshal(buff.Next(id.ArrIDLen))
 	if err != nil {
-		return errors.WithMessage(err, "Failed to unmarshal round identity.source")
+		return errors.WithMessagef(err,
+			"Failed to unmarshal round identity.source of %d", r.Id)
 	}
 
 	r.Source = sourceId
 
 	// Deserialize the timestamp bytes
 	timestampLen := binary.LittleEndian.Uint64(buff.Next(uint64Size))
-	tsByes := buff.Next(int(uint64(timestampLen)))
+	tsByes := buff.Next(int(timestampLen))
 	if err = r.LastCheck.UnmarshalBinary(tsByes); err != nil {
-		return errors.WithMessage(err, "Failed to unmarshal round timestamp")
+		return errors.WithMessagef(err,
+			"Failed to unmarshal round timestamp of %d", r.Id)
 	}
 
 	r.NumChecks = binary.LittleEndian.Uint64(buff.Next(uint64Size))
 
+	r.Info, _ = loadRoundInfo(kv, r.Id, r.Source, r.EpdId)
+
 	return nil
 }
 
-// Storage object saving rounds to retry for message retrieval
+// UncheckedRoundStore stores rounds to retry for message retrieval.
 type UncheckedRoundStore struct {
-	list map[id.Round]UncheckedRound
+	list map[roundIdentity]UncheckedRound
 	mux  sync.RWMutex
 	kv   *versioned.KV
 }
 
-// Constructor for a UncheckedRoundStore
+// NewUncheckedStore is a constructor for a UncheckedRoundStore.
 func NewUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) {
 	kv = kv.Prefix(uncheckedRoundPrefix)
 
 	urs := &UncheckedRoundStore{
-		list: make(map[id.Round]UncheckedRound, 0),
+		list: make(map[roundIdentity]UncheckedRound, 0),
 		kv:   kv,
 	}
 
 	return urs, urs.save()
-
 }
 
-// Loads an deserializes a UncheckedRoundStore from memory
+// LoadUncheckedStore loads a deserializes a UncheckedRoundStore from memory.
 func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) {
 
 	kv = kv.Prefix(uncheckedRoundPrefix)
@@ -154,7 +161,7 @@ func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) {
 	}
 
 	urs := &UncheckedRoundStore{
-		list: make(map[id.Round]UncheckedRound),
+		list: make(map[roundIdentity]UncheckedRound),
 		kv:   kv,
 	}
 
@@ -166,13 +173,16 @@ func LoadUncheckedStore(kv *versioned.KV) (*UncheckedRoundStore, error) {
 	return urs, err
 }
 
-// Adds a round to check on the list and saves to memory
-func (s *UncheckedRoundStore) AddRound(ri *pb.RoundInfo, ephID ephemeral.Id, source *id.ID) error {
+// AddRound adds a round to check on the list and saves to memory.
+func (s *UncheckedRoundStore) AddRound(rid id.Round, ri *pb.RoundInfo,
+	source *id.ID, ephID ephemeral.Id) error {
 	s.mux.Lock()
 	defer s.mux.Unlock()
-	rid := id.Round(ri.ID)
+	roundId := newRoundIdentity(rid, source, ephID)
+
+	stored, exists := s.list[roundId]
 
-	if _, exists := s.list[rid]; !exists {
+	if !exists || stored.Info == nil {
 		newUncheckedRound := UncheckedRound{
 			Info: ri,
 			Identity: Identity{
@@ -183,42 +193,55 @@ func (s *UncheckedRoundStore) AddRound(ri *pb.RoundInfo, ephID ephemeral.Id, sou
 			NumChecks: 0,
 		}
 
-		s.list[rid] = newUncheckedRound
-
+		s.list[roundId] = newUncheckedRound
 		return s.save()
 	}
 
 	return nil
 }
 
-// Retrieves an UncheckedRound from the map, if it exists
-func (s *UncheckedRoundStore) GetRound(rid id.Round) (UncheckedRound, bool) {
+// GetRound retrieves an UncheckedRound from the map, if it exists.
+func (s *UncheckedRoundStore) GetRound(rid id.Round, recipient *id.ID,
+	ephId ephemeral.Id) (UncheckedRound, bool) {
 	s.mux.RLock()
 	defer s.mux.RUnlock()
-	rnd, exists := s.list[rid]
+	rnd, exists := s.list[newRoundIdentity(rid, recipient, ephId)]
 	return rnd, exists
 }
 
-// Retrieves the list of rounds
-func (s *UncheckedRoundStore) GetList() map[id.Round]UncheckedRound {
+func (s *UncheckedRoundStore) GetList(*testing.T) map[roundIdentity]UncheckedRound {
 	s.mux.RLock()
 	defer s.mux.RUnlock()
 	return s.list
 }
 
-// Increments the amount of checks performed on this stored round
-func (s *UncheckedRoundStore) IncrementCheck(rid id.Round) error {
+// IterateOverList retrieves the list of rounds.
+func (s *UncheckedRoundStore) IterateOverList(iterator func(rid id.Round,
+	rnd UncheckedRound)) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	for _, rnd := range s.list {
+		go iterator(rnd.Id, rnd)
+	}
+}
+
+// IncrementCheck increments the amount of checks performed on this stored
+// round.
+func (s *UncheckedRoundStore) IncrementCheck(rid id.Round, recipient *id.ID,
+	ephId ephemeral.Id) error {
 	s.mux.Lock()
 	defer s.mux.Unlock()
-	rnd, exists := s.list[rid]
+	nri := newRoundIdentity(rid, recipient, ephId)
+	rnd, exists := s.list[nri]
 	if !exists {
 		return errors.Errorf("round %d could not be found in RAM", rid)
 	}
 
-	// If a round has been checked the maximum amount of times,
-	// we bail the round by removing it from store and no longer checking
+	// If a round has been checked the maximum amount of times, then bail the
+	// round by removing it from store and no longer checking
 	if rnd.NumChecks >= maxChecks {
-		if err := s.remove(rid); err != nil {
+		if err := s.remove(rid, rnd.Identity.Source, ephId); err != nil {
 			return errors.WithMessagef(err, "Round %d reached maximum checks "+
 				"but could not be removed", rid)
 		}
@@ -228,33 +251,56 @@ func (s *UncheckedRoundStore) IncrementCheck(rid id.Round) error {
 	// Update the rounds state
 	rnd.LastCheck = netTime.Now()
 	rnd.NumChecks++
-	s.list[rid] = rnd
+	s.list[nri] = rnd
 	return s.save()
 }
 
-// Remove deletes a round from UncheckedRoundStore's list and from storage
-func (s *UncheckedRoundStore) Remove(rid id.Round) error {
+// Remove deletes a round from UncheckedRoundStore's list and from storage.
+func (s *UncheckedRoundStore) Remove(rid id.Round, source *id.ID,
+	ephId ephemeral.Id) error {
 	s.mux.Lock()
 	defer s.mux.Unlock()
-	return s.remove(rid)
+	return s.remove(rid, source, ephId)
 }
 
-// Remove is a helper function which removes the round from UncheckedRoundStore's list
-// Note this method is unsafe and should only be used by methods with a lock
-func (s *UncheckedRoundStore) remove(rid id.Round) error {
-	if _, exists := s.list[rid]; !exists {
+// Remove is a helper function which removes the round from
+// UncheckedRoundStore's list. Note that this method is unsafe and should only
+// be used by methods with a lock.
+func (s *UncheckedRoundStore) remove(rid id.Round, recipient *id.ID,
+	ephId ephemeral.Id) error {
+	roundId := newRoundIdentity(rid, recipient, ephId)
+	ur, exists := s.list[roundId]
+	if !exists {
 		return errors.Errorf("round %d does not exist in store", rid)
 	}
-	delete(s.list, rid)
-	return s.save()
+
+	delete(s.list, roundId)
+	if err := s.save(); err != nil {
+		return errors.WithMessagef(err,
+			"Failed to delete round %d from unchecked round store", rid)
+	}
+
+	// Do not delete round infos if none exist
+	if ur.Info == nil {
+		return nil
+	}
+
+	if err := deleteRoundInfo(s.kv, rid, recipient, ephId); err != nil {
+		return errors.WithMessagef(err,
+			"Failed to delete round %d's roundinfo from unchecked round store, "+
+				"round itself deleted. This is a storage leak", rid)
+	}
+
+	return nil
 }
 
-// save stores the information from the round list into storage
+// save stores the information from the round list into storage.
 func (s *UncheckedRoundStore) save() error {
 	// Store list of rounds
 	data, err := s.marshal()
 	if err != nil {
-		return errors.WithMessagef(err, "Could not marshal data for unchecked rounds")
+		return errors.WithMessagef(err,
+			"Could not marshal data for unchecked rounds")
 	}
 
 	// Create the versioned object
@@ -267,24 +313,27 @@ func (s *UncheckedRoundStore) save() error {
 	// Save to storage
 	err = s.kv.Set(uncheckedRoundKey, uncheckedRoundVersion, obj)
 	if err != nil {
-		return errors.WithMessagef(err, "Could not store data for unchecked rounds")
+		return errors.WithMessagef(err,
+			"Could not store data for unchecked rounds")
 	}
 
 	return nil
 }
 
-// marshal is a helper function which serializes all rounds in list to bytes
+// marshal is a helper function which serializes all rounds in list to bytes.
 func (s *UncheckedRoundStore) marshal() ([]byte, error) {
 	buf := bytes.NewBuffer(nil)
+
 	// Write number of rounds the buffer
 	b := make([]byte, 8)
-	binary.PutVarint(b, int64(len(s.list)))
+	binary.BigEndian.PutUint32(b, uint32(len(s.list)))
 	buf.Write(b)
 
 	for rid, rnd := range s.list {
-		rndData, err := rnd.marshal()
+		rndData, err := rnd.marshal(s.kv)
 		if err != nil {
-			return nil, errors.WithMessagef(err, "Failed to marshal round %d", rid)
+			return nil, errors.WithMessagef(err,
+				"Failed to marshal round %d", rid)
 		}
 
 		buf.Write(rndData)
@@ -294,21 +343,68 @@ func (s *UncheckedRoundStore) marshal() ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
-// unmarshal deserializes an UncheckedRound from its stored byte data
+// unmarshal deserializes an UncheckedRound from its stored byte data.
 func (s *UncheckedRoundStore) unmarshal(data []byte) error {
 	buff := bytes.NewBuffer(data)
+
 	// Get number of rounds in list
-	length, _ := binary.Varint(buff.Next(8))
+	length := binary.BigEndian.Uint32(buff.Next(8))
 
 	for i := 0; i < int(length); i++ {
 		rnd := UncheckedRound{}
-		err := rnd.unmarshal(buff)
+		err := rnd.unmarshal(s.kv, buff)
 		if err != nil {
-			return errors.WithMessage(err, "Failed to unmarshal rounds in storage")
+			return errors.WithMessage(err,
+				"Failed to unmarshal rounds in storage")
 		}
 
-		s.list[id.Round(rnd.Info.ID)] = rnd
+		s.list[newRoundIdentity(rnd.Id, rnd.Source, rnd.EpdId)] = rnd
 	}
 
 	return nil
 }
+
+func storeRoundInfo(kv *versioned.KV, info *pb.RoundInfo, recipient *id.ID,
+	ephID ephemeral.Id) error {
+	now := netTime.Now()
+
+	data, err := proto.Marshal(info)
+	if err != nil {
+		return errors.WithMessagef(err,
+			"Failed to store individual unchecked round")
+	}
+
+	obj := versioned.Object{
+		Version:   roundInfoVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return kv.Set(
+		roundKey(id.Round(info.ID), recipient, ephID), roundInfoVersion, &obj)
+}
+
+func loadRoundInfo(kv *versioned.KV, id id.Round, recipient *id.ID,
+	ephID ephemeral.Id) (*pb.RoundInfo, error) {
+
+	vo, err := kv.Get(roundKey(id, recipient, ephID), roundInfoVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	ri := &pb.RoundInfo{}
+	if err = proto.Unmarshal(vo.Data, ri); err != nil {
+		return nil, errors.WithMessagef(err, "Failed to unmarshal roundInfo")
+	}
+
+	return ri, nil
+}
+
+func deleteRoundInfo(kv *versioned.KV, id id.Round, recipient *id.ID,
+	ephID ephemeral.Id) error {
+	return kv.Delete(roundKey(id, recipient, ephID), roundInfoVersion)
+}
+
+func roundKey(roundID id.Round, recipient *id.ID, ephID ephemeral.Id) string {
+	return roundKeyPrefix + newRoundIdentity(roundID, recipient, ephID).String()
+}
diff --git a/storage/rounds/uncheckedRounds_test.go b/storage/rounds/uncheckedRounds_test.go
index 63e8c195f4f2ffa752979e5f5780acdc6d2d3f16..4afecef152219391e6e3990824cdaa047be0783e 100644
--- a/storage/rounds/uncheckedRounds_test.go
+++ b/storage/rounds/uncheckedRounds_test.go
@@ -24,57 +24,52 @@ func TestNewUncheckedStore(t *testing.T) {
 	kv := versioned.NewKV(make(ekv.Memstore))
 
 	testStore := &UncheckedRoundStore{
-		list: make(map[id.Round]UncheckedRound),
+		list: make(map[roundIdentity]UncheckedRound),
 		kv:   kv.Prefix(uncheckedRoundPrefix),
 	}
 
 	store, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("NewUncheckedStore error: "+
-			"Could not create unchecked stor: %v", err)
+		t.Fatalf("NewUncheckedStore returned an error: %+v", err)
 	}
 
 	// Compare manually created object with NewUnknownRoundsStore
 	if !reflect.DeepEqual(testStore, store) {
-		t.Fatalf("NewUncheckedStore error: "+
-			"Returned incorrect Store."+
-			"\n\texpected: %+v\n\treceived: %+v", testStore, store)
+		t.Fatalf("NewUncheckedStore returned incorrect Store."+
+			"\nexpected: %+v\nreceived: %+v", testStore, store)
 	}
 
 	rid := id.Round(1)
-	roundInfo := &pb.RoundInfo{
-		ID: uint64(rid),
-	}
+	roundInfo := &pb.RoundInfo{ID: uint64(rid)}
+	recipient := id.NewIdFromString("recipientID", id.User, t)
+	ephID, _, _, _ := ephemeral.GetId(recipient, id.ArrIDLen, netTime.Now().UnixNano())
 	uncheckedRound := UncheckedRound{
 		Info:      roundInfo,
 		LastCheck: netTime.Now(),
 		NumChecks: 0,
+		Identity:  Identity{Source: recipient, EpdId: ephID},
 	}
 
-	store.list[rid] = uncheckedRound
+	ri := newRoundIdentity(rid, recipient, ephID)
+	store.list[ri] = uncheckedRound
 	if err = store.save(); err != nil {
-		t.Fatalf("NewUncheckedStore error: "+
-			"Could not save store: %v", err)
+		t.Fatalf("Could not save store: %+v", err)
 	}
 
 	// Test if round list data matches
 	expectedRoundData, err := store.marshal()
 	if err != nil {
-		t.Fatalf("NewUncheckedStore error: "+
-			"Could not marshal data: %v", err)
+		t.Fatalf("Failed to marshal UncheckedRoundStore: %+v", err)
 	}
 	roundData, err := store.kv.Get(uncheckedRoundKey, uncheckedRoundVersion)
 	if err != nil {
-		t.Fatalf("NewUncheckedStore error: "+
-			"Could not retrieve round list form storage: %v", err)
+		t.Fatalf("Failed to get round list from storage: %+v", err)
 	}
 
 	if !bytes.Equal(expectedRoundData, roundData.Data) {
-		t.Fatalf("NewUncheckedStore error: "+
-			"Data from store was not expected"+
-			"\n\tExpected %v\n\tReceived: %v", expectedRoundData, roundData.Data)
+		t.Fatalf("Data from store unexpected.\nexpected %+v\nreceived: %v",
+			expectedRoundData, roundData.Data)
 	}
-
 }
 
 // Unit test
@@ -83,51 +78,39 @@ func TestLoadUncheckedStore(t *testing.T) {
 
 	testStore, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("LoadUncheckedStore error: "+
-			"Could not call constructor NewUncheckedStore: %v", err)
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
 	}
 
 	// Add round to store
 	rid := id.Round(0)
-	roundInfo := &pb.RoundInfo{
-		ID: uint64(rid),
-	}
-
+	roundInfo := &pb.RoundInfo{ID: uint64(rid)}
 	ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
 	source := id.NewIdFromBytes([]byte("Sauron"), t)
-	err = testStore.AddRound(roundInfo, ephId, source)
+	err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId)
 	if err != nil {
-		t.Fatalf("LoadUncheckedStore error: "+
-			"Could not add round to store: %v", err)
+		t.Fatalf("Failed to add round to store: %+v", err)
 	}
 
 	// Load store
 	loadedStore, err := LoadUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("LoadUncheckedStore error: "+
-			"Could not call LoadUncheckedStore: %v", err)
+		t.Fatalf("LoadUncheckedStore returned an error: %+v", err)
 	}
 
 	// Check if round is in loaded store
-	rnd, exists := loadedStore.list[rid]
+	ri := newRoundIdentity(rid, source, ephId)
+	rnd, exists := loadedStore.list[ri]
 	if !exists {
-		t.Fatalf("LoadUncheckedStore error: "+
-			"Added round %d not found in loaded store", rid)
+		t.Fatalf("Added round %d not found in loaded store.", rid)
 	}
 
 	// Check if set values are expected
-	if !bytes.Equal(rnd.EpdId[:], ephId[:]) ||
-		!source.Cmp(rnd.Source) {
-		t.Fatalf("LoadUncheckedStore error: "+
-			"Values in loaded round %d are not expected."+
-			"\n\tExpected ephemeral: %v"+
-			"\n\tReceived ephemeral: %v"+
-			"\n\tExpected source: %v"+
-			"\n\tReceived source: %v", rid,
-			ephId, rnd.EpdId,
-			source, rnd.Source)
+	if !bytes.Equal(rnd.EpdId[:], ephId[:]) || !source.Cmp(rnd.Source) {
+		t.Fatalf("Values in loaded round %d are not expected."+
+			"\nexpected ephemeral: %d\nreceived ephemeral: %d"+
+			"\nexpected source: %s\nreceived source: %s",
+			rid, ephId.Int64(), rnd.EpdId.Int64(), source, rnd.Source)
 	}
-
 }
 
 // Unit test
@@ -136,28 +119,23 @@ func TestUncheckedRoundStore_AddRound(t *testing.T) {
 
 	testStore, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("AddRound error: "+
-			"Could not call constructor NewUncheckedStore: %v", err)
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
 	}
 
 	// Add round to store
 	rid := id.Round(0)
-	roundInfo := &pb.RoundInfo{
-		ID: uint64(rid),
-	}
+	roundInfo := &pb.RoundInfo{ID: uint64(rid)}
 	ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
 	source := id.NewIdFromBytes([]byte("Sauron"), t)
-	err = testStore.AddRound(roundInfo, ephId, source)
+	err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId)
 	if err != nil {
-		t.Fatalf("AddRound error: "+
-			"Could not add round to store: %v", err)
+		t.Fatalf("AddRound returned an error: %+v", err)
 	}
 
-	if _, exists := testStore.list[rid]; !exists {
-		t.Errorf("AddRound error: " +
-			"Could not find added round in list")
+	ri := newRoundIdentity(rid, source, ephId)
+	if _, exists := testStore.list[ri]; !exists {
+		t.Error("Could not find added round in list")
 	}
-
 }
 
 // Unit test
@@ -166,50 +144,118 @@ func TestUncheckedRoundStore_GetRound(t *testing.T) {
 
 	testStore, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("GetRound error: "+
-			"Could not call constructor NewUncheckedStore: %v", err)
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
 	}
 
 	// Add round to store
 	rid := id.Round(0)
-	roundInfo := &pb.RoundInfo{
-		ID: uint64(rid),
-	}
+	roundInfo := &pb.RoundInfo{ID: uint64(rid)}
 	ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
-	source := id.NewIdFromBytes([]byte("Sauron"), t)
-	err = testStore.AddRound(roundInfo, ephId, source)
+	source := id.NewIdFromString("Sauron", id.User, t)
+	err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId)
 	if err != nil {
-		t.Fatalf("GetRound error: "+
-			"Could not add round to store: %v", err)
+		t.Fatalf("Failed to add round to store: %+v", err)
 	}
 
 	// Retrieve round that was inserted
-	retrievedRound, exists := testStore.GetRound(rid)
+	retrievedRound, exists := testStore.GetRound(rid, source, ephId)
 	if !exists {
 		t.Fatalf("GetRound error: " +
 			"Could not get round from store")
 	}
 
-	if !bytes.Equal(retrievedRound.EpdId[:], ephId[:]) ||
-		!source.Cmp(retrievedRound.Source) {
-		t.Fatalf("GetRound error: "+
-			"Values in loaded round %d are not expected."+
-			"\n\tExpected ephemeral: %v"+
-			"\n\tReceived ephemeral: %v"+
-			"\n\tExpected source: %v"+
-			"\n\tReceived source: %v", rid,
-			ephId, retrievedRound.EpdId,
-			source, retrievedRound.Source)
+	if !bytes.Equal(retrievedRound.EpdId[:], ephId[:]) {
+		t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+
+			"\nexpected: %d\nreceived: %d", rid, ephId.Int64(),
+			retrievedRound.EpdId.Int64())
+	}
+
+	if !source.Cmp(retrievedRound.Source) {
+		t.Fatalf("Retrieved source ID for round %d does not match expected."+
+			"\nexpected: %s\nreceived: %s", rid, source, retrievedRound.Source)
 	}
 
 	// Try to pull unknown round from store
 	unknownRound := id.Round(1)
-	_, exists = testStore.GetRound(unknownRound)
+	unknownRecipient := id.NewIdFromString("invalidID", id.User, t)
+	unknownEphId := ephemeral.Id{11, 12, 13, 14, 15, 16, 17, 18}
+	_, exists = testStore.GetRound(unknownRound, unknownRecipient, unknownEphId)
 	if exists {
-		t.Fatalf("GetRound error: " +
-			"Should not find unknown round in store.")
+		t.Fatalf("Should not find unknown round %d in store.", unknownRound)
+	}
+}
+
+// Tests that two identifies for the same round can be retrieved separately.
+func TestUncheckedRoundStore_GetRound_TwoIDs(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	s, err := NewUncheckedStore(kv)
+	if err != nil {
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
+	}
+
+	// Add round to store for the same round but two sources
+	rid := id.Round(0)
+	roundInfo := &pb.RoundInfo{ID: uint64(rid)}
+	ephId1 := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	source1 := id.NewIdFromString("Sauron", id.User, t)
+	err = s.AddRound(rid, roundInfo, source1, ephId1)
+	if err != nil {
+		t.Fatalf("Failed to add round for source 1 to store: %+v", err)
+	}
+
+	ephId2 := ephemeral.Id{11, 12, 13, 14, 15, 16, 17, 18}
+	source2 := id.NewIdFromString("Sauron2", id.User, t)
+	err = s.AddRound(rid, roundInfo, source2, ephId2)
+	if err != nil {
+		t.Fatalf("Failed to add round for source 2 to store: %+v", err)
+	}
+
+	// Increment each a set number of times
+	incNum1, incNum2 := 3, 13
+	for i := 0; i < incNum1; i++ {
+		if err = s.IncrementCheck(rid, source1, ephId1); err != nil {
+			t.Errorf("Failed to incremement for source 1 (%d): %+v", i, err)
+		}
+	}
+	for i := 0; i < incNum2; i++ {
+		if err = s.IncrementCheck(rid, source2, ephId2); err != nil {
+			t.Errorf("Failed to incremement for source 2 (%d): %+v", i, err)
+		}
+	}
+
+	// Retrieve round that was inserted
+	retrievedRound, exists := s.GetRound(rid, source1, ephId1)
+	if !exists {
+		t.Fatalf("Could not get round for source 1 from store")
+	}
+
+	if !bytes.Equal(retrievedRound.EpdId[:], ephId1[:]) {
+		t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+
+			"\nexpected: %d\nreceived: %d", rid, ephId1.Int64(),
+			retrievedRound.EpdId.Int64())
+	}
+
+	if !source1.Cmp(retrievedRound.Source) {
+		t.Fatalf("Retrieved source ID for round %d does not match expected."+
+			"\nexpected: %s\nreceived: %s", rid, source1, retrievedRound.Source)
+	}
+
+	retrievedRound, exists = s.GetRound(rid, source2, ephId2)
+	if !exists {
+		t.Fatalf("Could not get round for source 2 from store")
+	}
+
+	if !bytes.Equal(retrievedRound.EpdId[:], ephId2[:]) {
+		t.Fatalf("Retrieved ephemeral ID for round %d does not match expected."+
+			"\nexpected: %d\nreceived: %d", rid, ephId2.Int64(),
+			retrievedRound.EpdId.Int64())
 	}
 
+	if !source2.Cmp(retrievedRound.Source) {
+		t.Fatalf("Retrieved source ID for round %d does not match expected."+
+			"\nexpected: %s\nreceived: %s", rid, source2, retrievedRound.Source)
+	}
 }
 
 // Unit test
@@ -218,39 +264,36 @@ func TestUncheckedRoundStore_GetList(t *testing.T) {
 
 	testStore, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("GetList error: "+
-			"Could not call constructor NewUncheckedStore: %v", err)
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
 	}
 
 	// Add rounds to store
 	numRounds := 10
 	for i := 0; i < numRounds; i++ {
 		rid := id.Round(i)
-		roundInfo := &pb.RoundInfo{
-			ID: uint64(rid),
-		}
+		roundInfo := &pb.RoundInfo{ID: uint64(rid)}
 		ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
 		source := id.NewIdFromUInt(uint64(i), id.User, t)
-		err = testStore.AddRound(roundInfo, ephId, source)
+		err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId)
 		if err != nil {
-			t.Errorf("GetList error: "+
-				"Could not add round to store: %v", err)
+			t.Errorf("Failed to add round to store: %+v", err)
 		}
 	}
 
 	// Retrieve list
-	retrievedList := testStore.GetList()
+	retrievedList := testStore.GetList(t)
 	if len(retrievedList) != numRounds {
-		t.Errorf("GetList error: "+
-			"List returned is not of expected size."+
-			"\n\tExpected: %v\n\tReceived: %v", numRounds, len(retrievedList))
+		t.Errorf("List returned is not of expected size."+
+			"\nexpected: %d\nreceived: %d", numRounds, len(retrievedList))
 	}
 
 	for i := 0; i < numRounds; i++ {
 		rid := id.Round(i)
-		if _, exists := retrievedList[rid]; !exists {
-			t.Errorf("GetList error: "+
-				"Retrieved list does not contain expected round %d.", rid)
+		ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+		source := id.NewIdFromUInt(uint64(i), id.User, t)
+		ri := newRoundIdentity(rid, source, ephId)
+		if _, exists := retrievedList[ri]; !exists {
+			t.Errorf("Retrieved list does not contain expected round %d.", rid)
 		}
 	}
 
@@ -262,62 +305,55 @@ func TestUncheckedRoundStore_IncrementCheck(t *testing.T) {
 
 	testStore, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("IncrementCheck error: "+
-			"Could not call constructor NewUncheckedStore: %v", err)
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
 	}
 
 	// Add rounds to store
 	numRounds := 10
 	for i := 0; i < numRounds; i++ {
-		rid := id.Round(i)
-		roundInfo := &pb.RoundInfo{
-			ID: uint64(rid),
-		}
+		roundInfo := &pb.RoundInfo{ID: uint64(i)}
 		ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
 		source := id.NewIdFromUInt(uint64(i), id.User, t)
-		err = testStore.AddRound(roundInfo, ephId, source)
+		err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId)
 		if err != nil {
-			t.Errorf("IncrementCheck error: "+
-				"Could not add round to store: %v", err)
+			t.Fatalf("Failed to add round to store: %+v", err)
 		}
 	}
 
 	testRound := id.Round(3)
+	ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	source := id.NewIdFromUInt(uint64(testRound), id.User, t)
 	numChecks := 4
 	for i := 0; i < numChecks; i++ {
-		err = testStore.IncrementCheck(testRound)
+		err = testStore.IncrementCheck(testRound, source, ephId)
 		if err != nil {
-			t.Errorf("IncrementCheck error: "+
-				"Could not increment check for round %d: %v", testRound, err)
+			t.Errorf("Could not increment check for round %d: %v", testRound, err)
 		}
 	}
 
-	rnd, _ := testStore.GetRound(testRound)
+	rnd, _ := testStore.GetRound(testRound, source, ephId)
 	if rnd.NumChecks != uint64(numChecks) {
-		t.Errorf("IncrementCheck error: "+
-			"Round %d did not have expected number of checks."+
-			"\n\tExpected: %v\n\tReceived: %v", testRound, numChecks, rnd.NumChecks)
+		t.Errorf("Round %d did not have expected number of checks."+
+			"\nexpected: %v\nreceived: %v", testRound, numChecks, rnd.NumChecks)
 	}
 
 	// Error path: check unknown round can not be incremented
 	unknownRound := id.Round(numRounds + 5)
-	err = testStore.IncrementCheck(unknownRound)
+	err = testStore.IncrementCheck(unknownRound, source, ephId)
 	if err == nil {
-		t.Errorf("IncrementCheck error: "+
-			"Should not find round %d which was not added to store", unknownRound)
+		t.Errorf("Should not find round %d which was not added to store",
+			unknownRound)
 	}
 
 	// Reach max checks, ensure that round is removed
 	maxRound := id.Round(7)
+	source = id.NewIdFromUInt(uint64(maxRound), id.User, t)
 	for i := 0; i < maxChecks+1; i++ {
-		err = testStore.IncrementCheck(maxRound)
+		err = testStore.IncrementCheck(maxRound, source, ephId)
 		if err != nil {
-			t.Errorf("IncrementCheck error: "+
-				"Could not increment check for round %d: %v", maxRound, err)
+			t.Errorf("Could not increment check for round %d: %v", maxRound, err)
 		}
-
 	}
-
 }
 
 // Unit test
@@ -325,47 +361,40 @@ func TestUncheckedRoundStore_Remove(t *testing.T) {
 	kv := versioned.NewKV(make(ekv.Memstore))
 	testStore, err := NewUncheckedStore(kv)
 	if err != nil {
-		t.Fatalf("Remove error: "+
-			"Could not call constructor NewUncheckedStore: %v", err)
+		t.Fatalf("Failed to make new UncheckedRoundStore: %+v", err)
 	}
 
 	// Add rounds to store
 	numRounds := 10
 	for i := 0; i < numRounds; i++ {
-		rid := id.Round(i)
-		roundInfo := &pb.RoundInfo{
-			ID: uint64(rid),
-		}
+		roundInfo := &pb.RoundInfo{ID: uint64(i)}
 		ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
 		source := id.NewIdFromUInt(uint64(i), id.User, t)
-		err = testStore.AddRound(roundInfo, ephId, source)
+		err = testStore.AddRound(id.Round(roundInfo.ID), roundInfo, source, ephId)
 		if err != nil {
-			t.Errorf("Remove error: "+
-				"Could not add round to store: %v", err)
+			t.Fatalf("Failed to add round to store: %+v", err)
 		}
 	}
 
 	// Remove round from storage
 	removedRound := id.Round(1)
-	err = testStore.Remove(removedRound)
+	ephId := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	source := id.NewIdFromUInt(uint64(removedRound), id.User, t)
+	err = testStore.Remove(removedRound, source, ephId)
 	if err != nil {
-		t.Errorf("Remove error: "+
-			"Could not removed round %d from storage: %v", removedRound, err)
+		t.Errorf("Could not removed round %d from storage: %v", removedRound, err)
 	}
 
 	// Check that round was removed
-	_, exists := testStore.GetRound(removedRound)
+	_, exists := testStore.GetRound(removedRound, source, ephId)
 	if exists {
-		t.Errorf("Remove error: "+
-			"Round %d expected to be removed from storage", removedRound)
+		t.Errorf("Round %d expected to be removed from storage", removedRound)
 	}
 
 	// Error path: attempt to remove unknown round
 	unknownRound := id.Round(numRounds + 5)
-	err = testStore.Remove(unknownRound)
+	err = testStore.Remove(unknownRound, source, ephId)
 	if err == nil {
-		t.Errorf("Remove error: "+
-			"Should not removed round %d which is not in storage", unknownRound)
+		t.Errorf("Should not removed round %d which is not in storage", unknownRound)
 	}
-
 }
diff --git a/storage/rounds/unknownRounds.go b/storage/rounds/unknownRounds.go
index 3711b8787f530633a360bb91ab7911d9f8cc6830..98e863d2b911c5ad7cd7cd9d1eed68ba9e4edc2b 100644
--- a/storage/rounds/unknownRounds.go
+++ b/storage/rounds/unknownRounds.go
@@ -113,8 +113,9 @@ func LoadUnknownRounds(kv *versioned.KV,
 // in params, it removes from the map
 // Afterwards it adds the roundToAdd to the map if an entry isn't present
 // Finally it saves the modified map to disk.
+// The abandon function can be used to pass the abandoned round somewhere else
 func (urs *UnknownRounds) Iterate(checker func(rid id.Round) bool,
-	roundsToAdd []id.Round) []id.Round {
+	roundsToAdd []id.Round, abandon func(round id.Round)) []id.Round {
 	returnSlice := make([]id.Round, 0)
 	urs.mux.Lock()
 	defer urs.mux.Unlock()
@@ -132,6 +133,8 @@ func (urs *UnknownRounds) Iterate(checker func(rid id.Round) bool,
 			// If the round has been checked the maximum amount,
 			// the rond is removed from the map
 			if totalChecks > urs.params.MaxChecks {
+				localRnd := rnd
+				go abandon(localRnd)
 				delete(urs.rounds, rnd)
 			}
 		}
@@ -197,3 +200,14 @@ func (urs *UnknownRounds) Delete() {
 func (urs *UnknownRounds) unmarshal(b []byte) error {
 	return json.Unmarshal(b, &urs.rounds)
 }
+
+func (urs *UnknownRounds) Get(round id.Round) (present bool, numchecked uint64) {
+	urs.mux.Lock()
+	defer urs.mux.Unlock()
+	numcheck, exist := urs.rounds[round]
+	if !exist {
+		return false, 0
+	}
+	return exist, *numcheck
+
+}
diff --git a/storage/rounds/unknownRounds_test.go b/storage/rounds/unknownRounds_test.go
index f437e0839f7d37208f2d7b5c826e6a6cfeb7d5b2..7611579bf84dc6cb1e23c67f8b0c7e68ea765c2e 100644
--- a/storage/rounds/unknownRounds_test.go
+++ b/storage/rounds/unknownRounds_test.go
@@ -86,7 +86,7 @@ func TestUnknownRoundsStore_Iterate(t *testing.T) {
 	}
 
 	// Iterate over initial map
-	received := store.Iterate(mockChecker, nil)
+	received := store.Iterate(mockChecker, nil, func(round id.Round) { return })
 
 	// Check the received list for 2 conditions:
 	// a) that returned rounds are no longer in the map
@@ -106,7 +106,7 @@ func TestUnknownRoundsStore_Iterate(t *testing.T) {
 	}
 
 	// Add even round list to map
-	received = store.Iterate(mockChecker, roundListEven)
+	received = store.Iterate(mockChecker, roundListEven, func(round id.Round) { return })
 
 	if len(received) != 0 {
 		t.Errorf("Second iteration should return an empty list (no even rounds are left)."+
@@ -116,7 +116,7 @@ func TestUnknownRoundsStore_Iterate(t *testing.T) {
 	// Iterate over map until all rounds have checks incremented over
 	// maxCheck
 	for i := 0; i < defaultMaxCheck+1; i++ {
-		_ = store.Iterate(mockChecker, []id.Round{})
+		_ = store.Iterate(mockChecker, []id.Round{}, func(round id.Round) { return })
 
 	}
 
@@ -172,7 +172,7 @@ func TestLoadUnknownRoundsStore(t *testing.T) {
 
 	// Check that LoadStore works after iterate call (which implicitly saves)
 	mockChecker := func(round id.Round) bool { return false }
-	received := store.Iterate(mockChecker, nil)
+	received := store.Iterate(mockChecker, nil, func(round id.Round) { return })
 
 	// Iterate is being called as a dummy, should not return anything
 	if len(received) != 0 {
diff --git a/storage/utility/e2eMessageBuffer.go b/storage/utility/e2eMessageBuffer.go
index 259c6407a2c08c45b4f2e486182964031669b98c..5e4abcb3aa168332e5603899bf73e1e0facc8cdc 100644
--- a/storage/utility/e2eMessageBuffer.go
+++ b/storage/utility/e2eMessageBuffer.go
@@ -155,12 +155,12 @@ func (emb *E2eMessageBuffer) Next() (message.Send, params.E2E, bool) {
 		message.Type(msg.MessageType)}, msg.Params, true
 }
 
-func (emb *E2eMessageBuffer) Succeeded(m message.Send) {
+func (emb *E2eMessageBuffer) Succeeded(m message.Send, p params.E2E) {
 	emb.mb.Succeeded(e2eMessage{m.Recipient.Marshal(),
-		m.Payload, uint32(m.MessageType), params.E2E{}})
+		m.Payload, uint32(m.MessageType), p})
 }
 
-func (emb *E2eMessageBuffer) Failed(m message.Send) {
+func (emb *E2eMessageBuffer) Failed(m message.Send, p params.E2E) {
 	emb.mb.Failed(e2eMessage{m.Recipient.Marshal(),
-		m.Payload, uint32(m.MessageType), params.E2E{}})
+		m.Payload, uint32(m.MessageType), p})
 }
diff --git a/storage/utility/e2eMessageBuffer_test.go b/storage/utility/e2eMessageBuffer_test.go
index 0aca79126fe53903957780f298e26ae1434597db..6eda44ba0cdca36ed43e9332739b968e92ed6ea9 100644
--- a/storage/utility/e2eMessageBuffer_test.go
+++ b/storage/utility/e2eMessageBuffer_test.go
@@ -9,6 +9,7 @@ package utility
 
 import (
 	"encoding/json"
+	"fmt"
 	"gitlab.com/elixxir/client/interfaces/message"
 	"gitlab.com/elixxir/client/interfaces/params"
 	"gitlab.com/elixxir/client/storage/versioned"
@@ -112,7 +113,7 @@ func TestE2EMessageHandler_Smoke(t *testing.T) {
 	if !exists {
 		t.Error("Next() did not find any messages in buffer.")
 	}
-	cmb.Succeeded(msg)
+	cmb.Succeeded(msg, params.E2E{})
 
 	if len(cmb.mb.messages) != 1 {
 		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
@@ -127,7 +128,7 @@ func TestE2EMessageHandler_Smoke(t *testing.T) {
 		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
 			0, len(cmb.mb.messages))
 	}
-	cmb.Failed(msg)
+	cmb.Failed(msg, params.E2E{})
 
 	if len(cmb.mb.messages) != 1 {
 		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
@@ -138,7 +139,7 @@ func TestE2EMessageHandler_Smoke(t *testing.T) {
 	if !exists {
 		t.Error("Next() did not find any messages in buffer.")
 	}
-	cmb.Succeeded(msg)
+	cmb.Succeeded(msg, params.E2E{})
 
 	msg, _, exists = cmb.Next()
 	if exists {
@@ -174,3 +175,45 @@ func makeTestE2EMessages(n int, t *testing.T) ([]e2eMessage, []message.Send) {
 
 	return msgs, send
 }
+
+func TestE2EParamMarshalUnmarshal(t *testing.T) {
+	msg := &e2eMessage{
+		Recipient:   id.DummyUser[:],
+		Payload:     []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
+		MessageType: 42,
+		Params: params.E2E{
+			Type:       1,
+			RetryCount: 7,
+			CMIX: params.CMIX{
+				RoundTries: 6,
+				Timeout:    99,
+				RetryDelay: -4,
+			},
+		},
+	}
+
+	fmt.Printf("msg1: %#v\n", msg)
+
+	b, err := json.Marshal(&msg)
+
+	if err != nil {
+		t.Errorf("Failed to Marshal E2eMessage")
+	}
+
+	fmt.Printf("json: %s\n", string(b))
+
+	msg2 := &e2eMessage{}
+
+	err = json.Unmarshal(b, &msg2)
+
+	if err != nil {
+		t.Errorf("Failed to Unmarshal E2eMessage")
+	}
+
+	fmt.Printf("msg2: %#v\n", msg2)
+
+	if !reflect.DeepEqual(msg, msg2) {
+		t.Errorf("Unmarshaled message is not the same")
+	}
+
+}
diff --git a/storage/utility/messageBuffer.go b/storage/utility/messageBuffer.go
index 31af9f7f2a9b472873d476a686321a33145b2b42..4dd68dc2e7e3f45b4e5315e1aebc5b23b0eb1d04 100644
--- a/storage/utility/messageBuffer.go
+++ b/storage/utility/messageBuffer.go
@@ -21,6 +21,10 @@ import (
 // message stored in the buffer.
 type MessageHash [16]byte
 
+func (m MessageHash) String() string {
+	return base64.StdEncoding.EncodeToString(m[:])
+}
+
 // Sub key used in building keys for saving the message to the key value store
 const messageSubKey = "bufferedMessage"
 
@@ -248,23 +252,31 @@ func (mb *MessageBuffer) Next() (interface{}, bool) {
 		return format.Message{}, false
 	}
 
-	// Pop the next MessageHash from the "not processing" list
-	h := next(mb.messages)
-	jww.TRACE.Printf("Critical Messages Next returned %s",
-		base64.StdEncoding.EncodeToString(h[:]))
+	var m interface{}
+	var err error
 
-	delete(mb.messages, h)
+	//run until empty or a valid message is
+	for m == nil && len(mb.messages) > 0 {
+		// Pop the next MessageHash from the "not processing" list
+		h := next(mb.messages)
+		jww.TRACE.Printf("Critical Messages Next returned %s",
+			base64.StdEncoding.EncodeToString(h[:]))
 
-	// Add message to list of processing messages
-	mb.processingMessages[h] = struct{}{}
+		delete(mb.messages, h)
 
-	// Retrieve the message for storage
-	m, err := mb.handler.LoadMessage(mb.kv, makeStoredMessageKey(mb.key, h))
-	if err != nil {
-		jww.FATAL.Panicf("Could not load message: %v", err)
+		// Add message to list of processing messages
+		mb.processingMessages[h] = struct{}{}
+
+		// Retrieve the message for storage
+		m, err = mb.handler.LoadMessage(mb.kv, makeStoredMessageKey(mb.key, h))
+		if err != nil {
+			jww.ERROR.Printf("Failed to load message %s from store, "+
+				"this may happen on occasion due to replays to increase "+
+				"reliability: %v", h, err)
+		}
 	}
 
-	return m, true
+	return m, m != nil
 }
 
 // next returns the first MessageHash in the map returned by range.
@@ -288,17 +300,20 @@ func (mb *MessageBuffer) Succeeded(m interface{}) {
 	delete(mb.processingMessages, h)
 	delete(mb.messages, h)
 
-	// Done message from key value store
-	err := mb.handler.DeleteMessage(mb.kv, makeStoredMessageKey(mb.key, h))
+	// Save modified buffer to key value store
+	err := mb.save()
 	if err != nil {
 		jww.FATAL.Fatalf("Failed to save: %v", err)
 	}
 
-	// Save modified buffer to key value store
-	err = mb.save()
+	// Done message from key value store
+	err = mb.handler.DeleteMessage(mb.kv, makeStoredMessageKey(mb.key, h))
 	if err != nil {
-		jww.FATAL.Fatalf("Failed to save: %v", err)
+		jww.ERROR.Printf("Failed to delete message from store, "+
+			"this may happen on occasion due to replays to increase "+
+			"reliability: %v", err)
 	}
+
 }
 
 // Failed sets a message as failed to process. It changes the message back to
@@ -314,6 +329,12 @@ func (mb *MessageBuffer) Failed(m interface{}) {
 	// Done from "processing" state
 	delete(mb.processingMessages, h)
 
+	// Save message as versioned object
+	err := mb.handler.SaveMessage(mb.kv, m, makeStoredMessageKey(mb.key, h))
+	if err != nil {
+		jww.FATAL.Panicf("Error saving message: %v", err)
+	}
+
 	// Add to "not processed" state
 	mb.messages[h] = struct{}{}
 }
diff --git a/storage/utility/messageBuffer_test.go b/storage/utility/messageBuffer_test.go
index 5e30960f9ccc3ad7060a876b7924fd3f0d8f3016..6cb9f3660ba7c1f7433d9beb94edfd9a9515c674 100644
--- a/storage/utility/messageBuffer_test.go
+++ b/storage/utility/messageBuffer_test.go
@@ -244,6 +244,25 @@ func TestMessageBuffer_Next(t *testing.T) {
 	}
 }
 
+func TestMessageBuffer_InvalidNext(t *testing.T) {
+	// Create new MessageBuffer and fill with messages
+	testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), newTestHandler(), "testKey")
+	if err != nil {
+		t.Fatalf("Failed to create new MessageBuffer: %v", err)
+	}
+	m := []byte("This is a message that should fail")
+	h := testMB.handler.HashMessage(m)
+	testMB.Add(m)
+	err = testMB.handler.DeleteMessage(testMB.kv, makeStoredMessageKey(testMB.key, h))
+	if err != nil {
+		t.Fatalf("Failed to set up test (delete from kv failed): %+v", err)
+	}
+	msg, exists := testMB.Next()
+	if msg != nil || exists {
+		t.Fatalf("This should fail with an invalid message, instead got: %+v, %+v", m, exists)
+	}
+}
+
 // Tests happy path of MessageBuffer.Remove().
 func TestMessageBuffer_Succeeded(t *testing.T) {
 	th := newTestHandler()
diff --git a/ud/addFact.go b/ud/addFact.go
index a32668f7179d1282f7f96438f13f76905e8b2b8f..1fc1a15f0a24fd327445d4d8220517863cd5eda6 100644
--- a/ud/addFact.go
+++ b/ud/addFact.go
@@ -17,9 +17,9 @@ type addFactComms interface {
 	SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error)
 }
 
-// Adds a fact for the user to user discovery. Will only succeed if the
-// user is already registered and the system does not have the fact currently
-// registered for any user.
+// SendRegisterFact adds a fact for the user to user discovery. Will only
+// succeed if the user is already registered and the system does not have the
+// fact currently registered for any user.
 // This does not complete the fact registration process, it returns a
 // confirmation id instead. Over the communications system the fact is
 // associated with, a code will be sent. This confirmation ID needs to be
@@ -43,10 +43,10 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (strin
 	}
 
 	// Create a hash of our fact
-	fhash := factID.Fingerprint(f)
+	fHash := factID.Fingerprint(f)
 
 	// Sign our inFact for putting into the request
-	fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil)
+	fSig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil)
 	if err != nil {
 		return "", err
 	}
@@ -58,11 +58,17 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (strin
 			Fact:     inFact.Fact,
 			FactType: uint32(inFact.T),
 		},
-		FactSig: fsig,
+		FactSig: fSig,
+	}
+
+	// Get UD host
+	host, err := m.getHost()
+	if err != nil {
+		return "", err
 	}
 
 	// Send the message
-	response, err := aFC.SendRegisterFact(m.host, &remFactMsg)
+	response, err := aFC.SendRegisterFact(host, &remFactMsg)
 
 	confirmationID := ""
 	if response != nil {
diff --git a/ud/addFact_test.go b/ud/addFact_test.go
index 3e1ce5ad31464a042d70a1de251d8b8668a3e8fa..eebb92a1d867c3873897ad56eac60d1b415ec5f2 100644
--- a/ud/addFact_test.go
+++ b/ud/addFact_test.go
@@ -1,6 +1,7 @@
 package ud
 
 import (
+	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/comms/connect"
@@ -12,20 +13,16 @@ import (
 
 type testAFC struct{}
 
-// Dummy implementation of SendRegisterFact so we don't need
-// to run our own UDB server
-func (rFC *testAFC) SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error) {
+// Dummy implementation of SendRegisterFact so that we don't need to run our own
+// UDB server.
+func (rFC *testAFC) SendRegisterFact(*connect.Host, *pb.FactRegisterRequest) (
+	*pb.FactRegisterResponse, error) {
 	return &pb.FactRegisterResponse{}, nil
 }
 
 // Test that the addFact function completes successfully
 func TestAddFact(t *testing.T) {
 	isReg := uint32(1)
-	// Add our host, addFact uses it to get the ID of the user
-	h, err := connect.NewHost(&id.DummyUser, "address", nil, connect.GetDefaultHostParams())
-	if err != nil {
-		t.Fatal(err)
-	}
 
 	// Create a new Private Key to use for signing the Fact
 	rng := csprng.NewSystemRNG()
@@ -34,9 +31,15 @@ func TestAddFact(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+
 	// Create our Manager object
 	m := Manager{
-		host:       h,
+		comms:      comms,
+		net:        newTestNetworkManager(t),
 		privKey:    cpk,
 		registered: &isReg,
 	}
@@ -49,13 +52,13 @@ func TestAddFact(t *testing.T) {
 		T:    2,
 	}
 
-	// Setup a dummy comms that implements SendRegisterFact
+	// Set up a dummy comms that implements SendRegisterFact
 	// This way we don't need to run UDB just to check that this
 	// function works.
-	tafc := testAFC{}
+	tAFC := testAFC{}
 	uid := &id.ID{}
 	// Run addFact and see if it returns without an error!
-	_, err = m.addFact(f, uid, &tafc)
+	_, err = m.addFact(f, uid, &tAFC)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/ud/confirmFact.go b/ud/confirmFact.go
index a625e7e6337d8facc4cff7096d078ee53b264a7d..b7c4294e2c026e35da1ae70b7739fe6feea0c0bd 100644
--- a/ud/confirmFact.go
+++ b/ud/confirmFact.go
@@ -12,8 +12,9 @@ type confirmFactComm interface {
 	SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error)
 }
 
-// Confirms a fact first registered via AddFact. The confirmation ID comes from
-// AddFact while the code will come over the associated communications system
+// SendConfirmFact confirms a fact first registered via AddFact. The
+// confirmation ID comes from AddFact while the code will come over the
+// associated communications system.
 func (m *Manager) SendConfirmFact(confirmationID, code string) error {
 	jww.INFO.Printf("ud.SendConfirmFact(%s, %s)", confirmationID, code)
 	if err := m.confirmFact(confirmationID, code, m.comms); err != nil {
@@ -28,10 +29,16 @@ func (m *Manager) confirmFact(confirmationID, code string, comm confirmFactComm)
 			"client is not registered")
 	}
 
+	// Get UD host
+	host, err := m.getHost()
+	if err != nil {
+		return err
+	}
+
 	msg := &pb.FactConfirmRequest{
 		ConfirmationID: confirmationID,
 		Code:           code,
 	}
-	_, err := comm.SendConfirmFact(m.host, msg)
+	_, err = comm.SendConfirmFact(host, msg)
 	return err
 }
diff --git a/ud/confirmFact_test.go b/ud/confirmFact_test.go
index 2e060c09857b6e0f90d9fac6b5a81f5757d4d79b..9fe29b0a4491e6df330595d27d570e98d87f7fb9 100644
--- a/ud/confirmFact_test.go
+++ b/ud/confirmFact_test.go
@@ -1,10 +1,10 @@
 package ud
 
 import (
+	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/comms/messages"
-	"gitlab.com/xx_network/primitives/id"
 	"reflect"
 	"testing"
 )
@@ -20,17 +20,17 @@ func (t *testComm) SendConfirmFact(_ *connect.Host, message *pb.FactConfirmReque
 
 // Happy path.
 func TestManager_confirmFact(t *testing.T) {
-	// Create new host
-	host, err := connect.NewHost(&id.UDB, "0.0.0.0", nil, connect.GetDefaultHostParams())
+	isReg := uint32(1)
+
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
 	if err != nil {
-		t.Fatalf("Could not create a new host: %+v", err)
+		t.Errorf("Failed to start client comms: %+v", err)
 	}
 
-	isReg := uint32(1)
-
 	// Set up manager
 	m := &Manager{
-		host:       host,
+		comms:      comms,
+		net:        newTestNetworkManager(t),
 		registered: &isReg,
 	}
 
diff --git a/ud/lookup.go b/ud/lookup.go
index e7bdb0da0c0ed10bc8ccaa15c79bc6b9c30b8a9e..c5d8b4dd96757ec69b52e629ad69c131b92ed7aa 100644
--- a/ud/lookup.go
+++ b/ud/lookup.go
@@ -1,11 +1,11 @@
 package ud
 
 import (
-	"fmt"
 	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/crypto/contact"
+	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
@@ -38,7 +38,13 @@ func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Durat
 		m.lookupResponseProcess(uid, callback, payload, err)
 	}
 
-	err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, LookupTag,
+	// Get UD contact
+	c, err := m.getContact()
+	if err != nil {
+		return err
+	}
+
+	err = m.single.TransmitSingleUse(c, requestMarshaled, LookupTag,
 		maxLookupMessages, f, timeout)
 	if err != nil {
 		return errors.WithMessage(err, "Failed to transmit lookup request.")
@@ -67,11 +73,17 @@ func (m *Manager) lookupResponseProcess(uid *id.ID, callback lookupCallback,
 		return
 	}
 
-	fmt.Printf("pubKey: %+v\n", lookupResponse.PubKey)
 	c := contact.Contact{
 		ID:       uid,
 		DhPubKey: m.grp.NewIntFromBytes(lookupResponse.PubKey),
 	}
 
+	if lookupResponse.Username != "" {
+		c.Facts = fact.FactList{{
+			Fact: lookupResponse.Username,
+			T:    fact.Username,
+		}}
+	}
+
 	go callback(c, nil)
 }
diff --git a/ud/lookup_test.go b/ud/lookup_test.go
index 02162faaf2e47e555fe54dd8a6fd1d9a3dd36f03..63cb7ab60c07c3b5740ab676a3579667192025c3 100644
--- a/ud/lookup_test.go
+++ b/ud/lookup_test.go
@@ -5,6 +5,8 @@ import (
 	"github.com/pkg/errors"
 	"gitlab.com/elixxir/client/single"
 	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/comms/client"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/xx_network/crypto/large"
@@ -20,9 +22,17 @@ import (
 func TestManager_Lookup(t *testing.T) {
 	// Set up manager
 	isReg := uint32(1)
+
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+
 	m := &Manager{
+		comms:      comms,
+		storage:    storage.InitTestingSession(t),
+		net:        newTestNetworkManager(t),
 		grp:        cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		udContact:  contact.Contact{ID: &id.UDB},
 		single:     &mockSingleLookup{},
 		registered: &isReg,
 	}
@@ -41,7 +51,7 @@ func TestManager_Lookup(t *testing.T) {
 	uid := id.NewIdFromUInt(0x500000000000000, id.User, t)
 
 	// Run the lookup
-	err := m.Lookup(uid, callback, 10*time.Millisecond)
+	err = m.Lookup(uid, callback, 10*time.Millisecond)
 	if err != nil {
 		t.Errorf("Lookup() returned an error: %+v", err)
 	}
diff --git a/ud/manager.go b/ud/manager.go
index 9342419bdbbe5b8edb01bb32f858032cc6da8186..1b9de113a558afc451e2acabfb5dabde096a4f88 100644
--- a/ud/manager.go
+++ b/ud/manager.go
@@ -35,59 +35,43 @@ type Manager struct {
 	net     interfaces.NetworkManager
 
 	// Loaded from external access
-	udContact contact.Contact
-	privKey   *rsa.PrivateKey
-	grp       *cyclic.Group
+	privKey *rsa.PrivateKey
+	grp     *cyclic.Group
 
 	// internal structures
-	host   *connect.Host
 	single SingleInterface
 	myID   *id.ID
 
 	registered *uint32
 }
 
-// New manager builds a new user discovery manager. It requires that an
-// updated NDF is available and will error if one is not.
+// NewManager builds a new user discovery manager. It requires that an updated
+// NDF is available and will error if one is not.
 func NewManager(client *api.Client, single *single.Manager) (*Manager, error) {
 	jww.INFO.Println("ud.NewManager()")
 	if client.NetworkFollowerStatus() != api.Running {
-		return nil, errors.New("cannot start UD Manager when network follower is not " +
-			"running.")
+		return nil, errors.New(
+			"cannot start UD Manager when network follower is not running.")
 	}
 
 	m := &Manager{
-		client:    client,
-		comms:     client.GetComms(),
-		rng:       client.GetRng(),
-		sw:        client.GetSwitchboard(),
-		storage:   client.GetStorage(),
-		net:       client.GetNetworkInterface(),
-		udContact: contact.Contact{},
-		single:    single,
+		client:  client,
+		comms:   client.GetComms(),
+		rng:     client.GetRng(),
+		sw:      client.GetSwitchboard(),
+		storage: client.GetStorage(),
+		net:     client.GetNetworkInterface(),
+		single:  single,
 	}
 
-	var err error
-
-	// check that user discovery is available in the ndf
+	// check that user discovery is available in the NDF
 	def := m.net.GetInstance().GetPartialNdf().Get()
-	if m.udContact.ID, err = id.Unmarshal(def.UDB.ID); err != nil {
-		return nil, errors.WithMessage(err, "NDF does not have User Discovery "+
-			"information; is there network access?: ID could not be "+
-			"unmarshaled.")
-	}
 
 	if def.UDB.Cert == "" {
 		return nil, errors.New("NDF does not have User Discovery information, " +
 			"is there network access?: Cert not present.")
 	}
 
-	// Unmarshal UD DH public key
-	m.udContact.DhPubKey = m.storage.E2e().GetGroup().NewInt(1)
-	if err = m.udContact.DhPubKey.UnmarshalJSON(def.UDB.DhPubKey); err != nil {
-		return nil, errors.WithMessage(err, "Failed to unmarshal UD DH public key.")
-	}
-
 	// Create the user discovery host object
 	hp := connect.GetDefaultHostParams()
 	// Client will not send KeepAlive packets
@@ -95,11 +79,6 @@ func NewManager(client *api.Client, single *single.Manager) (*Manager, error) {
 	hp.MaxRetries = 3
 	hp.SendTimeout = 3 * time.Second
 	hp.AuthEnabled = false
-	m.host, err = m.comms.AddHost(&id.UDB, def.UDB.Address, []byte(def.UDB.Cert), hp)
-	if err != nil {
-		return nil, errors.WithMessage(err, "User Discovery host object could "+
-			"not be constructed.")
-	}
 
 	m.myID = m.storage.User().GetCryptographicIdentity().GetReceptionID()
 
@@ -114,3 +93,60 @@ func NewManager(client *api.Client, single *single.Manager) (*Manager, error) {
 
 	return m, nil
 }
+
+// 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) {
+	netDef := m.net.GetInstance().GetPartialNdf().Get()
+
+	// Unmarshal UD ID from the NDF
+	udID, err := id.Unmarshal(netDef.UDB.ID)
+	if err != nil {
+		return nil, errors.Errorf("failed to unmarshal UD ID from NDF: %+v", err)
+	}
+
+	// Return the host, if it exists
+	host, exists := m.comms.GetHost(udID)
+	if exists {
+		return host, nil
+	}
+
+	params := connect.GetDefaultHostParams()
+	params.AuthEnabled = false
+
+	// Add a new host and return it if it does not already exist
+	host, err = m.comms.AddHost(udID, netDef.UDB.Address,
+		[]byte(netDef.UDB.Cert), params)
+	if err != nil {
+		return nil, errors.WithMessage(err, "User Discovery host object could "+
+			"not be constructed.")
+	}
+
+	return host, nil
+}
+
+// getContact returns the contact for UD as retrieved from the NDF.
+func (m *Manager) getContact() (contact.Contact, error) {
+	netDef := m.net.GetInstance().GetPartialNdf().Get()
+
+	// Unmarshal UD ID from the NDF
+	udID, err := id.Unmarshal(netDef.UDB.ID)
+	if err != nil {
+		return contact.Contact{},
+			errors.Errorf("failed to unmarshal UD ID from NDF: %+v", err)
+	}
+
+	// Unmarshal UD DH public key
+	dhPubKey := m.storage.E2e().GetGroup().NewInt(1)
+	if err = dhPubKey.UnmarshalJSON(netDef.UDB.DhPubKey); err != nil {
+		return contact.Contact{},
+			errors.WithMessage(err, "Failed to unmarshal UD DH public key.")
+	}
+
+	return contact.Contact{
+		ID:             udID,
+		DhPubKey:       dhPubKey,
+		OwnershipProof: nil,
+		Facts:          nil,
+	}, nil
+}
diff --git a/ud/register.go b/ud/register.go
index 8a25bafc01d150708136945a119de470b6e64e82..f6b205a069fa054708f75c591c147a7660f8cfb3 100644
--- a/ud/register.go
+++ b/ud/register.go
@@ -81,8 +81,14 @@ func (m *Manager) register(username string, comm registerUserComms) error {
 		FactSig: signedFact,
 	}
 
+	// Get UD host
+	host, err := m.getHost()
+	if err != nil {
+		return err
+	}
+
 	// Register user with user discovery
-	_, err = comm.SendRegisterUser(m.host, msg)
+	_, err = comm.SendRegisterUser(host, msg)
 
 	if err == nil {
 		err = m.setRegistered()
diff --git a/ud/register_test.go b/ud/register_test.go
index 7cfd66f1da801bf54b3c853b2232eb3d2f0d6645..0bdd3ef54f664235549b98e40f380f7bc21585c3 100644
--- a/ud/register_test.go
+++ b/ud/register_test.go
@@ -3,6 +3,7 @@ package ud
 import (
 	"bytes"
 	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/crypto/factID"
 	"gitlab.com/elixxir/crypto/fastRNG"
@@ -12,7 +13,6 @@ import (
 	"gitlab.com/xx_network/comms/messages"
 	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/crypto/signature/rsa"
-	"gitlab.com/xx_network/primitives/id"
 	"reflect"
 	"testing"
 )
@@ -28,17 +28,17 @@ func (t *testRegisterComm) SendRegisterUser(_ *connect.Host, msg *pb.UDBUserRegi
 
 // Happy path.
 func TestManager_register(t *testing.T) {
-	// Create new host
-	host, err := connect.NewHost(&id.UDB, "0.0.0.0", nil, connect.GetDefaultHostParams())
+	isReg := uint32(0)
+
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
 	if err != nil {
-		t.Fatalf("Could not create a new host: %+v", err)
+		t.Errorf("Failed to start client comms: %+v", err)
 	}
 
-	isReg := uint32(0)
-
 	// Set up manager
 	m := &Manager{
-		host:       host,
+		comms:      comms,
+		net:        newTestNetworkManager(t),
 		rng:        fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
 		storage:    storage.InitTestingSession(t),
 		registered: &isReg,
diff --git a/ud/remove.go b/ud/remove.go
index 99d9447f330cf0a5c133975abea3a29de1a71b0e..67f6721773baed94402963fad95726dbe34e9365 100644
--- a/ud/remove.go
+++ b/ud/remove.go
@@ -17,7 +17,7 @@ type removeFactComms interface {
 	SendRemoveFact(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error)
 }
 
-// Removes a previously confirmed fact.  Will fail if the fact is not
+// RemoveFact removes a previously confirmed fact. Will fail if the fact is not
 // associated with this client.
 func (m *Manager) RemoveFact(fact fact.Fact) error {
 	jww.INFO.Printf("ud.RemoveFact(%s)", fact.Stringify())
@@ -38,10 +38,10 @@ func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error {
 	}
 
 	// Create a hash of our fact
-	fhash := factID.Fingerprint(fact)
+	fHash := factID.Fingerprint(fact)
 
 	// Sign our inFact for putting into the request
-	fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil)
+	fSig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil)
 	if err != nil {
 		return err
 	}
@@ -50,11 +50,17 @@ func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error {
 	remFactMsg := mixmessages.FactRemovalRequest{
 		UID:         m.myID.Marshal(),
 		RemovalData: &mmFact,
-		FactSig:     fsig,
+		FactSig:     fSig,
+	}
+
+	// Get UD host
+	host, err := m.getHost()
+	if err != nil {
+		return err
 	}
 
 	// Send the message
-	_, err = rFC.SendRemoveFact(m.host, &remFactMsg)
+	_, err = rFC.SendRemoveFact(host, &remFactMsg)
 
 	// Return the error
 	return err
@@ -64,7 +70,7 @@ type removeUserComms interface {
 	SendRemoveUser(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error)
 }
 
-// Removes a previously confirmed fact.  Will fail if the fact is not
+// RemoveUser removes a previously confirmed fact. Will fail if the fact is not
 // associated with this client.
 func (m *Manager) RemoveUser(fact fact.Fact) error {
 	jww.INFO.Printf("ud.RemoveUser(%s)", fact.Stringify())
@@ -85,10 +91,10 @@ func (m *Manager) removeUser(fact fact.Fact, rFC removeUserComms) error {
 	}
 
 	// Create a hash of our fact
-	fhash := factID.Fingerprint(fact)
+	fHash := factID.Fingerprint(fact)
 
 	// Sign our inFact for putting into the request
-	fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil)
+	fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fHash, nil)
 	if err != nil {
 		return err
 	}
@@ -100,8 +106,14 @@ func (m *Manager) removeUser(fact fact.Fact, rFC removeUserComms) error {
 		FactSig:     fsig,
 	}
 
+	// Get UD host
+	host, err := m.getHost()
+	if err != nil {
+		return err
+	}
+
 	// Send the message
-	_, err = rFC.SendRemoveUser(m.host, &remFactMsg)
+	_, err = rFC.SendRemoveUser(host, &remFactMsg)
 
 	// Return the error
 	return err
diff --git a/ud/remove_test.go b/ud/remove_test.go
index d65cb0e8ccd888e374e42c2585fe41dfe2305c66..5705a843a4a182b3ad959243f4b94092551803de 100644
--- a/ud/remove_test.go
+++ b/ud/remove_test.go
@@ -1,6 +1,7 @@
 package ud
 
 import (
+	"gitlab.com/elixxir/comms/client"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/comms/connect"
@@ -13,16 +14,12 @@ import (
 
 type testRFC struct{}
 
-func (rFC *testRFC) SendRemoveFact(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) {
+func (rFC *testRFC) SendRemoveFact(*connect.Host, *pb.FactRemovalRequest) (
+	*messages.Ack, error) {
 	return &messages.Ack{}, nil
 }
 
 func TestRemoveFact(t *testing.T) {
-	h, err := connect.NewHost(&id.DummyUser, "address", nil, connect.GetDefaultHostParams())
-	if err != nil {
-		t.Fatal(err)
-	}
-
 	rng := csprng.NewSystemRNG()
 	cpk, err := rsa.GenerateKey(rng, 2048)
 	if err != nil {
@@ -31,9 +28,15 @@ func TestRemoveFact(t *testing.T) {
 
 	isReg := uint32(1)
 
-	m := Manager{
-		comms:      nil,
-		host:       h,
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+
+	// Set up manager
+	m := &Manager{
+		comms:      comms,
+		net:        newTestNetworkManager(t),
 		privKey:    cpk,
 		registered: &isReg,
 		myID:       &id.ID{},
@@ -44,23 +47,20 @@ func TestRemoveFact(t *testing.T) {
 		T:    2,
 	}
 
-	trfc := testRFC{}
+	tRFC := testRFC{}
 
-	err = m.removeFact(f, &trfc)
+	err = m.removeFact(f, &tRFC)
 	if err != nil {
 		t.Fatal(err)
 	}
 }
 
-func (rFC *testRFC) SendRemoveUser(host *connect.Host, message *pb.FactRemovalRequest) (*messages.Ack, error) {
+func (rFC *testRFC) SendRemoveUser(*connect.Host, *pb.FactRemovalRequest) (
+	*messages.Ack, error) {
 	return &messages.Ack{}, nil
 }
 
 func TestRemoveUser(t *testing.T) {
-	h, err := connect.NewHost(&id.DummyUser, "address", nil, connect.GetDefaultHostParams())
-	if err != nil {
-		t.Fatal(err)
-	}
 
 	rng := csprng.NewSystemRNG()
 	cpk, err := rsa.GenerateKey(rng, 2048)
@@ -70,9 +70,15 @@ func TestRemoveUser(t *testing.T) {
 
 	isReg := uint32(1)
 
-	m := Manager{
-		comms:      nil,
-		host:       h,
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+
+	// Set up manager
+	m := &Manager{
+		comms:      comms,
+		net:        newTestNetworkManager(t),
 		privKey:    cpk,
 		registered: &isReg,
 		myID:       &id.ID{},
@@ -83,9 +89,9 @@ func TestRemoveUser(t *testing.T) {
 		T:    2,
 	}
 
-	trfc := testRFC{}
+	tRFC := testRFC{}
 
-	err = m.removeUser(f, &trfc)
+	err = m.removeUser(f, &tRFC)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/ud/search.go b/ud/search.go
index 41001234896bba755fd6f032afdb19df8a23b22b..83f33901ca97938c4d74966f11d3909fdd227061 100644
--- a/ud/search.go
+++ b/ud/search.go
@@ -14,7 +14,7 @@ import (
 
 // SearchTag specifies which callback to trigger when UD receives a search
 // request.
-const SearchTag = "xxNetwork_UdLookup"
+const SearchTag = "xxNetwork_UdSearch"
 
 // TODO: reconsider where this comes from
 const maxSearchMessages = 20
@@ -45,7 +45,13 @@ func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout ti
 		m.searchResponseHandler(factMap, callback, payload, err)
 	}
 
-	err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, SearchTag,
+	// Get UD contact
+	c, err := m.getContact()
+	if err != nil {
+		return err
+	}
+
+	err = m.single.TransmitSingleUse(c, requestMarshaled, SearchTag,
 		maxSearchMessages, f, timeout)
 	if err != nil {
 		return errors.WithMessage(err, "Failed to transmit search request.")
@@ -86,7 +92,7 @@ func (m *Manager) searchResponseHandler(factMap map[string]fact.Fact,
 		return
 	}
 
-	//return an error if no facts are found
+	// return an error if no facts are found
 	if len(searchResponse.Contacts) == 0 {
 		go callback(nil, errors.New("No contacts found in search"))
 	}
diff --git a/ud/search_test.go b/ud/search_test.go
index c333cf08c5beede7a0ea8389b3adcbb2b4e18544..d0a1edfb9c9dbd1623ae5e5b9e1234c421c87b44 100644
--- a/ud/search_test.go
+++ b/ud/search_test.go
@@ -3,9 +3,11 @@ package ud
 import (
 	"fmt"
 	"github.com/golang/protobuf/proto"
-	errors "github.com/pkg/errors"
+	"github.com/pkg/errors"
 	"gitlab.com/elixxir/client/single"
 	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/comms/client"
 	"gitlab.com/elixxir/crypto/contact"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/factID"
@@ -23,10 +25,19 @@ import (
 func TestManager_Search(t *testing.T) {
 	// Set up manager
 	isReg := uint32(1)
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
+
+	comms, err := client.NewClientComms(nil, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+
+	store := storage.InitTestingSession(t)
+
 	m := &Manager{
-		grp:        grp,
-		udContact:  contact.Contact{ID: &id.UDB, DhPubKey: grp.NewInt(42)},
+		comms:      comms,
+		storage:    store,
+		net:        newTestNetworkManager(t),
+		grp:        store.E2e().GetGroup(),
 		single:     &mockSingleSearch{},
 		registered: &isReg,
 	}
@@ -62,7 +73,7 @@ func TestManager_Search(t *testing.T) {
 		})
 	}
 
-	err := m.Search(factList, callback, 10*time.Millisecond)
+	err = m.Search(factList, callback, 10*time.Millisecond)
 	if err != nil {
 		t.Errorf("Search() returned an error: %+v", err)
 	}
@@ -74,7 +85,12 @@ func TestManager_Search(t *testing.T) {
 			t.Errorf("Callback returned an error: %+v", cb.err)
 		}
 
-		expectedContacts := []contact.Contact{m.udContact}
+		c, err := m.getContact()
+		if err != nil {
+			t.Errorf("Failed to get UD contact: %+v", err)
+		}
+
+		expectedContacts := []contact.Contact{c}
 		if !contact.Equal(expectedContacts[0], cb.c[0]) {
 			t.Errorf("Failed to get expected Contacts."+
 				"\n\texpected: %+v\n\treceived: %+v", expectedContacts, cb.c)
diff --git a/ud/udMessages.pb.go b/ud/udMessages.pb.go
index 82477b3fdd3d3a968997295895f227e1096c7501..0aae2063325f57b73c42ff53e30020225aefcd7e 100644
--- a/ud/udMessages.pb.go
+++ b/ud/udMessages.pb.go
@@ -266,6 +266,7 @@ func (m *LookupSend) GetUserID() []byte {
 // Message sent from UDB for looking up a user
 type LookupResponse struct {
 	PubKey               []byte   `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
+	Username             string   `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
 	Error                string   `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
@@ -304,6 +305,13 @@ func (m *LookupResponse) GetPubKey() []byte {
 	return nil
 }
 
+func (m *LookupResponse) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
 func (m *LookupResponse) GetError() string {
 	if m != nil {
 		return m.Error
@@ -325,23 +333,23 @@ func init() {
 }
 
 var fileDescriptor_9e0cfdc16fb09bb6 = []byte{
-	// 281 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4b, 0xf4, 0x30,
-	0x10, 0xc5, 0xc9, 0x6e, 0xbb, 0x5f, 0x77, 0xbe, 0xa5, 0x4a, 0x10, 0x29, 0x9e, 0x4a, 0xf4, 0x50,
-	0x04, 0x0b, 0xae, 0x77, 0x0f, 0x2a, 0xa2, 0xa8, 0x97, 0xec, 0xcd, 0x5b, 0xb6, 0x1d, 0xb7, 0x22,
-	0x36, 0x21, 0x93, 0x1e, 0xf6, 0xee, 0x1f, 0x2e, 0x4d, 0x63, 0x17, 0x61, 0xbd, 0xe5, 0xcd, 0xcc,
-	0x8f, 0x79, 0xf3, 0x02, 0x87, 0x5d, 0xfd, 0x82, 0x44, 0x6a, 0x83, 0x54, 0x1a, 0xab, 0x9d, 0xe6,
-	0xb1, 0x51, 0x96, 0x50, 0x2c, 0x21, 0x79, 0x50, 0xd4, 0xdc, 0xab, 0xca, 0x71, 0x0e, 0x51, 0xa3,
-	0xa8, 0xc9, 0x58, 0xce, 0x8a, 0x85, 0xf4, 0xef, 0xbe, 0xe6, 0xb6, 0x06, 0xb3, 0x49, 0xce, 0x8a,
-	0x58, 0xfa, 0xb7, 0xf8, 0x62, 0xf0, 0xef, 0x56, 0xb7, 0xae, 0x67, 0x8e, 0x61, 0xd6, 0x11, 0xda,
-	0xc7, 0xbb, 0x40, 0x05, 0xd5, 0xd7, 0x4d, 0xb7, 0x7e, 0xc2, 0xad, 0x27, 0x17, 0x32, 0x28, 0x7e,
-	0x02, 0x49, 0x3f, 0xd1, 0xaa, 0x4f, 0xcc, 0xa6, 0x39, 0x2b, 0xe6, 0x72, 0xd4, 0xfc, 0x02, 0xe6,
-	0xce, 0xbe, 0x6f, 0x7a, 0x2f, 0x94, 0x45, 0xf9, 0xb4, 0xf8, 0xbf, 0x3c, 0x28, 0xbd, 0xcd, 0xf2,
-	0xc7, 0xa3, 0xdc, 0x4d, 0x88, 0x4b, 0x80, 0x15, 0x2a, 0x5b, 0x35, 0x2b, 0x6c, 0x6b, 0x7e, 0x0a,
-	0xd1, 0x9b, 0xaa, 0x5c, 0xc6, 0xf6, 0x73, 0xbe, 0x29, 0x24, 0xa4, 0x03, 0x22, 0x91, 0x8c, 0x6e,
-	0x09, 0xf9, 0x39, 0x24, 0xd5, 0x70, 0x0a, 0x05, 0x34, 0x0d, 0x68, 0xb8, 0x50, 0x8e, 0x7d, 0x7e,
-	0x04, 0x31, 0x5a, 0xab, 0x6d, 0x30, 0x3e, 0x08, 0x71, 0x06, 0xf0, 0xac, 0xf5, 0x47, 0x67, 0xbc,
-	0x8d, 0x3f, 0xf2, 0x10, 0xd7, 0x90, 0x0e, 0x53, 0xe3, 0xe6, 0x5d, 0x42, 0xec, 0x57, 0x42, 0x7b,
-	0xb7, 0xdc, 0x44, 0xaf, 0x93, 0xae, 0x5e, 0xcf, 0xfc, 0xdf, 0x5d, 0x7d, 0x07, 0x00, 0x00, 0xff,
-	0xff, 0x1f, 0xee, 0x62, 0x3e, 0xcf, 0x01, 0x00, 0x00,
+	// 283 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x41, 0x4b, 0xc3, 0x40,
+	0x10, 0x85, 0xd9, 0x36, 0xad, 0xe9, 0x58, 0xa2, 0x2c, 0x22, 0xc1, 0x53, 0x58, 0x3d, 0x04, 0xc1,
+	0x80, 0xf5, 0x1f, 0xa8, 0x88, 0xa2, 0x5e, 0xb6, 0xb7, 0xde, 0xb6, 0xc9, 0xd8, 0x88, 0x98, 0x5d,
+	0x76, 0x36, 0x87, 0xde, 0xfd, 0xe1, 0x92, 0xcd, 0x9a, 0x82, 0xb4, 0xb7, 0x79, 0x33, 0xf3, 0xb1,
+	0x6f, 0xde, 0xc2, 0x69, 0x5b, 0xbd, 0x23, 0x91, 0xda, 0x20, 0x15, 0xc6, 0x6a, 0xa7, 0xf9, 0xc4,
+	0x28, 0x4b, 0x28, 0x16, 0x10, 0x3f, 0x2b, 0xaa, 0x9f, 0x54, 0xe9, 0x38, 0x87, 0xa8, 0x56, 0x54,
+	0xa7, 0x2c, 0x63, 0xf9, 0x5c, 0xfa, 0xba, 0xeb, 0xb9, 0xad, 0xc1, 0x74, 0x94, 0xb1, 0x7c, 0x22,
+	0x7d, 0x2d, 0x7e, 0x18, 0x1c, 0x3d, 0xe8, 0xc6, 0x75, 0xcc, 0x39, 0x4c, 0x5b, 0x42, 0xfb, 0xf2,
+	0x18, 0xa8, 0xa0, 0xba, 0xbe, 0x69, 0xd7, 0xaf, 0xb8, 0xf5, 0xe4, 0x5c, 0x06, 0xc5, 0x2f, 0x20,
+	0xee, 0x36, 0x1a, 0xf5, 0x8d, 0xe9, 0x38, 0x63, 0xf9, 0x4c, 0x0e, 0x9a, 0xdf, 0xc0, 0xcc, 0xd9,
+	0xcf, 0x4d, 0xe7, 0x85, 0xd2, 0x28, 0x1b, 0xe7, 0xc7, 0x8b, 0x93, 0xc2, 0xdb, 0x2c, 0xfe, 0x3c,
+	0xca, 0xdd, 0x86, 0xb8, 0x05, 0x58, 0xa2, 0xb2, 0x65, 0xbd, 0xc4, 0xa6, 0xe2, 0x97, 0x10, 0x7d,
+	0xa8, 0xd2, 0xa5, 0x6c, 0x3f, 0xe7, 0x87, 0x42, 0x42, 0xd2, 0x23, 0x12, 0xc9, 0xe8, 0x86, 0x90,
+	0x5f, 0x43, 0x5c, 0xf6, 0xa7, 0x50, 0x40, 0x93, 0x80, 0x86, 0x0b, 0xe5, 0x30, 0xe7, 0x67, 0x30,
+	0x41, 0x6b, 0xb5, 0x0d, 0xc6, 0x7b, 0x21, 0xae, 0x00, 0xde, 0xb4, 0xfe, 0x6a, 0x8d, 0xb7, 0x71,
+	0x20, 0x0f, 0xb1, 0x82, 0xa4, 0xdf, 0x1a, 0x5e, 0xde, 0x25, 0xc4, 0x0e, 0x26, 0x34, 0xfa, 0x97,
+	0xd0, 0x5e, 0x07, 0xf7, 0xd1, 0x6a, 0xd4, 0x56, 0xeb, 0xa9, 0xff, 0xd7, 0xbb, 0xdf, 0x00, 0x00,
+	0x00, 0xff, 0xff, 0x5a, 0xee, 0x38, 0xba, 0xeb, 0x01, 0x00, 0x00,
 }
diff --git a/ud/udMessages.proto b/ud/udMessages.proto
index c8f993c8cb08d5fbe40b9c9a782e5ddbc8e4da65..e6905f8359be96a7f331561495d44b20c7e1ab17 100644
--- a/ud/udMessages.proto
+++ b/ud/udMessages.proto
@@ -48,5 +48,6 @@ message LookupSend {
 // Message sent from UDB for looking up a user
 message LookupResponse {
   bytes pubKey = 1;
+  string username = 2;
   string error = 3;
 }
diff --git a/ud/utils_test.go b/ud/utils_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c04b9cb1579d65efae12c5191db0f07eaa8c8d07
--- /dev/null
+++ b/ud/utils_test.go
@@ -0,0 +1,146 @@
+////////////////////////////////////////////////////////////////////////////////
+// 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 ud
+
+import (
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/network/gateway"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"gitlab.com/xx_network/primitives/ndf"
+	"testing"
+	"time"
+)
+
+func newTestNetworkManager(t *testing.T) interfaces.NetworkManager {
+	instanceComms := &connect.ProtoComms{
+		Manager: connect.NewManagerTesting(t),
+	}
+
+	thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(),
+		getNDF(), nil, nil, t)
+	if err != nil {
+		t.Fatalf("Failed to create new test instance: %v", err)
+	}
+
+	return &testNetworkManager{
+		instance: thisInstance,
+	}
+}
+
+// testNetworkManager is a test implementation of NetworkManager interface.
+type testNetworkManager struct {
+	instance *network.Instance
+}
+
+func (tnm *testNetworkManager) SendE2E(message.Send, params.E2E, *stoppable.Single) ([]id.Round, e2e.MessageID, time.Time, error) {
+	return nil, e2e.MessageID{}, time.Time{}, nil
+}
+
+func (tnm *testNetworkManager) SendUnsafe(message.Send, params.Unsafe) ([]id.Round, error) {
+	return nil, nil
+}
+
+func (tnm *testNetworkManager) GetVerboseRounds() string {
+	return ""
+}
+
+func (tnm *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) {
+	return 0, ephemeral.Id{}, nil
+}
+
+func (tnm *testNetworkManager) SendManyCMIX(map[id.ID]format.Message, params.CMIX) (id.Round, []ephemeral.Id, error) {
+	return 0, nil, nil
+}
+
+type dummyEventMgr struct{}
+
+func (d *dummyEventMgr) Report(int, string, string, string) {}
+func (tnm *testNetworkManager) GetEventManager() interfaces.EventManager {
+	return &dummyEventMgr{}
+}
+
+func (tnm *testNetworkManager) GetInstance() *network.Instance             { return tnm.instance }
+func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker { return nil }
+func (tnm *testNetworkManager) Follow(interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	return nil, nil
+}
+func (tnm *testNetworkManager) CheckGarbledMessages()        {}
+func (tnm *testNetworkManager) InProgressRegistrations() int { return 0 }
+func (tnm *testNetworkManager) GetSender() *gateway.Sender   { return nil }
+func (tnm *testNetworkManager) GetAddressSize() uint8        { return 0 }
+func (tnm *testNetworkManager) RegisterAddressSizeNotification(string) (chan uint8, error) {
+	return nil, nil
+}
+func (tnm *testNetworkManager) UnregisterAddressSizeNotification(string) {}
+func (tnm *testNetworkManager) SetPoolFilter(gateway.Filter)             {}
+
+func getNDF() *ndf.NetworkDefinition {
+	return &ndf.NetworkDefinition{
+		UDB: ndf.UDB{
+			ID:      id.DummyUser.Bytes(),
+			Cert:    "",
+			Address: "address",
+			DhPubKey: []byte{123, 34, 86, 97, 108, 117, 101, 34, 58, 49, 44, 34,
+				70, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, 34, 58,
+				51, 49, 54, 49, 50, 55, 48, 53, 56, 49, 51, 52, 50, 49, 54, 54,
+				57, 52, 55, 125},
+		},
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A" +
+				"8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3D" +
+				"D2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E78615" +
+				"75E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC" +
+				"6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C" +
+				"4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F2" +
+				"6E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE" +
+				"448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E" +
+				"198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FF" +
+				"DDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323" +
+				"631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C" +
+				"3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E63" +
+				"19BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC3" +
+				"5873847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642" +
+				"F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757" +
+				"264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F" +
+				"9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091E" +
+				"B51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D" +
+				"0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D3" +
+				"92145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A" +
+				"2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7" +
+				"995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E2480" +
+				"9670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D" +
+				"1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A33" +
+				"8661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361" +
+				"C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B28" +
+				"5DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD929" +
+				"59859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D83" +
+				"2186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8" +
+				"B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}