diff --git a/.gitignore b/.gitignore
index 30f29f0e733db9a0ae86702d463517e7be394e29..39999c116ca2b2ef70d507711307cfc85757e7ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,10 @@ localdev_*
 *.class
 *.aar
 *.jar
+# Ignore test output related to ekv
+.ekv*
+.*test*
+*.1
+*.2
+# Ignore temp files
+*.bak
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 939c429d956d80ba7afb9d650f9b4cacf6d4df3d..343f8db7669105b8d56f446deb66f29838502562 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,8 +3,8 @@
 variables:
   REPO_DIR: gitlab.com/elixxir
   REPO_NAME: client
-  DOCKER_IMAGE: elixxirlabs/cuda-go:latest
-  MIN_CODE_COVERAGE: "74"
+  DOCKER_IMAGE: elixxirlabs/cuda-go:go1.13-cuda11.1-mc
+  MIN_CODE_COVERAGE: "35"
 
 before_script:
   ##
@@ -21,7 +21,7 @@ before_script:
   - ssh-keyscan -t rsa gitlab.com > ~/.ssh/known_hosts
   - git config --global url."git@gitlab.com:".insteadOf "https://gitlab.com/"
   - export PATH=$HOME/go/bin:$PATH
-  - export GOPRIVATE=gitlab.com/elixxir/*,gitlab.com/xx_network/*
+  - export GOPRIVATE="*gitlab.com/elixxir/*,*gitlab.com/xx_network/*"
 
 
 stages:
@@ -70,6 +70,7 @@ build:
     - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.win64 main.go
     - GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.win32 main.go
     - GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.darwin64 main.go
+    - /upload-artifacts.sh release/
   artifacts:
     paths:
      - release/
@@ -91,6 +92,7 @@ bindings:
   tags:
     - ios
   script:
+    - export PATH="/usr/local/opt/go@1.13/bin:$PATH"
     - go get -u golang.org/x/mobile/cmd/gomobile
     - go get -u golang.org/x/mobile/bind
     - rm -rf $HOME/go/src/gitlab.com/elixxir/client/
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..03ce3f2f34129931075fb1ead5eaeac7b9746c57
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+Copyright (c) 2020, xx network SEZC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this 
+list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice, 
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
index b8d85b2d4b167ff0347162df66cc69dda12755e3..2e56e3530bb3ef5d03352961f8eef095b4a47c01 100644
--- a/Makefile
+++ b/Makefile
@@ -5,8 +5,8 @@ setup:
 
 version:
 	go run main.go generate
-	sed -i.bak 's/package\ cmd/package\ globals/g' version_vars.go
-	mv version_vars.go globals/version_vars.go
+	sed -i.bak 's/package\ cmd/package\ api/g' version_vars.go
+	mv version_vars.go api/version_vars.go
 
 clean:
 	rm -rf vendor/
@@ -20,16 +20,20 @@ build:
 	go mod tidy
 
 update_release:
+	GOFLAGS="" go get -u gitlab.com/xx_network/primitives@release
 	GOFLAGS="" go get -u gitlab.com/elixxir/primitives@release
 	GOFLAGS="" go get -u gitlab.com/elixxir/crypto@release
-	GOFLAGS="" go get -u gitlab.com/elixxir/comms@release
+	GOFLAGS="" go get -u gitlab.com/xx_network/crypto@release
 	GOFLAGS="" go get -u gitlab.com/xx_network/comms@release
+	GOFLAGS="" go get -u gitlab.com/elixxir/comms@release
 
 update_master:
-	GOFLAGS="" go get -u gitlab.com/elixxir/primitives@master
-	GOFLAGS="" go get -u gitlab.com/elixxir/crypto@master
-	GOFLAGS="" go get -u gitlab.com/elixxir/comms@master
-	GOFLAGS="" go get -u gitlab.com/xx_network/comms@master
+	GOFLAGS="" go get gitlab.com/xx_network/primitives@master
+	GOFLAGS="" go get gitlab.com/elixxir/primitives@master
+	GOFLAGS="" go get gitlab.com/xx_network/crypto@master
+	GOFLAGS="" go get gitlab.com/elixxir/crypto@master
+	GOFLAGS="" go get gitlab.com/xx_network/comms@master
+	GOFLAGS="" go get gitlab.com/elixxir/comms@master
 
 master: clean update_master build version
 
diff --git a/README.md b/README.md
index 809c8a0144349c9759a7628fb3188f6425585a4f..98a8c6b0b888063c9ffd447641f55a0d5339a191 100644
--- a/README.md
+++ b/README.md
@@ -1,157 +1,483 @@
-# elixxir/client
+# xx network Client
 
 [![pipeline status](https://gitlab.com/elixxir/client/badges/master/pipeline.svg)](https://gitlab.com/elixxir/client/commits/master)
 [![coverage report](https://gitlab.com/elixxir/client/badges/master/coverage.svg)](https://gitlab.com/elixxir/client/commits/master)
 
-This repo contains the Elixxir command-line client (used for integration
-testing) and related libraries that facilitate making more full-featured
-clients for all platforms.
-
-##Running the Command Line Client
-
-First, make sure dependencies are installed into the vendor folder by running
-`glide up`. Then, in the project directory, run `go run main.go`.
-
-If what you're working on requires you to change other repos, you can remove
-the other repo from the vendor folder and Go's build tools will look for those
-packages in your Go path instead. Knowing which dependencies to remove can be
-really helpful if you're changing a lot of repos at once.
-
-If glide isn't working and you don't know why, try removing glide.lock and
-~/.glide to brutally cleanse the cache.
-
-
-Mutually exclusive (almost) required args:
-
-|Long flag|Short flag|Effect|Example|
-|---|---|---|---|
-|--userid|-i|ID of precanned user to use|-i 5|
-|--regcode|-e|Registration code to use for logging in a new user|-e AAAA|
-
-The above args are mutually exclusive and are not fully required.
-
-For example, to login as canned user 18, use `-i 18` and any registration code specified with `-e` will be ignored.
-To login as a new user, `-i` MUST not be specified, and `-e` will be the registration code to be used.
-
-NOTE: There is a third way of starting the client, which ONLY works without specifying any of the above args.
-This will internally ignore the registration address, if specified, and will do registration directly on the Nodes
-only.
-
-Optional args:
-
-|Long flag|Short flag|Effect|Example|
-|---|---|---|---|
-|--message|-m|Message to send|-m "top of the morning"|
-|--messageTimeout|-t|The number of seconds to wait for 'waitForMessages' messages to arrive (default 45)|-t 42|
-|--ndf|-n|Path to the network definition JSON file (default "ndf.json")| -n "ndf.json"|
-|--SearchForUser|-s|Sets the email to search for to find a user with user discovery| -s "david@chaum.com|
-|--dest64| |Sets the destination user id encoded in base 64| --dest64 "yCvV6AsEK3l+45Gn4awBJ4lpb+hT2sO6yzxjeraRor0="|
-|--destid|-d|ID to send message to| -d 69|
-|--email|-E|Email to register for User Discovery| -e "david@chaum.com"|
-|--end2end| |Send messages with E2E encryption to destination user. Must have found each other via UDB first| -end2end|
-|--help| |help for client| --help|
-|--keyParams| |Define key generation parameters. Pass values in comma separated list in the following order: MinKeys,MaxKeys,NumRekeys,TTLScalar,MinNumKeys| |
-|--ndfPubKey|-p|Path to the public key for the network definition JSON file|
-|--nick| |Nickname to register for User Discovery (default "Default")| --nick "zezima"|
-|--noBlockingTransmission| |Sets if transmitting messages blocks or not.  Defaults to true if unset.|--noBlockingTransmission|
-|--noTLS| |Set to ignore TLS. Connections will fail if the network requires TLS. For debugging|--noTLS|
-|--privateKey| |The path for a PEM encoded private key which will be used to create the user|--privateKey "key.pem"|
-|--rateLimiting| |Sets the amount of time, in ms, that the client waits between sending messages.  set to zero to disable.  Automatically disabled if 'blockingTransmission' is false (default 1000)| --rateLimiting 100|
-|--regcode string|-r|Registration Code with the registration server |--regcode "AAAA"|
-|--sessionfile|-f|Passes a file path for loading a session.  If the file doesnt exist the code will register the user and store it there.  If not passed the session will be stored to ram and lost when the cli finishes| -s "user.session"|
-|--skipNDFVerification| |Specifies if the NDF should be loaded without the signature|--skipNDFVerification|
-|--userid|-i|ID to sign in as. Does not register, must be an available precanned user |-i 32|
-|--verbose|-v|Verbose mode for debugging|-v|
-|--version|-V|Show the client version information|-V|
-|--waitForMessages|-w|Denotes the number of messages the client should receive before closing (default 1)|-w 7|
-
-Runs a client for cMix anonymous communication platform
+The xx network client is a library and related command line tool 
+that facilitate making full-featured xx clients for all platforms. The
+command line tool can be built for any platform supported by
+golang. The libraries are built for iOS and Android using
+[gomobile](https://godoc.org/golang.org/x/mobile/cmd/gomobile).
+
+This repository contains everything necessary to implement all of the
+xx network messaging features. These include the end-to-end encryption
+and metadata protection. It also contains features to extend the base 
+messaging protocols.
+
+For library writers, the client requires a writable folder to store
+data, functions for receiving and approving requests for creating
+secure end-to-end messaging channels, for discovering users, and for
+receiving different types of messages. Details for implementing these
+features are in the Library Overview section below.
+
+The client is open source software released under the simplified BSD License.
+
+## Command Line Usage
+
+The command line tool is intended for testing xx network functionality and not
+for regular user use. 
+
+Compilation (assuming golang 1.13 or newer):
+
+```
+git clone https://gitlab.com/elixxir/client.git client
+cd client
+go mod vendor -v
+go mod tidy
+go test ./...
+# Linux 64 bit binary
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o client.linux64 main.go
+# Windows 64 bit binary
+GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o client.win64 main.go
+# Windows 32 big binary
+GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.win32 main.go
+# Mac OSX 64 bit binary (intel)
+GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/client.darwin64 main.go
+```
+
+To get an NDF from a network gateway and the permissioning server, use the `getndf` subcommand.  The `getndf` subcommand allows command line users to poll the NDF from both a gateway and the permissioning server without any pre-established client connection. It requires an IP address, port, and ssl certificate. You can download an ssl cert with:
+```
+openssl s_client -showcerts -connect permissioning.prod.cmix.rip:11420 < /dev/null 2>&1 | openssl x509 -outform PEM > certfile.pem
+```
+
+Example usage for Gateways:
+
+```
+$ go run main.go getndf --gwhost localhost:8440 --cert ~/integration/keys/cmix.rip.crt | jq . | head
+{
+  "Timestamp": "2021-01-29T01:19:49.227246827Z",
+  "Gateways": [
+    {
+      "Id": "BRM+Iotl6ujIGhjRddZMBdauapS7Z6jL0FJGq7IkUdYB",
+      "Address": ":8440",
+      "Tls_certificate": "-----BEGIN CERTIFICATE-----\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\n6m52PyzMNV+2N21IPppKwA==\n-----END CERTIFICATE-----\n"
+    },
+    {
+      "Id": "JCBd9mAQb2BW8hc8H9avy1ubcjUAa7MHrPp0dBU/VqQB",
+```
+
+Example usage for the Permissioning server:
+
+```
+$ go run main.go getndf --permhost localhost:18000 --cert ~/integration/keys/cmix.rip.crt  | jq . | head
+{
+  "Timestamp": "2021-01-29T01:19:49.227246827Z",
+  "Gateways": [
+    {
+      "Id": "BRM+Iotl6ujIGhjRddZMBdauapS7Z6jL0FJGq7IkUdYB",
+      "Address": ":8440",
+      "Tls_certificate": "-----BEGIN CERTIFICATE-----\nMIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp\ncDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT\nMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV\nBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh\nDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs\nWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE\ntJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA\nm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9\nbJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA\nAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA\nneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf\nU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2\nqvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4\ncyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R\ntgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5\n6m52PyzMNV+2N21IPppKwA==\n-----END CERTIFICATE-----\n"
+    },
+    {
+      "Id": "JCBd9mAQb2BW8hc8H9avy1ubcjUAa7MHrPp0dBU/VqQB",
+```
+
+Basic command line usage, sending unsafe, unencrypted messages to yourself:
+
+```
+client --password user-password --ndf ndf.json -l client.log -s session-directory --writeContact user-contact.json --unsafe -m \"Hello World, without E2E Encryption\"
+```
+
+* `--password` is the password used to encrypt and load the session.
+* `--ndf` is the network definition file, downloadable from the xx network
+  website when available.
+* `-l` the file to write logs (user messages are still printed to stdout)
+* `--writeContact` Output the user's contact information to this file.
+* `--unsafe` Send message without encryption (necessary whenever you have not
+  already established an e2e channel)
+* `-m` The message to send
+
+The client defaults to sending to itself when not supplied.
+
+Sending unsafe messages between 2 users:
+
+```
+# Get user contact jsons
+client --password user1-password --ndf ndf.json -l client1.log -s user1session --writeContact user1-contact.json --unsafe -m "Hi"
+client --password user2-password --ndf ndf.json -l client2.log -s user2session --writeContact user2-contact.json --unsafe -m "Hi"
+
+# Send messages to each other, run them in the background so they both receive
+# each other's messages
+client --password user1-password --ndf ndf.json -l client1.log -s user1session --destfile user2-contact.json --unsafe -m "Hi User 2, from User 1 without E2E Encryption" &
+client --password user2-password --ndf ndf.json -l client2.log -s user2session --destfile user1-contact.json --unsafe -m "Hi User 1, from User 2 without E2E Encryption" &
+```
+
+* `--destfile` is used to specify the recipient. You can also use
+  `--destid b64:...` using the user's base64 id which is printed in the logs.
+
+To send with end to end encryption, you must first establish a connection
+with the other user:
+
+```
+# Get user contact jsons
+client --password user1-password --ndf ndf.json -l client1.log -s user1session --writeContact user1-contact.json --unsafe -m "Hi"
+client --password user2-password --ndf ndf.json -l client2.log -s user2session --writeContact user2-contact.json --unsafe -m "Hi"
+
+# Send E2E Messages
+client --password user1-password --ndf ndf.json -l client1.log -s user1session --destfile user2-contact.json --unsafe-channel-creation -m "Hi User 2, from User 1 with E2E Encryption" &
+client --password user2-password --ndf ndf.json -l client2.log -s user2session --destfile user1-contact.json --unsafe-channel-creation -m "Hi User 1, from User 2 with E2E Encryption" &
+```
+
+Note that we have dropped the `--unsafe` in exchange for:
+* `--unsafe-channel-creation` Auto-create and auto-accept channel requests.
+
+To be considered "safe" the user should be prompted. You can do this
+with the command line by explicitly accepting the channel creation
+when sending and/or explicitly accepting a request with
+`--accept-channel`.
+
+Full usage of client can be found with `client --help`:
+
+```
+$ ./client --help
+Usage:
+  client [flags]
+  client [command]
+
+Available Commands:
+  generate    Generates version and dependency information for the
+              Elixxir binary
+  help        Help about any command
+  version     Print the version and dependency information for the
+              Elixxir binary
+
+Flags:
+      --accept-channel            Accept the channel request for the
+                                  corresponding recipient ID
+      --destfile string           Read this contact file for the destination id
+  -d, --destid string             ID to send message to (if below 40, will be
+                                  precanned. Use '0x' or 'b64:' for hex and
+                                  base64 representations) (default "0")
+      --forceHistoricalRounds     Force all rounds to be sent to historical
+                                  round retrieval
+  -h, --help                      help for client
+  -l, --log string                Path to the log output path (- is stdout)
+                                  (default "-")
+  -m, --message string            Message to send
+  -n, --ndf string                Path to the network definition JSON file
+                                  (default "ndf.json")
+  -p, --password string           Password to the session file
+      --receiveCount uint         How many messages we should wait for before
+                                  quitting (default 1)
+      --regcode string            Registration code (optional)
+      --sendCount uint            The number of times to send the message
+                                  (default 1)
+      --sendDelay uint            The delay between sending the messages in ms
+                                  (default 500)
+      --sendid uint               Use precanned user id (must be between 1 and
+                                  40, inclusive)
+  -s, --session string            Sets the initial directory for client storage
+      --unsafe                    Send raw, unsafe messages without e2e
+                                  encryption.
+      --unsafe-channel-creation   Turns off the user identity authenticated
+                                  channel check, automatically approving
+                                  authenticated channels
+  -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
+                                  arrive (default 15)
+  -w, --writeContact string       Write the contact file for this user to this
+                                  file
 
 Use "client [command] --help" for more information about a command.
-
-
-
-##Project Structure
-
-`api` package contains functions that clients written in Go should call to do
-all of the main interactions with the client library.
-
-`bindings` package exists for compatibility with Gomobile. All functions and
-structs in the `bindings` package must be able to be bound with `$ gomobile bind`
-or they will be unceremoniously removed. There are many requirements for 
-this, and if you're writing bindings, you should check the `gomobile` 
-documentation listed below.
-
-In general, clients written in Go should use the `api` package and clients 
-written in other languages should use the `bindings` package.
-
-`bots` contains code for interacting with bots. If the amount of code required
-to easily interact with a bot is reasonably small, it should go in this package.
-
-`cmd` contains the command line client itself, including the dummy messaging
-prototype that sends messages at a constant rate.
-
-`crypto` contains code for encrypting and decrypting individual messages with
-the client's part of the cipher. 
-
-`globals` contains a few global variables. Avoid putting more things in here
-without seriously considering the alternatives. Most important is the Log 
-variable:
-
-globals.Log.ERROR.Println("this is an error")
-
-Using this global Log variable allows external users of jww logging, like the 
-console UI, to see and print log messages from the client library if they need
-to, so please use globals.Log for all logging messages to make this behavior
-work consistently.
-
-If you think you can come up with a better design to deal with this problem, 
-please go ahead and implement it. Anything that moves towards the globals 
-package no longer existing is probably a win.
-
-`io` contains functions for communicating between the client and the gateways.
-It's also currently responsible for putting fragmented messages back together.
-
-`parse` contains functions for serializing and deserializing various specialized
-information into messages. This includes message types and fragmenting messages
-that are too long.
-
-`payment` deals with the wallet and payments, and keeping track of all related
-data in non-volatile storage.
-
-`switchboard` includes a structure that you can use to listen to incoming 
-messages and dispatch them to the correct handlers.
-
-`user` includes objects that deal with the user's identity and the session 
-and session storage.
-
-##Gomobile
-
-We bind all exported symbols from the bindings package for use on mobile 
-platforms. To set up Gomobile for Android, install the NDK and 
-pass the -ndk flag to ` $ gomobile init`. Other repositories that use Gomobile
-for binding should include a shell script that creates the bindings.
-
-###Recommended Reading for Gomobile
-
-https://godoc.org/golang.org/x/mobile/cmd/gomobile (setup and available 
-subcommands)
-
-https://godoc.org/golang.org/x/mobile/cmd/gobind (reference cycles, type 
-restrictions)
-
-Currently we aren't using reverse bindings, i.e. calling mobile from Go.
-
-###Testing Bindings via Gomobile
-
-The separate `bindings-integration` repository exists to make it easier to 
-automatically test bindings. Writing instrumented tests from Android allows 
-you to create black-box tests that also prove that all the methods you think 
-are getting bound are indeed bound, rather than silently getting skipped.
-
-You can also verify that all symbols got bound by unzipping `bindings-sources.jar`
-and inspecting the resulting source files.
-
-Every time you make a change to the client or bindings, you must rebuild the 
-client bindings into a .aar to propagate those changes to the app. There's a 
-script that runs gomobile for you in the `bindings-integration` repository.
+```
+
+Note that the client cannot be used on the betanet with precanned user ids.
+
+## Library Overview
+
+The xx client is designed to be used as a go library (and by extension a 
+c library). 
+ 
+Support is also present for go mobile to build Android and iOS libraries. We
+bind all exported symbols from the bindings package for use on mobile
+platforms.
+
+### Implementation Notes
+
+Clients need to perform the same actions *in the same order* as shown in
+`cmd/root.go`. Specifically, certain handlers need to be registered and
+set up before starting network threads (i.e., before StartNetworkFollowers
+-- #2 below) and you cannot perform certain actions until the network
+connection reaches the "healthy" state. Below are relevant code listings for
+how to do these actions.
+
+the ndf is the network definition file, downloadable from the xx network 
+website when available.
+
+1. Creating and/or Loading a client:
+```
+	//create a new client if none exist
+	if _, err := os.Stat(storeDir); os.IsNotExist(err) {
+		// Load NDF
+		ndfPath := viper.GetString("ndf")
+		ndfJSON, err := ioutil.ReadFile(ndfPath)
+		if err != nil {
+			jww.FATAL.Panicf(err.Error())
+		}
+		err = api.NewClient(string(ndfJSON), storeDir,
+			[]byte(pass), regCode)
+		}
+
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+	}
+
+	//load the client
+	client, err := api.Login(storeDir, []byte(pass))
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+```
+2. Set up registration, authorization request handlers
+```
+	user := client.GetUser()
+
+	// Set up reception handler
+	swboard := client.GetSwitchboard()
+	recvCh := make(chan message.Receive, 10000) // Needs to be large
+	// Note the name below is arbitrary
+	listenerID := swboard.RegisterChannel("DefaultCLIReceiver",
+		switchboard.AnyUser(), message.Text, recvCh)
+	jww.INFO.Printf("Message ListenerID: %v", listenerID)
+
+	// Set up auth request handler, which simply prints the
+	// user id of the requestor.
+	authMgr := client.GetAuthRegistrar()
+	authMgr.AddGeneralRequestCallback(printChanRequest)
+...
+func printChanRequest(requestor contact.Contact, message string) {
+	msg := fmt.Sprintf("Authentication channel request from: %s\n",
+		requestor.ID)
+	jww.INFO.Printf(msg)
+	fmt.Printf(msg)
+	msg = fmt.Sprintf("Authentication channel request message: %s\n", message)
+	jww.INFO.Printf(msg)
+	fmt.Printf(msg)
+	// Or you can auto confirm with:
+	// err := client.ConfirmAuthenticatedChannel(
+	//	requestor)
+
+}
+```
+
+3. Start network threads and wait until network is healthy:
+```
+	err = client.StartNetworkFollower()
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+
+	// Wait until connected or crash on timeout
+	connected := make(chan bool, 10)
+	client.GetHealth().AddChannel(connected)
+	waitUntilConnected(connected)
+...
+func waitUntilConnected(connected chan bool) {
+	waitTimeout := time.Duration(viper.GetUint("waitTimeout"))
+	timeoutTimer := time.NewTimer(waitTimeout * time.Second)
+	isConnected := false
+	//Wait until we connect or panic if we can't by a timeout
+	for !isConnected {
+		select {
+		case isConnected = <-connected:
+			jww.INFO.Printf("Network Status: %v\n",
+				isConnected)
+			break
+		case <-timeoutTimer.C:
+			jww.FATAL.Panic("timeout on connection")
+		}
+	}
+}
+```
+
+4. Adding authenticated channels (if we haven't done it yet)
+```
+	if client.HasAuthenticatedChannel(recipientID) {
+		jww.INFO.Printf("Authenticated channel already in place for %s",
+			recipientID)
+		return
+	}
+	// Check if a channel exists for this recipientID
+	recipientContact, err := client.GetAuthenticatedChannelRequest(
+		recipientID)
+	if err == nil {
+		jww.INFO.Printf("Accepting existing channel request for %s",
+			recipientID)
+		err := client.ConfirmAuthenticatedChannel(recipientContact)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+		return
+	} else {
+		recipientContact = recipient
+	}
+
+	me := client.GetUser().GetContact()
+	jww.INFO.Printf("Requesting auth channel from: %s",
+		recipientID)
+	err := client.RequestAuthenticatedChannel(recipientContact,
+		me, msg)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+```
+
+5. Sending E2E and Unsafe Messages
+```
+	msg := message.Send{
+		Recipient:   recipientID,
+		Payload:     []byte(msgBody),
+		MessageType: message.Text,
+	}
+	paramsE2E := params.GetDefaultE2E()
+	paramsUnsafe := params.GetDefaultUnsafe()
+
+	fmt.Printf("Sending to %s: %s\n", recipientID, msgBody)
+	var roundIDs []id.Round
+	if unsafe {
+		roundIDs, err = client.SendUnsafe(msg,
+			paramsUnsafe)
+	} else {
+		roundIDs, _, err = client.SendE2E(msg,
+			paramsE2E)
+	}
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	jww.INFO.Printf("RoundIDs: %+v\n", roundIDs)
+```
+The "RoundIDs" are the rounds in which your message parts were sent. After those
+rounds have completed on the network, you can assume that the message has "sent"
+successfully. See the client interface section for info on how to access round
+state changes.
+
+6. Receiving Messages (assuming you set the receiver above in step 2)
+```
+	timeoutTimer := time.NewTimer(waitTimeout * time.Second)
+	select {
+	case <-timeoutTimer.C:
+		fmt.Println("Timed out!")
+		break
+	case m := <-recvCh:
+		fmt.Printf("Message received: %s\n", string(
+			m.Payload))
+		break
+	}
+```
+
+The main entry point for developing with the client is `api/client` (or
+`bindings/client`). We recommend using go doc to explore:
+
+```
+go doc -all ./api
+go doc -all ./interfaces
+```
+
+Looking at the API will, for example, show you there is a RoundEvents callback
+registration function, which lets your client see round events:
+
+```
+func (c *Client) GetRoundEvents() interfaces.RoundEvents
+    RegisterRoundEventsCb registers a callback for round events.
+```
+
+and then inside interfaces:
+
+```
+type RoundEvents interface {
+        // designates a callback to call on the specified event
+        // rid is the id of the round the event occurs on
+        // callback is the callback the event is triggered on
+        // timeout is the amount of time before an error event is returned
+        // valid states are the states which the event should trigger on
+        AddRoundEvent(rid id.Round, callback ds.RoundEventCallback,
+                timeout time.Duration, validStates ...states.Round) *ds.EventCallback
+
+        // designates a go channel to signal the specified event
+        // rid is the id of the round the event occurs on
+        // eventChan is the channel the event is triggered on
+        // timeout is the amount of time before an error event is returned
+        // valid states are the states which the event should trigger on
+        AddRoundEventChan(rid id.Round, eventChan chan ds.EventReturn,
+                timeout time.Duration, validStates ...states.Round) *ds.EventCallback
+
+        //Allows the un-registration of a round event before it triggers
+        Remove(rid id.Round, e *ds.EventCallback)
+}
+```
+
+Which, when investigated, yields the following prototype:
+
+```
+// Callbacks must use this function signature
+type RoundEventCallback func(ri *pb.RoundInfo, timedOut bool)
+```
+
+showing that you can receive a full RoundInfo object for any round event
+received by the client library on the network.
+
+### Building the Library for iOS and Android
+
+To set up Gomobile for Android, install the NDK and pass the -ndk flag
+to ` $ gomobile init`. Other repositories that use Gomobile for
+binding should include a shell script that creates the bindings. For
+iOS, gomobile must be run on an OS X machine with Xcode installed.
+
+Important reference info:
+1. [Setting up Gomobile and subcommands](https://godoc.org/golang.org/x/mobile/cmd/gomobile)
+2. [Reference cycles, type restrictions](https://godoc.org/golang.org/x/mobile/cmd/gobind)
+
+To clone and build:
+
+```
+# Go mobile install
+go get -u golang.org/x/mobile/cmd/gomobile
+go get -u golang.org/x/mobile/bind
+gomobile init... # Note this line will be different depending on sdk/target!
+# Get and test code
+git clone https://gitlab.com/elixxir/client.git client
+cd client
+go mod vendor -v
+go mod tidy
+go test ./...
+# Android
+gomobile bind -target android -androidapi 21 gitlab.com/elixxir/client/bindings
+# iOS
+gomobile bind -target ios gitlab.com/elixxir/client/bindings
+zip -r iOS.zip Bindings.framework
+```
+
+You can verify that all symbols got bound by unzipping
+`bindings-sources.jar` and inspecting the resulting source files.
+
+Every time you make a change to the client or bindings, you must
+rebuild the client bindings into a .aar or iOS.zip to propagate those
+changes to the app. There's a script that runs gomobile for you in the
+`bindings-integration` repository.
+
+## Roadmap
+
+See the larger network documentation for more, but there are 2 specific
+parts of the roadmap that are intended for the client:
+
+* Ephemeral IDs - Sending messages to users with temporal/ephemeral recipient
+  user identities.
+* User Discovery - A bot that will allow the user to look for others on the
+  network.
+* Notifications - An optional notifications system which uses firebase
+* Efficiency improvements - mechanisms for message pickup and network tracking 
+* will evolve to allow tradeoffs and options for use
+
+We also are always looking at how to simplify and improve the library interface.
diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go
new file mode 100644
index 0000000000000000000000000000000000000000..0debb583f3496b945f3601118a5940d69683a3bd
--- /dev/null
+++ b/api/authenticatedChannel.go
@@ -0,0 +1,125 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/auth"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// RequestAuthenticatedChannel sends a request to another party to establish an
+// authenticated channel
+// It will not run if the network status is not healthy
+// An error will be returned if a channel already exists, if a request was
+// already received, or if a request was already sent
+// When a confirmation occurs, the channel will be created and the callback
+// will be called
+func (c *Client) RequestAuthenticatedChannel(recipient, me contact.Contact,
+	message string) error {
+	jww.INFO.Printf("RequestAuthenticatedChannel(%s)", recipient.ID)
+
+	if !c.network.GetHealthTracker().IsHealthy() {
+		return errors.New("Cannot request authenticated channel " +
+			"creation when the network is not healthy")
+	}
+
+	return auth.RequestAuth(recipient, me, message, c.rng.GetStream(),
+		c.storage, c.network)
+}
+
+// GetAuthRegistrar gets the object which allows the registration of auth
+// callbacks
+func (c *Client) GetAuthRegistrar() interfaces.Auth {
+	jww.INFO.Printf("GetAuthRegistrar(...)")
+
+	return c.auth
+}
+
+// GetAuthenticatedChannelRequest returns the contact received in a request if
+// one exists for the given userID.  Returns an error if no contact is found.
+func (c *Client) GetAuthenticatedChannelRequest(partner *id.ID) (contact.Contact, error) {
+	jww.INFO.Printf("GetAuthenticatedChannelRequest(%s)", partner)
+
+	return c.storage.Auth().GetReceivedRequestData(partner)
+}
+
+// ConfirmAuthenticatedChannel creates an authenticated channel out of a valid
+// received request and sends a message to the requestor that the request has
+// been confirmed
+// It will not run if the network status is not healthy
+// An error will be returned if a channel already exists, if a request doest
+// exist, or if the passed in contact does not exactly match the received
+// request
+func (c *Client) ConfirmAuthenticatedChannel(recipient contact.Contact) error {
+	jww.INFO.Printf("ConfirmAuthenticatedChannel(%s)", recipient.ID)
+
+	if !c.network.GetHealthTracker().IsHealthy() {
+		return errors.New("Cannot request authenticated channel " +
+			"creation when the network is not healthy")
+	}
+
+	return auth.ConfirmRequestAuth(recipient, c.rng.GetStream(),
+		c.storage, c.network)
+}
+
+// VerifyOwnership checks if the ownership proof on a passed contact matches the
+// identity in a verified contact
+func (c *Client) VerifyOwnership(received, verified contact.Contact) bool {
+	jww.INFO.Printf("VerifyOwnership(%s)", received.ID)
+
+	return auth.VerifyOwnership(received, verified, c.storage)
+}
+
+// HasAuthenticatedChannel returns true if an authenticated channel exists for
+// the partner
+func (c *Client) HasAuthenticatedChannel(partner *id.ID) bool {
+	m, err := c.storage.E2e().GetPartner(partner)
+	return m != nil && err == nil
+}
+
+// Create an insecure e2e relationship with a precanned user
+func (c *Client) MakePrecannedAuthenticatedChannel(precannedID uint) (contact.Contact, error) {
+
+	precan := c.MakePrecannedContact(precannedID)
+
+	// add the precanned user as a e2e contact
+	sesParam := c.parameters.E2EParams
+	err := c.storage.E2e().AddPartner(precan.ID, precan.DhPubKey,
+		c.storage.E2e().GetDHPrivateKey(), sesParam, sesParam)
+
+	// check garbled messages in case any messages arrived before creating
+	// the channel
+	c.network.CheckGarbledMessages()
+
+	return precan, err
+}
+
+// Create an insecure e2e contact object for a precanned user
+func (c *Client) MakePrecannedContact(precannedID uint) contact.Contact {
+
+	e2eGrp := c.storage.E2e().GetGroup()
+
+	// get the user definition
+	precanned := createPrecannedUser(precannedID, c.rng.GetStream(),
+		c.storage.Cmix().GetGroup(), e2eGrp)
+
+	// compute their public e2e key
+	partnerPubKey := e2eGrp.ExpG(precanned.E2eDhPrivateKey, e2eGrp.NewInt(1))
+
+	return contact.Contact{
+		ID:             precanned.ReceptionID,
+		DhPubKey:       partnerPubKey,
+		OwnershipProof: nil,
+		Facts:          make([]fact.Fact, 0),
+	}
+}
diff --git a/api/client.go b/api/client.go
index 820c3457a9e3ffc7b5627b867580592cd8df483e..31a81017e4d6825f5f3d64f79d4844685ef1b110 100644
--- a/api/client.go
+++ b/api/client.go
@@ -1,627 +1,569 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
 
 package api
 
 import (
-	"bufio"
-	"crypto"
-	gorsa "crypto/rsa"
-	"crypto/sha256"
-	"encoding/base64"
-	"fmt"
-	"github.com/golang/protobuf/proto"
+	"time"
+
 	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/bots"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/io"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/rekey"
-	"gitlab.com/elixxir/client/user"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/auth"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/user"
+	"gitlab.com/elixxir/client/keyExchange"
+	"gitlab.com/elixxir/client/network"
+	"gitlab.com/elixxir/client/permissioning"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/client"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/crypto/tls"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"gitlab.com/xx_network/comms/connect"
-	goio "io"
-	"strings"
-	"testing"
-	"time"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/primitives/version"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/ndf"
 )
 
 type Client struct {
-	storage             globals.Storage
-	session             user.Session
-	receptionManager    *io.ReceptionManager
-	ndf                 *ndf.NetworkDefinition
-	topology            *connect.Circuit
-	opStatus            OperationProgressCallback
-	rekeyChan           chan struct{}
-	registrationVersion string
-
-	// Pointer to a send function, which allows testing to override the default
-	// using NewTestClient
-	sendFunc sender
-}
-
-// Type that defines what the default and any testing send functions should look like
-type sender func(message parse.MessageInterface, rm *io.ReceptionManager, session user.Session, topology *connect.Circuit, host *connect.Host) error
-
-//used to report the state of registration
-type OperationProgressCallback func(int)
-
-// Creates a new client with the default send function
-func NewClient(s globals.Storage, locA, locB string, ndfJSON *ndf.NetworkDefinition) (*Client, error) {
-	return newClient(s, locA, locB, ndfJSON, send)
-}
+	//generic RNG for client
+	rng *fastRNG.StreamGenerator
+	// the storage session securely stores data to disk and memoizes as is
+	// appropriate
+	storage *storage.Session
+	//the switchboard is used for inter-process signaling about received messages
+	switchboard *switchboard.Switchboard
+	//object used for communications
+	comms *client.Comms
+	// Network parameters
+	parameters params.Network
+
+	// note that the manager has a pointer to the context in many cases, but
+	// this interface allows it to be mocked for easy testing without the
+	// loop
+	network interfaces.NetworkManager
+	//object used to register and communicate with permissioning
+	permissioning *permissioning.Permissioning
+	//object containing auth interactions
+	auth *auth.Manager
+
+	//contains stopables for all running threads
+	runner *stoppable.Multi
+	status *statusTracker
+
+	//handler for external services
+	services *serviceProcessiesList
+
+	clientErrorChannel chan interfaces.ClientError
+}
+
+// NewClient creates client storage, generates keys, connects, and registers
+// with the network. Note that this does not register a username/identity, but
+// merely creates a new cryptographic identity for adding such information
+// at a later date.
+func NewClient(ndfJSON, storageDir string, password []byte, registrationCode string) error {
+	jww.INFO.Printf("NewClient()")
+	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	rngStream := rngStreamGen.GetStream()
+
+	// Parse the NDF
+	def, err := parseNDF(ndfJSON)
+	if err != nil {
+		return err
+	}
+	cmixGrp, e2eGrp := decodeGroups(def)
 
-// Creates a new test client with an overridden send function
-func NewTestClient(s globals.Storage, locA, locB string, ndfJSON *ndf.NetworkDefinition, i interface{}, sendFunc sender) (*Client, error) {
-	switch i.(type) {
-	case *testing.T:
-		break
-	case *testing.M:
-		break
-	case *testing.B:
-		break
-	default:
-		globals.Log.FATAL.Panicf("GenerateId is restricted to testing only. Got %T", i)
-	}
-	return newClient(s, locA, locB, ndfJSON, sendFunc)
-}
+	protoUser := createNewUser(rngStream, cmixGrp, e2eGrp)
 
-// Creates a new Client using the storage mechanism provided.
-// If none is provided, a default storage using OS file access
-// is created
-// returns a new Client object, and an error if it fails
-func newClient(s globals.Storage, locA, locB string, ndfJSON *ndf.NetworkDefinition, sendFunc sender) (*Client, error) {
-	var store globals.Storage
-	if s == nil {
-		globals.Log.INFO.Printf("No storage provided," +
-			" initializing Client with default storage")
-		store = &globals.DefaultStorage{}
-	} else {
-		store = s
+	// Get current client version
+	currentVersion, err := version.ParseVersion(SEMVER)
+	if err != nil {
+		return errors.WithMessage(err, "Could not parse version string.")
 	}
 
-	err := store.SetLocation(locA, locB)
-
+	// Create Storage
+	passwordStr := string(password)
+	storageSess, err := storage.New(storageDir, passwordStr, protoUser,
+		currentVersion, cmixGrp, e2eGrp, rngStreamGen)
 	if err != nil {
-		err = errors.New("Invalid Local Storage Location: " + err.Error())
-		globals.Log.ERROR.Printf(err.Error())
-		return nil, err
+		return err
 	}
 
-	cl := new(Client)
-	cl.storage = store
-	cl.ndf = ndfJSON
-	cl.sendFunc = sendFunc
+	// Save NDF to be used in the future
+	storageSess.SetBaseNDF(def)
 
-	//Create the cmix group and init the registry
-	cmixGrp := cyclic.NewGroup(
-		large.NewIntFromString(cl.ndf.CMIX.Prime, 16),
-		large.NewIntFromString(cl.ndf.CMIX.Generator, 16))
-	user.InitUserRegistry(cmixGrp)
+	//store the registration code for later use
+	storageSess.SetRegCode(registrationCode)
 
-	cl.opStatus = func(int) {
-		return
+	//move the registration state to keys generated
+	err = storageSess.ForwardRegistrationStatus(storage.KeyGenComplete)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to denote state "+
+			"change in session")
 	}
 
-	cl.rekeyChan = make(chan struct{}, 1)
-
-	return cl, nil
+	//TODO: close the session
+	return nil
 }
 
-// LoadSession loads the session object for the UID
-func (cl *Client) Login(password string) (*id.ID, error) {
-
-	var session user.Session
-	var err error
-	done := make(chan struct{})
-
-	// run session loading in a separate goroutine so if it panics it can
-	// be caught and an error can be returned
-	go func() {
-		defer func() {
-			if r := recover(); r != nil {
-				globals.Log.ERROR.Println("Session file loading crashed")
-				err = sessionFileError
-				done <- struct{}{}
-			}
-		}()
-
-		session, err = user.LoadSession(cl.storage, password)
-		done <- struct{}{}
-	}()
-
-	//wait for session file loading to complete
-	<-done
+// NewPrecannedClient creates an insecure user with predetermined keys with nodes
+// It creates client storage, generates keys, connects, and registers
+// with the network. Note that this does not register a username/identity, but
+// merely creates a new cryptographic identity for adding such information
+// at a later date.
+func NewPrecannedClient(precannedID uint, defJSON, storageDir string, password []byte) error {
+	jww.INFO.Printf("NewPrecannedClient()")
+	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	rngStream := rngStreamGen.GetStream()
 
+	// Parse the NDF
+	def, err := parseNDF(defJSON)
 	if err != nil {
-		return nil, errors.Wrap(err, "Login: Could not login")
+		return err
 	}
+	cmixGrp, e2eGrp := decodeGroups(def)
 
-	if session == nil {
-		return nil, errors.New("Unable to load session, no error reported")
-	}
-	if session.GetRegState() < user.KeyGenComplete {
-		return nil, errors.New("Cannot log a user in which has not " +
-			"completed registration ")
-	}
+	protoUser := createPrecannedUser(precannedID, rngStream, cmixGrp, e2eGrp)
 
-	cl.session = session
-	newRm, err := io.NewReceptionManager(cl.rekeyChan, cl.session.GetCurrentUser().User,
-		rsa.CreatePrivateKeyPem(cl.session.GetRSAPrivateKey()),
-		rsa.CreatePublicKeyPem(cl.session.GetRSAPublicKey()),
-		cl.session.GetSalt())
+	// Get current client version
+	currentVersion, err := version.ParseVersion(SEMVER)
 	if err != nil {
-		return nil, errors.Wrap(err, "Failed to create new reception manager")
+		return errors.WithMessage(err, "Could not parse version string.")
 	}
-	newRm.Comms.Manager = cl.receptionManager.Comms.Manager
-	cl.receptionManager = newRm
-	return cl.session.GetCurrentUser().User, nil
-}
 
-// Logout closes the connection to the server and the messageReceiver and clears out the client values,
-// so we can effectively shut everything down.  at this time it does
-// nothing with the user id. In the future this will release resources
-// and safely release any sensitive memory. Recommended time out is 500ms.
-func (cl *Client) Logout(timeoutDuration time.Duration) error {
-	if cl.session == nil {
-		err := errors.New("Logout: Cannot Logout when you are not logged in")
-		globals.Log.ERROR.Printf(err.Error())
+	// Create Storage
+	passwordStr := string(password)
+	storageSess, err := storage.New(storageDir, passwordStr, protoUser,
+		currentVersion, cmixGrp, e2eGrp, rngStreamGen)
+	if err != nil {
 		return err
 	}
 
-	// Here using a select statement and the fact that making cl.sess.GetQuitChan is blocking, we can detect when
-	// killing the reception manager is taking too long and we use the time out to stop the attempt and return an error.
-	timer := time.NewTimer(timeoutDuration)
-	select {
-	case cl.session.GetQuitChan() <- struct{}{}:
-		cl.receptionManager.Comms.DisconnectAll()
-	case <-timer.C:
-		return errors.Errorf("Message receiver shut down timed out after %s ms", timeoutDuration)
+	// Save NDF to be used in the future
+	storageSess.SetBaseNDF(def)
+
+	//move the registration state to indicate registered with permissioning
+	err = storageSess.ForwardRegistrationStatus(
+		storage.PermissioningComplete)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to denote state "+
+			"change in session")
 	}
 
-	// Store the user session files before logging out
-	errStore := cl.session.StoreSession()
-	if errStore != nil {
-		err := errors.New(fmt.Sprintf("Logout: Store Failed: %s" +
-			errStore.Error()))
-		globals.Log.ERROR.Printf(err.Error())
-		return err
+	//TODO: close the session
+	return nil
+}
+
+// OpenClient session, but don't connect to the network or log in
+func OpenClient(storageDir string, password []byte, parameters params.Network) (*Client, error) {
+	jww.INFO.Printf("OpenClient()")
+	// Use fastRNG for RNG ops (AES fortuna based RNG using system RNG)
+	rngStreamGen := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+
+	// Get current client version
+	currentVersion, err := version.ParseVersion(SEMVER)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Could not parse version string.")
 	}
 
-	// Clear all keys from ram
-	errImmolate := cl.session.Immolate()
-	cl.session = nil
-	if errImmolate != nil {
-		err := errors.New(fmt.Sprintf("Logout: Immolation Failed: %s" +
-			errImmolate.Error()))
-		globals.Log.ERROR.Printf(err.Error())
-		return err
+	// Load Storage
+	passwordStr := string(password)
+	storageSess, err := storage.Load(storageDir, passwordStr, currentVersion,
+		rngStreamGen)
+	if err != nil {
+		return nil, err
 	}
 
-	// Here we clear away all state in the client struct that should not be persistent
-	cl.session = nil
-	cl.receptionManager = nil
-	cl.topology = nil
-	cl.registrationVersion = ""
+	// Set up a new context
+	c := &Client{
+		storage:     storageSess,
+		switchboard: switchboard.New(),
+		rng:         rngStreamGen,
+		comms:       nil,
+		network:     nil,
+		runner:      stoppable.NewMulti("client"),
+		status:      newStatusTracker(),
+		parameters:  parameters,
+	}
 
-	return nil
+	return c, nil
 }
 
-// VerifyNDF verifies the signature of the network definition file (NDF) and
-// returns the structure. Panics when the NDF string cannot be decoded and when
-// the signature cannot be verified. If the NDF public key is empty, then the
-// signature verification is skipped and warning is printed.
-func VerifyNDF(ndfString, ndfPub string) *ndf.NetworkDefinition {
-	// If there is no public key, then skip verification and print warning
-	if ndfPub == "" {
-		globals.Log.WARN.Printf("Running without signed network " +
-			"definition file")
-	} else {
-		ndfReader := bufio.NewReader(strings.NewReader(ndfString))
-		ndfData, err := ndfReader.ReadBytes('\n')
-		ndfData = ndfData[:len(ndfData)-1]
-		if err != nil {
-			globals.Log.FATAL.Panicf("Could not read NDF: %v", err)
-		}
-		ndfSignature, err := ndfReader.ReadBytes('\n')
-		if err != nil {
-			globals.Log.FATAL.Panicf("Could not read NDF Sig: %v",
-				err)
-		}
-		ndfSignature, err = base64.StdEncoding.DecodeString(
-			string(ndfSignature[:len(ndfSignature)-1]))
-		if err != nil {
-			globals.Log.FATAL.Panicf("Could not read NDF Sig: %v",
-				err)
-		}
-		// Load the TLS cert given to us, and from that get the RSA public key
-		cert, err := tls.LoadCertificate(ndfPub)
-		if err != nil {
-			globals.Log.FATAL.Panicf("Could not load public key: %v", err)
-		}
-		pubKey := &rsa.PublicKey{PublicKey: *cert.PublicKey.(*gorsa.PublicKey)}
+// Login initalizes a client object from existing storage.
+func Login(storageDir string, password []byte, parameters params.Network) (*Client, error) {
+	jww.INFO.Printf("Login()")
+
+	//Open the client
+	c, err := OpenClient(storageDir, password, parameters)
 
-		// Hash NDF JSON
-		rsaHash := sha256.New()
-		rsaHash.Write(ndfData)
+	if err != nil {
+		return nil, err
+	}
 
-		globals.Log.INFO.Printf("%s \n::\n %s",
-			ndfSignature, ndfData)
+	//Attach the services interface
+	c.services = newServiceProcessiesList(c.runner)
+
+	//initilize comms
+	err = c.initComms()
+	if err != nil {
+		return nil, err
+	}
 
-		// Verify signature
-		err = rsa.Verify(
-			pubKey, crypto.SHA256, rsaHash.Sum(nil), ndfSignature, nil)
+	//get the NDF to pass into permissioning and the network manager
+	def := c.storage.GetBaseNDF()
 
+	//initialize permissioning
+	if def.Registration.Address != "" {
+		err = c.initPermissioning(def)
 		if err != nil {
-			globals.Log.FATAL.Panicf("Could not verify NDF: %v", err)
+			return nil, err
 		}
+	} else {
+		jww.WARN.Printf("Registration with permissioning skipped due to " +
+			"blank permissionign address. Client will not be able to register " +
+			"or track network.")
 	}
 
-	ndfJSON, _, err := ndf.DecodeNDF(ndfString)
+	// Initialize network and link it to context
+	c.network, err = network.NewManager(c.storage, c.switchboard, c.rng, c.comms,
+		parameters, def)
 	if err != nil {
-		globals.Log.FATAL.Panicf("Could not decode NDF: %v", err)
+		return nil, err
 	}
-	return ndfJSON
-}
 
-func (cl *Client) GetRegistrationVersion() string { // on client
-	return cl.registrationVersion
-}
+	//update gateway connections
+	err = c.network.GetInstance().UpdateGatewayConnections()
+	if err != nil {
+		return nil, err
+	}
 
-//GetNDF returns the clients ndf
-func (cl *Client) GetNDF() *ndf.NetworkDefinition {
-	return cl.ndf
-}
+	//initilize the auth tracker
+	c.auth = auth.NewManager(c.switchboard, c.storage, c.network)
 
-func (cl *Client) SetOperationProgressCallback(rpc OperationProgressCallback) {
-	cl.opStatus = func(i int) { go rpc(i) }
+	return c, nil
 }
 
-// Populates a text message and returns its wire representation
-// TODO support multi-type messages or telling if a message is too long?
-func FormatTextMessage(message string) []byte {
-	textMessage := cmixproto.TextMessage{
-		Color:   -1,
-		Message: message,
-		Time:    time.Now().Unix(),
-	}
+// LoginWithNewBaseNDF_UNSAFE initializes a client object from existing storage
+// while replacing the base NDF.  This is designed for some specific deployment
+// procedures and is generally unsafe.
+func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte,
+	newBaseNdf string, parameters params.Network) (*Client, error) {
+	jww.INFO.Printf("LoginWithNewBaseNDF_UNSAFE()")
 
-	wireRepresentation, _ := proto.Marshal(&textMessage)
-	return wireRepresentation
-}
+	// Parse the NDF
+	def, err := parseNDF(newBaseNdf)
+	if err != nil {
+		return nil, err
+	}
 
-var sessionFileError = errors.New("Session file cannot be loaded and " +
-	"is possibly corrupt. Please contact support@xxmessenger.io")
+	//Open the client
+	c, err := OpenClient(storageDir, password, parameters)
 
-func (cl *Client) InitListeners() error {
-	transmitGateway, err := id.Unmarshal(cl.ndf.Gateways[0].ID)
 	if err != nil {
-		globals.Log.DEBUG.Printf("%s: Gateways are: %+v", err.Error(),
-			cl.ndf.Gateways)
-		return err
-	}
-	transmissionHost, ok := cl.receptionManager.Comms.GetHost(transmitGateway)
-	if !ok {
-		return errors.New("Failed to retrieve host for transmission")
+		return nil, err
 	}
 
-	// Initialize UDB and nickname "bot" stuff here
-	bots.InitBots(cl.session, cl.receptionManager, cl.topology, transmissionHost)
-	// Initialize Rekey listeners
-	rekey.InitRekey(cl.session, cl.receptionManager, cl.topology, transmissionHost, cl.rekeyChan)
-	return nil
-}
+	//Attach the services interface
+	c.services = newServiceProcessiesList(c.runner)
 
-// Logs in user and sets session on client object
-// returns the nickname or error if login fails
-func (cl *Client) StartMessageReceiver(callback func(error)) error {
-	pollWaitTimeMillis := 500 * time.Millisecond
-	// TODO Don't start the message receiver if it's already started.
-	// Should be a pretty rare occurrence except perhaps for mobile.
-	receptionGateway, err := id.Unmarshal(cl.ndf.Gateways[len(cl.ndf.Gateways)-1].ID)
+	//initialize comms
+	err = c.initComms()
 	if err != nil {
-		return err
-	}
-	receptionHost, ok := cl.receptionManager.Comms.GetHost(receptionGateway)
-	if !ok {
-		return errors.New("Failed to retrieve host for transmission")
+		return nil, err
 	}
 
-	go func() {
-		defer func() {
-			if r := recover(); r != nil {
-				globals.Log.ERROR.Println("Message Receiver Panicked: ", r)
-				time.Sleep(1 * time.Second)
-				go func() {
-					callback(errors.New(fmt.Sprintln("Message Receiver Panicked", r)))
-				}()
-			}
-		}()
-		cl.receptionManager.MessageReceiver(cl.session, pollWaitTimeMillis, receptionHost, callback)
-	}()
+	//store the updated base NDF
+	c.storage.SetBaseNDF(def)
 
-	return nil
-}
+	//initialize permissioning
+	if def.Registration.Address != "" {
+		err = c.initPermissioning(def)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		jww.WARN.Printf("Registration with permissioning skipped due to " +
+			"blank permissionign address. Client will not be able to register " +
+			"or track network.")
+	}
 
-// Default send function, can be overridden for testing
-func (cl *Client) Send(message parse.MessageInterface) error {
-	transmitGateway, err := id.Unmarshal(cl.ndf.Gateways[0].ID)
+	// Initialize network and link it to context
+	c.network, err = network.NewManager(c.storage, c.switchboard, c.rng, c.comms,
+		parameters, def)
 	if err != nil {
-		return err
+		return nil, err
 	}
-	transmitGateway.SetType(id.Gateway)
-	host, ok := cl.receptionManager.Comms.GetHost(transmitGateway)
-	if !ok {
-		return errors.New("Failed to retrieve host for transmission")
+
+	//update gateway connections
+	err = c.network.GetInstance().UpdateGatewayConnections()
+	if err != nil {
+		return nil, err
 	}
 
-	return cl.sendFunc(message, cl.receptionManager, cl.session, cl.topology, host)
-}
+	//initilize the auth tracker
+	c.auth = auth.NewManager(c.switchboard, c.storage, c.network)
 
-// Send prepares and sends a message to the cMix network
-func send(message parse.MessageInterface, rm *io.ReceptionManager, session user.Session, topology *connect.Circuit, host *connect.Host) error {
-	recipientID := message.GetRecipient()
-	cryptoType := message.GetCryptoType()
-	return rm.SendMessage(session, topology, recipientID, cryptoType, message.Pack(), host)
+	return c, nil
 }
 
-// DisableBlockingTransmission turns off blocking transmission, for
-// use with the channel bot and dummy bot
-func (cl *Client) DisableBlockingTransmission() {
-	cl.receptionManager.DisableBlockingTransmission()
-}
+func (c *Client) initComms() error {
+	var err error
 
-// SetRateLimiting sets the minimum amount of time between message
-// transmissions just for testing, probably to be removed in production
-func (cl *Client) SetRateLimiting(limit uint32) {
-	cl.receptionManager.SetRateLimit(time.Duration(limit) * time.Millisecond)
-}
+	//get the user from session
+	u := c.storage.User()
+	cryptoUser := u.GetCryptographicIdentity()
 
-func (cl *Client) Listen(user *id.ID, messageType int32, newListener switchboard.Listener) string {
-	listenerId := cl.session.GetSwitchboard().
-		Register(user, messageType, newListener)
-	globals.Log.INFO.Printf("Listening now: user %v, message type %v, id %v",
-		user, messageType, listenerId)
-	return listenerId
+	//start comms
+	c.comms, err = client.NewClientComms(cryptoUser.GetTransmissionID(),
+		rsa.CreatePublicKeyPem(cryptoUser.GetTransmissionRSA().GetPublic()),
+		rsa.CreatePrivateKeyPem(cryptoUser.GetTransmissionRSA()),
+		cryptoUser.GetTransmissionSalt())
+	if err != nil {
+		return errors.WithMessage(err, "failed to load client")
+	}
+	return nil
 }
 
-func (cl *Client) StopListening(listenerHandle string) {
-	cl.session.GetSwitchboard().Unregister(listenerHandle)
-}
+func (c *Client) initPermissioning(def *ndf.NetworkDefinition) error {
+	var err error
+	//initialize permissioning
+	c.permissioning, err = permissioning.Init(c.comms, def)
+	if err != nil {
+		return errors.WithMessage(err, "failed to init "+
+			"permissioning handler")
+	}
 
-func (cl *Client) GetSwitchboard() *switchboard.Switchboard {
-	return cl.session.GetSwitchboard()
+	//register with permissioning if necessary
+	if c.storage.GetRegistrationStatus() == storage.KeyGenComplete {
+		jww.INFO.Printf("Client has not registered yet, attempting registration")
+		err = c.registerWithPermissioning()
+		if err != nil {
+			jww.ERROR.Printf("Client has failed registration: %s", err)
+			return errors.WithMessage(err, "failed to load client")
+		}
+		jww.INFO.Printf("Client sucsecfully registered with the network")
+	}
+	return nil
 }
 
-func (cl *Client) GetCurrentUser() *id.ID {
-	return cl.session.GetCurrentUser().User
-}
+// ----- Client Functions -----
+// StartNetworkFollower kicks off the tracking of the network. It starts
+// long running network client threads and returns an object for checking
+// state and stopping those threads.
+// Call this when returning from sleep and close when going back to
+// sleep.
+// These threads may become a significant drain on battery when offline, ensure
+// they are stopped if there is no internet access
+// Threads Started:
+//   - Network Follower (/network/follow.go)
+//   	tracks the network events and hands them off to workers for handling
+//   - Historical Round Retrieval (/network/rounds/historical.go)
+//		Retrieves data about rounds which are too old to be stored by the client
+//	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
+//		Requests all messages in a given round from the gateway of the last node
+//	 - Message Handling Worker Group (/network/message/handle.go)
+//		Decrypts and partitions messages when signals via the Switchboard
+//	 - Health Tracker (/network/health)
+//		Via the network instance tracks the state of the network
+//	 - Garbled Messages (/network/message/garbled.go)
+//		Can be signaled to check all recent messages which could be be decoded
+//		Uses a message store on disk for persistence
+//	 - Critical Messages (/network/message/critical.go)
+//		Ensures all protocol layer mandatory messages are sent
+//		Uses a message store on disk for persistence
+//	 - KeyExchange Trigger (/keyExchange/trigger.go)
+//		Responds to sent rekeys and executes them
+//   - KeyExchange Confirm (/keyExchange/confirm.go)
+//		Responds to confirmations of successful rekey operations
+//   - Auth Callback (/auth/callback.go)
+//      Handles both auth confirm and requests
+func (c *Client) StartNetworkFollower() (<-chan interfaces.ClientError, error) {
+	jww.INFO.Printf("StartNetworkFollower()")
+
+	c.clientErrorChannel = make(chan interfaces.ClientError, 1000)
+
+	cer := func(source, message, trace string) {
+		select {
+		case c.clientErrorChannel <- interfaces.ClientError{
+			Source:  source,
+			Message: message,
+			Trace:   trace,
+		}:
+		default:
+			jww.WARN.Printf("Failed to notify about ClientError from %s: %s", source, message)
+		}
+	}
 
-func (cl *Client) GetKeyParams() *keyStore.KeyParams {
-	return cl.session.GetKeyStore().GetKeyParams()
-}
+	err := c.status.toStarting()
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to Start the Network Follower")
+	}
 
-// Returns the local version of the client repo
-func GetLocalVersion() string {
-	return globals.SEMVER
-}
+	stopAuth := c.auth.StartProcessies()
+	c.runner.Add(stopAuth)
 
-type SearchCallback interface {
-	Callback(userID, pubKey []byte, err error)
-}
+	stopFollow, err := c.network.Follow(cer)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to start following "+
+			"the network")
+	}
+	c.runner.Add(stopFollow)
+	// Key exchange
+	c.runner.Add(keyExchange.Start(c.switchboard, c.storage, c.network, c.parameters.Rekey))
 
-// UDB Search API
-// Pass a callback function to extract results
-func (cl *Client) SearchForUser(emailAddress string,
-	cb SearchCallback, timeout time.Duration) {
-	//see if the user has been searched before, if it has, return it
-	uid, pk := cl.session.GetContactByValue(emailAddress)
-
-	if uid != nil {
-		cb.Callback(uid.Bytes(), pk, nil)
-	}
-
-	valueType := "EMAIL"
-	go func() {
-		uid, pubKey, err := bots.Search(valueType, emailAddress, cl.opStatus, timeout)
-		if err == nil && uid != nil && pubKey != nil {
-			cl.opStatus(globals.UDB_SEARCH_BUILD_CREDS)
-			err = cl.registerUserE2E(uid, pubKey)
-			if err != nil {
-				cb.Callback(uid[:], pubKey, err)
-				return
-			}
-			//store the user so future lookups can find it
-			cl.session.StoreContactByValue(emailAddress, uid, pubKey)
-
-			err = cl.session.StoreSession()
-			if err != nil {
-				cb.Callback(uid[:], pubKey, err)
-				return
-			}
-
-			// If there is something in the channel then send it; otherwise,
-			// skip over it
-			select {
-			case cl.rekeyChan <- struct{}{}:
-			default:
-			}
-
-			cb.Callback(uid[:], pubKey, err)
-
-		} else {
-			if err == nil {
-				globals.Log.INFO.Printf("UDB Search for email %s failed: user not found", emailAddress)
-				err = errors.New("user not found in UDB")
-				cb.Callback(nil, nil, err)
-			} else {
-				globals.Log.INFO.Printf("UDB Search for email %s failed: %+v", emailAddress, err)
-				cb.Callback(nil, nil, err)
-			}
+	err = c.status.toRunning()
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to Start the Network Follower")
+	}
 
-		}
-	}()
-}
+	c.services.run(c.runner)
 
-type NickLookupCallback interface {
-	Callback(nick string, err error)
+	return c.clientErrorChannel, nil
 }
 
-func (cl *Client) DeleteUser(u *id.ID) (string, error) {
-
-	//delete from session
-	v, err1 := cl.session.DeleteContact(u)
-
-	//delete from keystore
-	err2 := cl.session.GetKeyStore().DeleteContactKeys(u)
-
-	if err1 == nil && err2 == nil {
-		return v, nil
-	}
-
-	if err1 != nil && err2 == nil {
-		return "", errors.Wrap(err1, "Failed to remove from value store")
+// StopNetworkFollower stops the network follower if it is running.
+// It returns errors if the Follower is in the wrong status to stop or if it
+// fails to stop it.
+// if the network follower is running and this fails, the client object will
+// most likely be in an unrecoverable state and need to be trashed.
+func (c *Client) StopNetworkFollower(timeout time.Duration) error {
+	err := c.status.toStopping()
+	if err != nil {
+		return errors.WithMessage(err, "Failed to Stop the Network Follower")
 	}
-
-	if err1 == nil && err2 != nil {
-		return v, errors.Wrap(err2, "Failed to remove from key store")
+	close(c.clientErrorChannel)
+	err = c.runner.Close(timeout)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to Stop the Network Follower")
 	}
-
-	if err1 != nil && err2 != nil {
-		return "", errors.Wrap(fmt.Errorf("%s\n%s", err1, err2),
-			"Failed to remove from key store and value store")
+	c.runner = stoppable.NewMulti("client")
+	err = c.status.toStopped()
+	if err != nil {
+		return errors.WithMessage(err, "Failed to Stop the Network Follower")
 	}
-
-	return v, nil
-
+	return nil
 }
 
-// Nickname lookup API
-// Non-blocking, once the API call completes, the callback function
-// passed as argument is called
-func (cl *Client) LookupNick(user *id.ID,
-	cb NickLookupCallback) {
-	go func() {
-		nick, err := bots.LookupNick(user)
-		if err != nil {
-			globals.Log.INFO.Printf("Lookup for nickname for user %+v failed", user)
-		}
-		cb.Callback(nick, err)
-	}()
+// Gets the state of the network follower. Returns:
+// Stopped 	- 0
+// Starting - 1000
+// Running	- 2000
+// Stopping	- 3000
+func (c *Client) NetworkFollowerStatus() Status {
+	jww.INFO.Printf("NetworkFollowerStatus()")
+	return c.status.get()
 }
 
-//Message struct adherent to interface in bindings for data return from ParseMessage
-type ParsedMessage struct {
-	Typed   int32
-	Payload []byte
+// Returns the health tracker for registration and polling
+func (c *Client) GetHealth() interfaces.HealthTracker {
+	jww.INFO.Printf("GetHealth()")
+	return c.network.GetHealthTracker()
 }
 
-func (p ParsedMessage) GetSender() []byte {
-	return []byte{}
+// Returns the switchboard for Registration
+func (c *Client) GetSwitchboard() interfaces.Switchboard {
+	jww.INFO.Printf("GetSwitchboard()")
+	return c.switchboard
 }
 
-func (p ParsedMessage) GetPayload() []byte {
-	return p.Payload
+// RegisterRoundEventsCb registers a callback for round
+// events.
+func (c *Client) GetRoundEvents() interfaces.RoundEvents {
+	jww.INFO.Printf("GetRoundEvents()")
+	jww.WARN.Printf("GetRoundEvents does not handle Client Errors edge case!")
+	return c.network.GetInstance().GetRoundEvents()
 }
 
-func (p ParsedMessage) GetRecipient() []byte {
-	return []byte{}
+// AddService adds a service ot be controlled by the client thread control,
+// these will be started and stopped with the network follower
+func (c *Client) AddService(sp ServiceProcess) {
+	c.services.Add(sp)
 }
 
-func (p ParsedMessage) GetMessageType() int32 {
-	return p.Typed
+// GetUser returns the current user Identity for this client. This
+// can be serialized into a byte stream for out-of-band sharing.
+func (c *Client) GetUser() user.User {
+	jww.INFO.Printf("GetUser()")
+	return c.storage.GetUser()
 }
 
-func (p ParsedMessage) GetTimestampNano() int64 {
-	return 0
+// GetComms returns the client comms object
+func (c *Client) GetComms() *client.Comms {
+	return c.comms
 }
 
-func (p ParsedMessage) GetTimestamp() int64 {
-	return 0
+// GetRng returns the client rng object
+func (c *Client) GetRng() *fastRNG.StreamGenerator {
+	return c.rng
 }
 
-// Parses a passed message.  Allows a message to be aprsed using the interal parser
-// across the API
-func ParseMessage(message []byte) (ParsedMessage, error) {
-	tb, err := parse.Parse(message)
+// GetStorage returns the client storage object
+func (c *Client) GetStorage() *storage.Session {
+	return c.storage
+}
 
-	pm := ParsedMessage{}
+// GetNetworkInterface returns the client Network Interface
+func (c *Client) GetNetworkInterface() interfaces.NetworkManager {
+	return c.network
+}
 
-	if err != nil {
-		return pm, err
+// GetNodeRegistrationStatus gets the current status of node registration. It
+// returns the number of nodes that the client is registered and the number of
+// in progress node registrations. An error is returned if the network is not
+// healthy.
+func (c *Client) GetNodeRegistrationStatus() (int, int, error) {
+	// Return an error if the network is not healthy
+	if !c.GetHealth().IsHealthy() {
+		return 0, 0, errors.New("Cannot get number of node registrations when " +
+			"network is not healthy")
 	}
 
-	pm.Payload = tb.Body
-	pm.Typed = int32(tb.MessageType)
+	// Get the number of nodes that client is registered with
+	registeredNodes := c.storage.Cmix().Count()
 
-	return pm, nil
-}
+	// Get the number of in progress node registrations
+	inProgress := c.network.InProgressRegistrations()
 
-func (cl *Client) GetSessionData() ([]byte, error) {
-	return cl.session.GetSessionData()
+	return registeredNodes, inProgress, nil
 }
 
-// Set the output of the
-func SetLogOutput(w goio.Writer) {
-	globals.Log.SetLogOutput(w)
-}
-
-// GetSession returns the session object for external access.  Access at yourx
-// own risk
-func (cl *Client) GetSession() user.Session {
-	return cl.session
-}
-
-// ReceptionManager returns the comm manager object for external access.  Access
-// at your own risk
-func (cl *Client) GetCommManager() *io.ReceptionManager {
-	return cl.receptionManager
-}
+// ----- Utility Functions -----
+// parseNDF parses the initial ndf string for the client. do not check the
+// signature, it is deprecated.
+func parseNDF(ndfString string) (*ndf.NetworkDefinition, error) {
+	if ndfString == "" {
+		return nil, errors.New("ndf file empty")
+	}
 
-// LoadSessionText: load the encrypted session as a string
-func (cl *Client) LoadEncryptedSession() (string, error) {
-	encryptedSession, err := cl.GetSession().LoadEncryptedSession(cl.storage)
+	netDef, err := ndf.Unmarshal([]byte(ndfString))
 	if err != nil {
-		return "", err
+		return nil, err
 	}
-	//Encode session to bas64 for useability
-	encodedSession := base64.StdEncoding.EncodeToString(encryptedSession)
 
-	return encodedSession, nil
+	return netDef, nil
 }
 
-//WriteToSession: Writes an arbitrary string to the session file
-// Takes in a string that is base64 encoded (meant to be output of LoadEncryptedSession)
-func (cl *Client) WriteToSessionFile(replacement string, store globals.Storage) error {
-	//This call must not occur prior to a newClient call, thus check that client has been initialized
-	if cl.ndf == nil || cl.topology == nil {
-		errMsg := errors.Errorf("Cannot write to session if client hasn't been created yet")
-		return errMsg
-	}
-	//Decode the base64 encoded replacement string (assumed to be encoded form LoadEncryptedSession)
-	decodedSession, err := base64.StdEncoding.DecodeString(replacement)
-	if err != nil {
-		errMsg := errors.Errorf("Failed to decode replacment string: %+v", err)
-		return errMsg
-	}
-	//Write the new session data to both locations
-	err = user.WriteToSession(decodedSession, store)
-	if err != nil {
-		errMsg := errors.Errorf("Failed to store session: %+v", err)
-		return errMsg
-	}
+// decodeGroups returns the e2e and cmix groups from the ndf
+func decodeGroups(ndf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) {
+	largeIntBits := 16
 
-	return nil
+	//Generate the cmix group
+	cmixGrp = cyclic.NewGroup(
+		large.NewIntFromString(ndf.CMIX.Prime, largeIntBits),
+		large.NewIntFromString(ndf.CMIX.Generator, largeIntBits))
+	//Generate the e2e group
+	e2eGrp = cyclic.NewGroup(
+		large.NewIntFromString(ndf.E2E.Prime, largeIntBits),
+		large.NewIntFromString(ndf.E2E.Generator, largeIntBits))
+
+	return cmixGrp, e2eGrp
 }
diff --git a/api/client_test.go b/api/client_test.go
deleted file mode 100644
index bebd23da966c191ed8c45900a0626f25c54b4932..0000000000000000000000000000000000000000
--- a/api/client_test.go
+++ /dev/null
@@ -1,767 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package api
-
-import (
-	"github.com/golang/protobuf/proto"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/io"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/csprng"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"reflect"
-	"testing"
-	"time"
-)
-
-var TestKeySize = 768
-
-// Make sure that a formatted text message can deserialize to the text
-// message we would expect
-func TestFormatTextMessage(t *testing.T) {
-	msgText := "Hello"
-	msg := FormatTextMessage(msgText)
-	parsed := cmixproto.TextMessage{}
-	err := proto.Unmarshal(msg, &parsed)
-	// Make sure it parsed correctly
-	if err != nil {
-		t.Errorf("Got error parsing text message: %v", err.Error())
-	}
-	// Check the field that we explicitly set by calling the method
-	if parsed.Message != msgText {
-		t.Errorf("Got wrong text from parsing message. Got %v, expected %v",
-			parsed.Message, msgText)
-	}
-	// Make sure that timestamp is reasonable
-	timeDifference := time.Now().Unix() - parsed.Time
-	if timeDifference > 2 || timeDifference < -2 {
-		t.Errorf("Message timestamp was off by more than one second. "+
-			"Original time: %x, parsed time: %x", time.Now().Unix(), parsed.Time)
-	}
-	t.Logf("message: %q", msg)
-}
-
-//Happy path
-func TestParsedMessage_GetSender(t *testing.T) {
-	pm := ParsedMessage{}
-	sndr := pm.GetSender()
-
-	if !reflect.DeepEqual(sndr, []byte{}) {
-		t.Errorf("Sender not empty from typed message")
-	}
-}
-
-//Happy path
-func TestParsedMessage_GetPayload(t *testing.T) {
-	pm := ParsedMessage{}
-	payload := []byte{0, 1, 2, 3}
-	pm.Payload = payload
-	pld := pm.GetPayload()
-
-	if !reflect.DeepEqual(pld, payload) {
-		t.Errorf("Output payload does not match input payload: %v %v", payload, pld)
-	}
-}
-
-//Happy path
-func TestParsedMessage_GetRecipient(t *testing.T) {
-	pm := ParsedMessage{}
-	rcpt := pm.GetRecipient()
-
-	if !reflect.DeepEqual(rcpt, []byte{}) {
-		t.Errorf("Recipient not empty from typed message")
-	}
-}
-
-//Happy path
-func TestParsedMessage_GetMessageType(t *testing.T) {
-	pm := ParsedMessage{}
-	var typeTest int32
-	typeTest = 6
-	pm.Typed = typeTest
-	typ := pm.GetMessageType()
-
-	if typ != typeTest {
-		t.Errorf("Returned type does not match")
-	}
-}
-
-// Error path
-func TestNewClient_Panic(t *testing.T) {
-	defer func() {
-		if e := recover(); e != nil {
-
-		}
-
-	}()
-	// Arbitrary invalid interface
-	var i rsa.PublicKey
-	// Passed into NewTestClient call to cause a panic
-	NewTestClient(&globals.RamStorage{}, "", "", def, i, send)
-	t.Errorf("Failed to detect a bad interface passed in")
-}
-
-// Happy path
-func TestNewClient(t *testing.T) {
-	_, err := NewTestClient(&globals.RamStorage{}, "", "", def, t, send)
-	if err != nil {
-		t.Errorf("Expected happy path, received error: %+v", err)
-	}
-}
-
-//Happy path
-func TestParse(t *testing.T) {
-	ms := parse.Message{}
-	ms.Body = []byte{0, 1, 2}
-	ms.MessageType = int32(cmixproto.Type_NO_TYPE)
-	ms.Receiver = &id.ZeroUser
-	ms.Sender = &id.ZeroUser
-
-	messagePacked := ms.Pack()
-
-	msOut, err := ParseMessage(messagePacked)
-
-	if err != nil {
-		t.Errorf("Message failed to parse: %s", err.Error())
-	}
-
-	if msOut.GetMessageType() != int32(ms.MessageType) {
-		t.Errorf("Types do not match after message parse: %v vs %v", msOut.GetMessageType(), ms.MessageType)
-	}
-
-	if !reflect.DeepEqual(ms.Body, msOut.GetPayload()) {
-		t.Errorf("Bodies do not match after message parse: %v vs %v", msOut.GetPayload(), ms.Body)
-	}
-
-}
-
-// Test that registerUserE2E correctly creates keys and adds them to maps
-func TestRegisterUserE2E(t *testing.T) {
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-	if err != nil {
-		t.Error(err)
-	}
-
-	rng := csprng.NewSystemRNG()
-	cmixGrp, e2eGrp := getGroups()
-	userID := id.NewIdFromUInt(18, id.User, t)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	myPrivKeyCyclic := e2eGrp.RandomCoprime(e2eGrp.NewMaxInt())
-	myPubKeyCyclic := e2eGrp.ExpG(myPrivKeyCyclic, e2eGrp.NewMaxInt())
-
-	partnerPubKeyCyclic := e2eGrp.RandomCoprime(e2eGrp.NewMaxInt())
-
-	privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize)
-	publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey}
-
-	myUser := &user.User{User: userID, Username: "test"}
-	session := user.NewSession(testClient.storage,
-		myUser, &publicKeyRSA,
-		privateKeyRSA, nil, nil, myPubKeyCyclic, myPrivKeyCyclic, make([]byte, 1), cmixGrp,
-		e2eGrp, "password")
-
-	testClient.session = session
-
-	testClient.registerUserE2E(partner, partnerPubKeyCyclic.Bytes())
-
-	// Confirm we can get all types of keys
-	km := session.GetKeyStore().GetSendManager(partner)
-	if km == nil {
-		t.Errorf("KeyStore returned nil when obtaining KeyManager for sending")
-	}
-	key, action := km.PopKey()
-	if key == nil {
-		t.Errorf("TransmissionKeys map returned nil")
-	} else if key.GetOuterType() != parse.E2E {
-		t.Errorf("Key type expected 'E2E', got %s",
-			key.GetOuterType())
-	} else if action != keyStore.None {
-		t.Errorf("Expected 'None' action, got %s instead",
-			action)
-	}
-
-	key, action = km.PopRekey()
-	if key == nil {
-		t.Errorf("TransmissionReKeys map returned nil")
-	} else if key.GetOuterType() != parse.Rekey {
-		t.Errorf("Key type expected 'Rekey', got %s",
-			key.GetOuterType())
-	} else if action != keyStore.None {
-		t.Errorf("Expected 'None' action, got %s instead",
-			action)
-	}
-
-	// Generate one reception key of each type to test
-	// fingerprint map
-	baseKey, _ := diffieHellman.CreateDHSessionKey(partnerPubKeyCyclic, myPrivKeyCyclic, e2eGrp)
-	recvKeys := e2e.DeriveKeys(e2eGrp, baseKey, partner, uint(1))
-	recvReKeys := e2e.DeriveEmergencyKeys(e2eGrp, baseKey, partner, uint(1))
-
-	h, _ := hash.NewCMixHash()
-	h.Write(recvKeys[0].Bytes())
-	fp := format.Fingerprint{}
-	copy(fp[:], h.Sum(nil))
-
-	key = session.GetKeyStore().GetRecvKey(fp)
-	if key == nil {
-		t.Errorf("ReceptionKeys map returned nil for Key")
-	} else if key.GetOuterType() != parse.E2E {
-		t.Errorf("Key type expected 'E2E', got %s",
-			key.GetOuterType())
-	}
-
-	h.Reset()
-	h.Write(recvReKeys[0].Bytes())
-	copy(fp[:], h.Sum(nil))
-
-	key = session.GetKeyStore().GetRecvKey(fp)
-	if key == nil {
-		t.Errorf("ReceptionKeys map returned nil for ReKey")
-	} else if key.GetOuterType() != parse.Rekey {
-		t.Errorf("Key type expected 'Rekey', got %s",
-			key.GetOuterType())
-	}
-	disconnectServers()
-}
-
-// Test all keys created with registerUserE2E match what is expected
-func TestRegisterUserE2E_CheckAllKeys(t *testing.T) {
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-	if err != nil {
-		t.Error(err)
-	}
-
-	cmixGrp, e2eGrp := getGroups()
-	userID := id.NewIdFromUInt(18, id.User, t)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	rng := csprng.NewSystemRNG()
-	myPrivKeyCyclic := e2eGrp.RandomCoprime(e2eGrp.NewMaxInt())
-	myPubKeyCyclic := e2eGrp.ExpG(myPrivKeyCyclic, e2eGrp.NewMaxInt())
-
-	partnerPrivKeyCyclic := e2eGrp.RandomCoprime(e2eGrp.NewMaxInt())
-	partnerPubKeyCyclic := e2eGrp.ExpG(partnerPrivKeyCyclic, e2eGrp.NewMaxInt())
-
-	privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize)
-	publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey}
-
-	myUser := &user.User{User: userID, Username: "test"}
-	session := user.NewSession(testClient.storage,
-		myUser, &publicKeyRSA, privateKeyRSA, nil,
-		nil, myPubKeyCyclic, myPrivKeyCyclic,
-		make([]byte, 1), cmixGrp, e2eGrp, "password")
-
-	testClient.session = session
-
-	testClient.registerUserE2E(partner, partnerPubKeyCyclic.Bytes())
-
-	// Generate all keys and confirm they all match
-	keyParams := testClient.GetKeyParams()
-	baseKey, _ := diffieHellman.CreateDHSessionKey(partnerPubKeyCyclic, myPrivKeyCyclic, e2eGrp)
-	keyTTL, numKeys := e2e.GenerateKeyTTL(baseKey.GetLargeInt(),
-		keyParams.MinKeys, keyParams.MaxKeys, keyParams.TTLParams)
-
-	sendKeys := e2e.DeriveKeys(e2eGrp, baseKey, userID, uint(numKeys))
-	sendReKeys := e2e.DeriveEmergencyKeys(e2eGrp, baseKey,
-		userID, uint(keyParams.NumRekeys))
-	recvKeys := e2e.DeriveKeys(e2eGrp, baseKey, partner, uint(numKeys))
-	recvReKeys := e2e.DeriveEmergencyKeys(e2eGrp, baseKey,
-		partner, uint(keyParams.NumRekeys))
-
-	// Confirm all keys
-	km := session.GetKeyStore().GetSendManager(partner)
-	if km == nil {
-		t.Errorf("KeyStore returned nil when obtaining KeyManager for sending")
-	}
-	for i := 0; i < int(numKeys); i++ {
-		key, action := km.PopKey()
-		if key == nil {
-			t.Errorf("TransmissionKeys map returned nil")
-		} else if key.GetOuterType() != parse.E2E {
-			t.Errorf("Key type expected 'E2E', got %s",
-				key.GetOuterType())
-		}
-
-		if i < int(keyTTL-1) {
-			if action != keyStore.None {
-				t.Errorf("Expected 'None' action, got %s instead",
-					action)
-			}
-		} else {
-			if action != keyStore.Rekey {
-				t.Errorf("Expected 'Rekey' action, got %s instead",
-					action)
-			}
-		}
-
-		if key.GetKey().Cmp(sendKeys[int(numKeys)-1-i]) != 0 {
-			t.Errorf("Key value expected %s, got %s",
-				sendKeys[int(numKeys)-1-i].Text(10),
-				key.GetKey().Text(10))
-		}
-	}
-
-	for i := 0; i < int(keyParams.NumRekeys); i++ {
-		key, action := km.PopRekey()
-		if key == nil {
-			t.Errorf("TransmissionReKeys map returned nil")
-		} else if key.GetOuterType() != parse.Rekey {
-			t.Errorf("Key type expected 'Rekey', got %s",
-				key.GetOuterType())
-		}
-
-		if i < int(keyParams.NumRekeys-1) {
-			if action != keyStore.None {
-				t.Errorf("Expected 'None' action, got %s instead",
-					action)
-			}
-		} else {
-			if action != keyStore.Purge {
-				t.Errorf("Expected 'Purge' action, got %s instead",
-					action)
-			}
-		}
-
-		if key.GetKey().Cmp(sendReKeys[int(keyParams.NumRekeys)-1-i]) != 0 {
-			t.Errorf("Key value expected %s, got %s",
-				sendReKeys[int(keyParams.NumRekeys)-1-i].Text(10),
-				key.GetKey().Text(10))
-		}
-	}
-
-	h, _ := hash.NewCMixHash()
-	fp := format.Fingerprint{}
-
-	for i := 0; i < int(numKeys); i++ {
-		h.Reset()
-		h.Write(recvKeys[i].Bytes())
-		copy(fp[:], h.Sum(nil))
-		key := session.GetKeyStore().GetRecvKey(fp)
-		if key == nil {
-			t.Errorf("ReceptionKeys map returned nil for Key")
-		} else if key.GetOuterType() != parse.E2E {
-			t.Errorf("Key type expected 'E2E', got %s",
-				key.GetOuterType())
-		}
-
-		if key.GetKey().Cmp(recvKeys[i]) != 0 {
-			t.Errorf("Key value expected %s, got %s",
-				recvKeys[i].Text(10),
-				key.GetKey().Text(10))
-		}
-	}
-
-	for i := 0; i < int(keyParams.NumRekeys); i++ {
-		h.Reset()
-		h.Write(recvReKeys[i].Bytes())
-		copy(fp[:], h.Sum(nil))
-		key := session.GetKeyStore().GetRecvKey(fp)
-		if key == nil {
-			t.Errorf("ReceptionKeys map returned nil for Rekey")
-		} else if key.GetOuterType() != parse.Rekey {
-			t.Errorf("Key type expected 'Rekey', got %s",
-				key.GetOuterType())
-		}
-
-		if key.GetKey().Cmp(recvReKeys[i]) != 0 {
-			t.Errorf("Key value expected %s, got %s",
-				recvReKeys[i].Text(10),
-				key.GetKey().Text(10))
-		}
-	}
-	disconnectServers()
-}
-
-// Test happy path for precannedRegister
-func TestClient_precannedRegister(t *testing.T) {
-	//Start client
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Error(err)
-	}
-
-	_, _, _, err = testClient.precannedRegister("WTROXJ33")
-	if err != nil {
-		t.Errorf("Error during precannedRegister: %+v", err)
-	}
-
-	//Disconnect and shutdown servers
-	disconnectServers()
-}
-
-// Test happy path for sendRegistrationMessage
-func TestClient_sendRegistrationMessage(t *testing.T) {
-
-	//Start client
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Error(err)
-	}
-
-	rng := csprng.NewSystemRNG()
-	privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize)
-	publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey}
-
-	_, err = testClient.sendRegistrationMessage("WTROXJ33", &publicKeyRSA)
-	if err != nil {
-		t.Errorf("Error during sendRegistrationMessage: %+v", err)
-	}
-
-	//Disconnect and shutdown servers
-	disconnectServers()
-}
-
-// Test happy path for requestNonce
-func TestClient_requestNonce(t *testing.T) {
-	cmixGrp, _ := getGroups()
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewMaxInt())
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewMaxInt())
-	rng := csprng.NewSystemRNG()
-	privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize)
-	publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey}
-
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Error(err)
-	}
-
-	salt := make([]byte, 256)
-	_, err = csprng.NewSystemRNG().Read(salt)
-	if err != nil {
-		t.Errorf("Unable to generate salt! %s", err)
-	}
-
-	gwID, err := id.Unmarshal(testClient.ndf.Gateways[0].ID)
-	if err != nil {
-		t.Fatal(err)
-	}
-	gwID.SetType(id.Gateway)
-	_, _, err = testClient.requestNonce(salt, []byte("test"), publicKeyDH, &publicKeyRSA, privateKeyRSA, gwID)
-	if err != nil {
-		t.Errorf("Error during requestNonce: %+v", err)
-	}
-
-	disconnectServers()
-}
-
-// Test happy path for confirmNonce
-func TestClient_confirmNonce(t *testing.T) {
-
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Error(err)
-	}
-	rng := csprng.NewSystemRNG()
-	privateKeyRSA, _ := rsa.GenerateKey(rng, TestKeySize)
-	gwID, err := id.Unmarshal(testClient.ndf.Gateways[0].ID)
-	if err != nil {
-		t.Fatal(err)
-	}
-	gwID.SetType(id.Gateway)
-	err = testClient.confirmNonce([]byte("user"), []byte("test"), privateKeyRSA, gwID)
-	if err != nil {
-		t.Errorf("Error during confirmNonce: %+v", err)
-	}
-	//Disconnect and shutdown servers
-	disconnectServers()
-}
-
-func getGroups() (*cyclic.Group, *cyclic.Group) {
-
-	cmixGrp := cyclic.NewGroup(
-		large.NewIntFromString("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48"+
-			"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F"+
-			"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5"+
-			"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2"+
-			"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41"+
-			"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE"+
-			"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15"+
-			"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", 16),
-		large.NewIntFromString("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613"+
-			"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4"+
-			"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472"+
-			"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5"+
-			"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA"+
-			"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
-			"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
-			"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
-
-	e2eGrp := cyclic.NewGroup(
-		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B"+
-			"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE"+
-			"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F"+
-			"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041"+
-			"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45"+
-			"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209"+
-			"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29"+
-			"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E"+
-			"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2"+
-			"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696"+
-			"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E"+
-			"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873"+
-			"847AEF49F66E43873", 16),
-		large.NewIntFromString("2", 16))
-
-	return cmixGrp, e2eGrp
-
-}
-
-// Test happy path for client.GetSession
-func TestClient_GetSession(t *testing.T) {
-
-	//Start client
-	testClient, _ := NewClient(&globals.RamStorage{}, "", "", def)
-
-	testClient.session = &user.SessionObj{}
-
-	if !reflect.DeepEqual(testClient.GetSession(), testClient.session) {
-		t.Error("Received session not the same as the real session")
-	}
-
-}
-
-// Test happy path for client.GetCommManager
-func TestClient_GetCommManager(t *testing.T) {
-
-	//Start client
-	testClient, _ := NewClient(&globals.RamStorage{}, "", "", def)
-
-	testClient.receptionManager = &io.ReceptionManager{}
-
-	if !reflect.DeepEqual(testClient.GetCommManager(), testClient.receptionManager) {
-		t.Error("Received session not the same as the real session")
-	}
-}
-
-// Test that client.Shutcown clears out all the expected variables and stops the message reciever.
-func TestClient_LogoutHappyPath(t *testing.T) {
-	//Initialize a client
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	tc, _ := NewClient(&d, "", "", def)
-
-	uid := id.NewIdFromString("kk", id.User, t)
-	tc.receptionManager, _ = io.NewReceptionManager(tc.rekeyChan, uid, nil, nil, nil)
-
-	err := tc.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = tc.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	//Start Message reciever
-	callBack := func(err error) {
-		t.Log(err)
-	}
-
-	err = tc.StartMessageReceiver(callBack)
-	if err != nil {
-		t.Logf("Failed to start message reciever %+v", err)
-		t.Fail()
-	}
-
-	//Introduce a delay to allow things to startup and run
-	time.Sleep(1 * time.Second)
-
-	err = tc.Logout(500 * time.Millisecond)
-	if err != nil {
-		t.Logf("Timeout occured failed to shutdown %+v", err)
-		t.Fail()
-	}
-
-	//Check everything that should be nil is nil
-	if tc.session != nil {
-		t.Logf("Session should be set to nil on shutdown")
-		t.Fail()
-	}
-	if tc.registrationVersion != "" {
-		t.Logf("RegistrationVerison should be set to empty string on shutdown")
-		t.Fail()
-	}
-	if tc.receptionManager != nil {
-		t.Logf("ReceptionManager should be set to nil on shutdown")
-		t.Fail()
-	}
-	if tc.topology != nil {
-		t.Logf("Topology should be set to nil on shutdown")
-		t.Fail()
-	}
-
-	//Test that the things that should not be nil are not nil
-	if tc.ndf == nil {
-		t.Logf("NDF should not be set to nil")
-		t.Fail()
-	}
-	if tc.storage == nil {
-		t.Logf("Storage should not be set to nil")
-		t.Fail()
-	}
-	if tc.opStatus == nil {
-		t.Logf("OPstatus should not be set to nil on shutdown")
-		t.Fail()
-	}
-	if tc.rekeyChan == nil {
-		t.Logf("rekeyChan should not be set to nil on shutdown")
-		t.Fail()
-	}
-}
-
-//Test that the client shutdown will timeout when it fails to shutdown
-func TestClient_LogoutTimeout(t *testing.T) {
-	//Initialize a client
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	tc, _ := NewClient(&d, "", "", def)
-
-	uid := id.NewIdFromString("kk", id.User, t)
-	tc.receptionManager, _ = io.NewReceptionManager(tc.rekeyChan, uid, nil, nil, nil)
-
-	err := tc.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = tc.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Because we never initiated startMessageReceiver this should timeout.
-	err = tc.Logout(500 * time.Millisecond)
-	if err == nil {
-		t.Logf("Timeout out should have occured")
-		t.Fail()
-	}
-
-	//Check everything that should be nil is nil
-	if tc.session == nil {
-		t.Logf("Session should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-	if tc.registrationVersion == "" {
-		t.Logf("RegistrationVerison should not be set to empty string on shutdown timeout")
-		t.Fail()
-	}
-	if tc.receptionManager == nil {
-		t.Logf("ReceptionManager should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-	if tc.topology == nil {
-		t.Logf("Topology should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-	if tc.opStatus == nil {
-		t.Logf("OPstatus should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-	if tc.rekeyChan == nil {
-		t.Logf("rekeyChan should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-
-	//Test that the things that should not be nil are not nil
-	if tc.ndf == nil {
-		t.Logf("NDF should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-	if tc.storage == nil {
-		t.Logf("Storage should not be set to nil on shutdown timeout")
-		t.Fail()
-	}
-
-}
-
-// Test that if we logout we can logback in.
-func TestClient_LogoutAndLoginAgain(t *testing.T) {
-	//Initialize a client
-	storage := globals.RamStorage{}
-	tc, initialId := NewClient(&storage, "", "", def)
-
-	uid := id.NewIdFromString("kk", id.User, t)
-	tc.receptionManager, _ = io.NewReceptionManager(tc.rekeyChan, uid, nil, nil, nil)
-
-	err := tc.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = tc.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	//Start Message receiver
-	callBack := func(err error) {
-		t.Log(err)
-	}
-
-	err = tc.StartMessageReceiver(callBack)
-	if err != nil {
-		t.Logf("Failed to start message reciever %+v", err)
-		t.Fail()
-	}
-
-	// Because we never initiated startMessageReceiver this should timeout.
-	err = tc.Logout(500 * time.Millisecond)
-	if err != nil {
-		t.Logf("Timeout out should have not occured. %+v", err)
-		t.Fail()
-	}
-
-	//Redefine client with old session files and attempt to login.
-	tc, newId := NewClient(&storage, "", "", def)
-	err = tc.InitNetwork()
-	if err != nil {
-		t.Fatalf("InitNetwork should have succeeded when creating second client %v", err)
-	}
-
-	_, err = tc.Login("")
-	if err != nil {
-		t.Logf("Login failed %+v", err)
-		t.Fail()
-	}
-
-	if newId != initialId {
-		t.Logf("Failed to log user back in to original session")
-		t.Fail()
-	}
-
-}
diff --git a/api/connect.go b/api/connect.go
deleted file mode 100644
index 90a64cca5b2fc1076355521ea9c8afb3db996c5d..0000000000000000000000000000000000000000
--- a/api/connect.go
+++ /dev/null
@@ -1,230 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package api
-
-import (
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/io"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/elixxir/primitives/version"
-	"gitlab.com/xx_network/comms/connect"
-)
-
-var ErrNoPermissioning = errors.New("No Permissioning In NDF")
-
-// Checks version and connects to gateways using TLS filepaths to create
-// credential information for connection establishment
-// Call this before logging in!
-func (cl *Client) InitNetwork() error {
-	var err error
-	if cl.receptionManager == nil {
-		// Start reception manager with a dummy user, so we can connect to things
-		cl.receptionManager, err = io.NewReceptionManager(cl.rekeyChan, &id.DummyUser, nil, nil, nil)
-		if err != nil {
-			return errors.Wrap(err, "Failed to create reception manager")
-		}
-	}
-
-	//InitNetwork to permissioning
-	err = addPermissioningHost(cl.receptionManager, cl.ndf)
-
-	if err != nil {
-		if err != ErrNoPermissioning {
-			// Permissioning has an error so stop running
-			return err
-		}
-		globals.Log.WARN.Print("Skipping connection to permissioning, most likely no permissioning information in NDF")
-	}
-
-	runPermissioning := err != ErrNoPermissioning
-
-	if runPermissioning {
-		globals.Log.DEBUG.Printf("Setting up permissioning...")
-		err = cl.setupPermissioning()
-
-		if err != nil {
-			return err
-		}
-	}
-
-	// InitNetwork to nodes
-	cl.topology, err = BuildNodeTopology(cl.ndf)
-	if err != nil {
-		return err
-	}
-
-	err = addNotificationBotHost(cl.receptionManager, cl.ndf)
-	if err != nil {
-		return errors.Errorf("Failed to connect to notification bot at %+v", cl.ndf)
-	}
-
-	return addGatewayHosts(cl.receptionManager, cl.ndf)
-}
-
-// AddNotificationBotHost adds notification bot as a host within the reception manager
-func addNotificationBotHost(rm *io.ReceptionManager, definition *ndf.NetworkDefinition) error {
-
-	err := addHost(rm, &id.NotificationBot, definition.Notification.Address,
-		definition.Notification.TlsCertificate, false, true)
-	if err != nil {
-		return errors.Errorf("Failed to connect to notification bot at %+v",
-			definition.Notification.Address)
-	}
-	return nil
-}
-
-// BuildNodeTopology is a helper function which goes through the ndf and
-// builds a circuit for all the node's in the definition
-func BuildNodeTopology(definition *ndf.NetworkDefinition) (*connect.Circuit, error) {
-	//build the topology
-	nodeIDs := make([]*id.ID, len(definition.Nodes))
-	var err error
-	for i, node := range definition.Nodes {
-		nodeIDs[i], err = id.Unmarshal(node.ID)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return connect.NewCircuit(nodeIDs), nil
-}
-
-// DisableTls disables tls for communications
-func (cl *Client) DisableTls() {
-	globals.Log.INFO.Println("Running client without tls")
-	cl.receptionManager.Comms.DisableAuth()
-	cl.receptionManager.Tls = false
-}
-
-// Begin client version checks via registration server
-func (cl *Client) setupPermissioning() error {
-
-	//Get remote version and update
-	ver, err := cl.receptionManager.GetRemoteVersion()
-	if err != nil {
-		return err
-	}
-	cl.registrationVersion = ver
-
-	//Request a new ndf from permissioning
-	def, err = cl.receptionManager.Comms.RetrieveNdf(cl.ndf)
-	if err != nil {
-		return err
-	}
-	if def != nil {
-		cl.ndf = def
-	}
-
-	globals.Log.DEBUG.Printf("Local version: %v; Remote version: %v",
-		globals.SEMVER, cl.GetRegistrationVersion())
-
-	// Only check the version if we got a remote version
-	// The remote version won't have been populated if we didn't connect to permissioning
-	if cl.GetRegistrationVersion() != "" {
-		// Parse client version
-		clientVersion, err := version.ParseVersion(globals.SEMVER)
-		if err != nil {
-			return err
-		}
-
-		// Parse the permissioning version
-		regVersion, err := version.ParseVersion(cl.GetRegistrationVersion())
-		if err != nil {
-			return err
-		}
-
-		ok := version.IsCompatible(regVersion, clientVersion)
-		if !ok {
-			return errors.Errorf("Couldn't connect to gateways: Versions"+
-				" incompatible; Local version: %v; remote version: %v", globals.SEMVER,
-				cl.GetRegistrationVersion())
-		}
-	} else {
-		globals.Log.WARN.Printf("Not checking version from " +
-			"registration server, because it's not populated. Do you have " +
-			"access to the registration server?")
-	}
-
-	return nil
-
-}
-
-// Connects to gateways using tls filepaths to create credential information
-// for connection establishment
-func addGatewayHosts(rm *io.ReceptionManager, definition *ndf.NetworkDefinition) error {
-	if len(definition.Gateways) < 1 {
-		return errors.New("could not connect due to invalid number of nodes")
-	}
-
-	// connect to all gateways
-	var errs error = nil
-	for i, gateway := range definition.Gateways {
-		gwID, err := id.Unmarshal(definition.Gateways[i].ID)
-		if err != nil {
-			err = errors.Errorf("Failed to unmarshal gateway ID %s at index %v: %+v",
-				definition.Gateways[i].ID, i, err)
-			if errs != nil {
-				errs = err
-			} else {
-				errs = errors.Wrap(errs, err.Error())
-			}
-			continue
-		}
-		err = addHost(rm, gwID, gateway.Address, gateway.TlsCertificate, false, false)
-		if err != nil {
-			err = errors.Errorf("Failed to create host for gateway %s at %s: %+v",
-				gwID.String(), gateway.Address, err)
-			if errs != nil {
-				errs = err
-			} else {
-				errs = errors.Wrap(errs, err.Error())
-			}
-		}
-	}
-	return errs
-}
-
-func addHost(rm *io.ReceptionManager, id *id.ID, address, cert string, disableTimeout, enableAuth bool) error {
-	var creds []byte
-	if cert != "" && rm.Tls {
-		creds = []byte(cert)
-	}
-	params := connect.GetDefaultHostParams()
-	if disableTimeout {
-		params.MaxRetries = 0
-	}
-	params.AuthEnabled = enableAuth
-	_, err := rm.Comms.AddHost(id, address, creds, params)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// There's currently no need to keep connected to permissioning constantly,
-// so we have functions to connect to and disconnect from it when a connection
-// to permissioning is needed
-func addPermissioningHost(rm *io.ReceptionManager, definition *ndf.NetworkDefinition) error {
-	if definition.Registration.Address != "" {
-		err := addHost(rm, &id.Permissioning, definition.Registration.Address,
-			definition.Registration.TlsCertificate, false, false)
-		if err != nil {
-			return errors.New(fmt.Sprintf(
-				"Failed connecting to create host for permissioning: %+v", err))
-		}
-		return nil
-	} else {
-		globals.Log.DEBUG.Printf("failed to connect to %v silently", definition.Registration.Address)
-		// Without an NDF, we can't connect to permissioning, but this isn't an
-		// error per se, because we should be phasing out permissioning at some
-		// point
-		return ErrNoPermissioning
-	}
-}
diff --git a/api/mockserver.go b/api/mockserver.go
deleted file mode 100644
index 0bcfe43566dc9124567c8ce03b9e3ad0943d04fe..0000000000000000000000000000000000000000
--- a/api/mockserver.go
+++ /dev/null
@@ -1,404 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// This sets up a dummy/mock server instance for testing purposes
-package api
-
-import (
-	"encoding/json"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/xx_network/comms/connect"
-	"gitlab.com/xx_network/comms/messages"
-	"sync"
-	"time"
-)
-
-var def *ndf.NetworkDefinition
-
-const InvalidClientVersion = "1.1.0"
-const BatchSize = 10
-
-// ---------------------------------------- MOCK MESSAGE --------------------------------------------------
-
-// APIMessage are an implementation of the interface in bindings and API
-// easy to use from Go
-type APIMessage struct {
-	Payload     []byte
-	SenderID    *id.ID
-	RecipientID *id.ID
-}
-
-func (m APIMessage) GetSender() *id.ID {
-	return m.SenderID
-}
-
-func (m APIMessage) GetRecipient() *id.ID {
-	return m.RecipientID
-}
-
-func (m APIMessage) GetPayload() []byte {
-	return m.Payload
-}
-
-func (m APIMessage) GetMessageType() int32 {
-	return int32(cmixproto.Type_NO_TYPE)
-}
-
-func (m APIMessage) GetCryptoType() parse.CryptoType {
-	return parse.None
-}
-
-func (m APIMessage) GetTimestamp() time.Time {
-	return time.Now()
-}
-
-func (m APIMessage) Pack() []byte {
-	// assuming that the type is independently populated.
-	// that's probably a bad idea
-	// there's no good reason to have the same method body for each of these
-	// two methods!
-	return m.Payload
-}
-
-// -------------------------------------- MOCK DUMMY STORAGE ---------------------------------------
-
-// Mock dummy storage interface for testing.
-type DummyStorage struct {
-	LocationA string
-	LocationB string
-	StoreA    []byte
-	StoreB    []byte
-	mutex     sync.Mutex
-}
-
-func (d *DummyStorage) IsEmpty() bool {
-	return d.StoreA == nil && d.StoreB == nil
-}
-
-func (d *DummyStorage) SetLocation(lA, lB string) error {
-	d.LocationA = lA
-	d.LocationB = lB
-	return nil
-}
-
-func (d *DummyStorage) GetLocation() (string, string) {
-	//return fmt.Sprintf("%s,%s", d.LocationA, d.LocationB)
-	return d.LocationA, d.LocationB
-}
-
-func (d *DummyStorage) SaveA(b []byte) error {
-	d.StoreA = make([]byte, len(b))
-	copy(d.StoreA, b)
-	return nil
-}
-
-func (d *DummyStorage) SaveB(b []byte) error {
-	d.StoreB = make([]byte, len(b))
-	copy(d.StoreB, b)
-	return nil
-}
-
-func (d *DummyStorage) Lock() {
-	d.mutex.Lock()
-}
-
-func (d *DummyStorage) Unlock() {
-	d.mutex.Unlock()
-}
-
-func (d *DummyStorage) LoadA() []byte {
-	return d.StoreA
-}
-
-func (d *DummyStorage) LoadB() []byte {
-	return d.StoreB
-}
-
-type DummyReceiver struct {
-	LastMessage APIMessage
-}
-
-func (d *DummyReceiver) Receive(message APIMessage) {
-	d.LastMessage = message
-}
-
-// --------------------------------- MOCK REGISTRATION SERVER ------------------------------------------------
-// Blank struct implementing Registration Handler interface for testing purposes (Passing to StartServer)
-type MockRegistration struct {
-	//LastReceivedMessage pb.CmixMessage
-}
-
-func (s *MockRegistration) RegisterNode(salt []byte,
-	NodeTLSCert, GatewayTLSCert, RegistrationCode, Addr, Addr2 string) error {
-	return nil
-}
-
-func (s *MockRegistration) PollNdf(clientNdfHash []byte, auth *connect.Auth) ([]byte, error) {
-
-	ndfData := def
-	ndfJson, _ := json.Marshal(ndfData)
-	return ndfJson, nil
-}
-
-func (s *MockRegistration) Poll(*pb.PermissioningPoll, *connect.Auth, string) (*pb.PermissionPollResponse, error) {
-	return nil, nil
-}
-
-// Registers a user and returns a signed public key
-func (s *MockRegistration) RegisterUser(registrationCode,
-	key string) (hash []byte, err error) {
-	return nil, nil
-}
-
-func (s *MockRegistration) GetCurrentClientVersion() (version string, err error) {
-	return globals.SEMVER, nil
-}
-
-func (i *MockRegistration) CheckRegistration(msg *pb.RegisteredNodeCheck) (*pb.RegisteredNodeConfirmation, error) {
-	return nil, nil
-}
-
-//registration handler for getUpdatedNDF error case
-type MockPermNdfErrorCase struct {
-}
-
-func (s *MockPermNdfErrorCase) RegisterNode(ID []byte,
-	NodeTLSCert, GatewayTLSCert, RegistrationCode, Addr, Addr2 string) error {
-	return nil
-}
-
-func (s *MockPermNdfErrorCase) PollNdf(clientNdfHash []byte) ([]byte, error) {
-	errMsg := fmt.Sprintf("Permissioning server does not have an ndf to give to client")
-	return nil, errors.New(errMsg)
-}
-
-func (s *MockPermNdfErrorCase) RegisterUser(registrationCode,
-	key string) (hash []byte, err error) {
-	return nil, nil
-}
-
-func (s *MockPermNdfErrorCase) GetCurrentClientVersion() (version string, err error) {
-	return globals.SEMVER, nil
-}
-
-//Mock Permissioning handler for error cases involving check version
-type MockpermCheckversionErrorcase struct {
-}
-
-func (s *MockpermCheckversionErrorcase) RegisterNode(ID []byte,
-	NodeTLSCert, GatewayTLSCert, RegistrationCode, Addr, Addr2 string) error {
-	return nil
-}
-func (s *MockpermCheckversionErrorcase) PollNdf(clientNdfHash []byte) ([]byte, error) {
-	ndfData := def
-	ndfJson, _ := json.Marshal(ndfData)
-	return ndfJson, nil
-}
-func (s *MockpermCheckversionErrorcase) RegisterUser(registrationCode,
-	key string) (hash []byte, err error) {
-	return nil, nil
-}
-func (s *MockpermCheckversionErrorcase) GetCurrentClientVersion() (version string, err error) {
-	return globals.SEMVER, errors.New("Could not get version")
-}
-
-//Registration handler for handling a bad client version (aka client is not up to date)
-type MockPermCheckversionBadversion struct {
-}
-
-func (s *MockPermCheckversionBadversion) RegisterNode(ID []byte,
-	NodeTLSCert, GatewayTLSCert, RegistrationCode, Addr, Addr2 string) error {
-	return nil
-}
-
-func (s *MockPermCheckversionBadversion) GetUpdatedNDF(clientNdfHash []byte) ([]byte, error) {
-	ndfData := buildMockNDF()
-	ndfJson, _ := json.Marshal(ndfData)
-	return ndfJson, nil
-}
-
-func (s *MockPermCheckversionBadversion) RegisterUser(registrationCode,
-	key string) (hash []byte, err error) {
-	return nil, nil
-}
-
-func (s *MockPermCheckversionBadversion) GetCurrentClientVersion() (version string, err error) {
-	return InvalidClientVersion, nil
-}
-
-func buildMockNDF() ndf.NetworkDefinition {
-
-	ExampleJSON := `{"Timestamp":"2019-06-04T20:48:48-07:00","gateways":[{"Address":"0.0.0.0:7900","Tls_certificate":"-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIJAKLdZ8UigIAeMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEaMBgGA1UEAwwRZ2F0ZXdheSou\nY21peC5yaXAwHhcNMTkwMzA1MTgzNTU0WhcNMjkwMzAyMTgzNTU0WjBvMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ2xhcmVtb250\nMRswGQYDVQQKDBJQcml2YXRlZ3JpdHkgQ29ycC4xGjAYBgNVBAMMEWdhdGV3YXkq\nLmNtaXgucmlwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9+AaxwDP\nxHbhLmn4HoZu0oUM48Qufc6T5XEZTrpMrqJAouXk+61Jc0EFH96/sbj7VyvnXPRo\ngIENbk2Y84BkB9SkRMIXya/gh9dOEDSgnvj/yg24l3bdKFqBMKiFg00PYB30fU+A\nbe3OI/le0I+v++RwH2AV0BMq+T6PcAGjCC1Q1ZB0wP9/VqNMWq5lbK9wD46IQiSi\n+SgIQeE7HoiAZXrGO0Y7l9P3+VRoXjRQbqfn3ETNL9ZvQuarwAYC9Ix5MxUrS5ag\nOmfjc8bfkpYDFAXRXmdKNISJmtCebX2kDrpP8Bdasx7Fzsx59cEUHCl2aJOWXc7R\n5m3juOVL1HUxjQIDAQABoyAwHjAcBgNVHREEFTATghFnYXRld2F5Ki5jbWl4LnJp\ncDANBgkqhkiG9w0BAQUFAAOCAQEAMu3xoc2LW2UExAAIYYWEETggLNrlGonxteSu\njuJjOR+ik5SVLn0lEu22+z+FCA7gSk9FkWu+v9qnfOfm2Am+WKYWv3dJ5RypW/hD\nNXkOYxVJNYFxeShnHohNqq4eDKpdqSxEcuErFXJdLbZP1uNs4WIOKnThgzhkpuy7\ntZRosvOF1X5uL1frVJzHN5jASEDAa7hJNmQ24kh+ds/Ge39fGD8pK31CWhnIXeDo\nvKD7wivi/gSOBtcRWWLvU8SizZkS3hgTw0lSOf5geuzvasCEYlqrKFssj6cTzbCB\nxy3ra3WazRTNTW4TmkHlCUC9I3oWTTxw5iQxF/I2kQQnwR7L3w==\n-----END CERTIFICATE-----"},{"Address":"0.0.0.0:7901","Tls_certificate":"-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIJAKLdZ8UigIAeMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEaMBgGA1UEAwwRZ2F0ZXdheSou\nY21peC5yaXAwHhcNMTkwMzA1MTgzNTU0WhcNMjkwMzAyMTgzNTU0WjBvMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ2xhcmVtb250\nMRswGQYDVQQKDBJQcml2YXRlZ3JpdHkgQ29ycC4xGjAYBgNVBAMMEWdhdGV3YXkq\nLmNtaXgucmlwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9+AaxwDP\nxHbhLmn4HoZu0oUM48Qufc6T5XEZTrpMrqJAouXk+61Jc0EFH96/sbj7VyvnXPRo\ngIENbk2Y84BkB9SkRMIXya/gh9dOEDSgnvj/yg24l3bdKFqBMKiFg00PYB30fU+A\nbe3OI/le0I+v++RwH2AV0BMq+T6PcAGjCC1Q1ZB0wP9/VqNMWq5lbK9wD46IQiSi\n+SgIQeE7HoiAZXrGO0Y7l9P3+VRoXjRQbqfn3ETNL9ZvQuarwAYC9Ix5MxUrS5ag\nOmfjc8bfkpYDFAXRXmdKNISJmtCebX2kDrpP8Bdasx7Fzsx59cEUHCl2aJOWXc7R\n5m3juOVL1HUxjQIDAQABoyAwHjAcBgNVHREEFTATghFnYXRld2F5Ki5jbWl4LnJp\ncDANBgkqhkiG9w0BAQUFAAOCAQEAMu3xoc2LW2UExAAIYYWEETggLNrlGonxteSu\njuJjOR+ik5SVLn0lEu22+z+FCA7gSk9FkWu+v9qnfOfm2Am+WKYWv3dJ5RypW/hD\nNXkOYxVJNYFxeShnHohNqq4eDKpdqSxEcuErFXJdLbZP1uNs4WIOKnThgzhkpuy7\ntZRosvOF1X5uL1frVJzHN5jASEDAa7hJNmQ24kh+ds/Ge39fGD8pK31CWhnIXeDo\nvKD7wivi/gSOBtcRWWLvU8SizZkS3hgTw0lSOf5geuzvasCEYlqrKFssj6cTzbCB\nxy3ra3WazRTNTW4TmkHlCUC9I3oWTTxw5iQxF/I2kQQnwR7L3w==\n-----END CERTIFICATE-----"},{"Address":"0.0.0.0:7902","Tls_certificate":"-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIJAKLdZ8UigIAeMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEaMBgGA1UEAwwRZ2F0ZXdheSou\nY21peC5yaXAwHhcNMTkwMzA1MTgzNTU0WhcNMjkwMzAyMTgzNTU0WjBvMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ2xhcmVtb250\nMRswGQYDVQQKDBJQcml2YXRlZ3JpdHkgQ29ycC4xGjAYBgNVBAMMEWdhdGV3YXkq\nLmNtaXgucmlwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9+AaxwDP\nxHbhLmn4HoZu0oUM48Qufc6T5XEZTrpMrqJAouXk+61Jc0EFH96/sbj7VyvnXPRo\ngIENbk2Y84BkB9SkRMIXya/gh9dOEDSgnvj/yg24l3bdKFqBMKiFg00PYB30fU+A\nbe3OI/le0I+v++RwH2AV0BMq+T6PcAGjCC1Q1ZB0wP9/VqNMWq5lbK9wD46IQiSi\n+SgIQeE7HoiAZXrGO0Y7l9P3+VRoXjRQbqfn3ETNL9ZvQuarwAYC9Ix5MxUrS5ag\nOmfjc8bfkpYDFAXRXmdKNISJmtCebX2kDrpP8Bdasx7Fzsx59cEUHCl2aJOWXc7R\n5m3juOVL1HUxjQIDAQABoyAwHjAcBgNVHREEFTATghFnYXRld2F5Ki5jbWl4LnJp\ncDANBgkqhkiG9w0BAQUFAAOCAQEAMu3xoc2LW2UExAAIYYWEETggLNrlGonxteSu\njuJjOR+ik5SVLn0lEu22+z+FCA7gSk9FkWu+v9qnfOfm2Am+WKYWv3dJ5RypW/hD\nNXkOYxVJNYFxeShnHohNqq4eDKpdqSxEcuErFXJdLbZP1uNs4WIOKnThgzhkpuy7\ntZRosvOF1X5uL1frVJzHN5jASEDAa7hJNmQ24kh+ds/Ge39fGD8pK31CWhnIXeDo\nvKD7wivi/gSOBtcRWWLvU8SizZkS3hgTw0lSOf5geuzvasCEYlqrKFssj6cTzbCB\nxy3ra3WazRTNTW4TmkHlCUC9I3oWTTxw5iQxF/I2kQQnwR7L3w==\n-----END CERTIFICATE-----"}],"nodes":[{"Id":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Dsa_public_key":"-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAERwUmUlL9YP\nq6MSn+bUr6qNZPsVYoQAo8nTjZWiuSjJa2XWnh7sftnISWkwkiiRxo7qfq3sAiD5\nB8+tM6kONeICBXukldXJerxoVBspYa+RiPuDWy2pwGRDBpfty3QqJOpu5g2ThYFJ\nD5Xu0yCuX8ZJRj33nliI8dQgKdQQva6p2VuXzyRT8LwXMfRwLuSB6Schc9mF8C\nkWCb4m0ujlEKe1xKoKt2zG9b1o7XyaVhxguSUAuEznifMzsEUfuONJOy+XoQELex\nF0wvLzNzABcyxkM3lx52uG41mKgJiV6Z0ZyuBRvt+V3VL/38tPn9lsTaFi8N6/IH\nRyy0bWP5s44=\n-----END PUBLIC KEY-----\n","Address":"0.0.0.0:5900","Tls_certificate":"-----BEGIN CERTIFICATE-----MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDAeFwOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDhDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfsWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSEtJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uAm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEAAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIfU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1RtgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E56m52PyzMNV+2N21IPppKwA==-----END CERTIFICATE-----"},{"Id":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Dsa_public_key":"-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAFbADcqA8KQh\nxzgylW6VS1dYYelO5DjPZVVSjfdcbj1twu4ZHDNZLOexpv4nGY8xS6vesELXcVOR\n/CHXgh/3byBZYm0zkrBi/FsJJ3nP2uZ1+QCRldI2KzqcLOWH/CAYj8koork9k1Dp\nFq7rMSDgw4pktqvFj9Eev8dSZuRnoCfZbt/6vxi1r30AYAjDYOwcysqcVyUa1tPa\nLEh3JksttXUCd5cvfqatWedTs5Vxo7ICW1toGBHABYvSJkwK0YFfi5RLw+Oda1sA\njJ+aLcIxQjrpoRC2alXCdwmZXVb+O6zluQctw6LJjt4J704ueSvR4VNNhr0uLYGW\nk7e+WoQCS98=\n-----END PUBLIC KEY-----\n","Address":"0.0.0.0:5901","Tls_certificate":"-----BEGIN CERTIFICATE-----MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDAeFwOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDhDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfsWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSEtJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uAm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEAAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIfU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1RtgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E56m52PyzMNV+2N21IPppKwA==-----END CERTIFICATE-----"},{"Id":[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Dsa_public_key":"-----BEGIN PUBLIC KEY-----\nMIIDNTCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAQCN19tTnkS3\nitBQXXR/h8OKl+rliFBLgO6h6GvZL4yQDZFtBAOmkrs3wLoDroJRGCeqz/IUb+JF\njslEr/mpm2kcmK77hr535dq7HsWz1fFl9YyGTaOH055FLSV9QEPAV9j3zWADdQ1v\nuSQll+QfWi6lIibWV4HNQ2ywRFoOY8OBLCJB90UXLeJpaPanpqiM8hjda2VGRDbi\nIixEE2lCOWITydiz2DmvXrLhVGF49+g5MDwbWO65dmasCe//Ff6Z4bJ6n049xv\nVtac8nX6FO3eBsV5d+rG6HZXSG3brCKRCSKYCTX1IkTSiutYxYqvwaluoCjOakh0\nKkqvQ8IeVZ+B\n-----END PUBLIC KEY-----\n","Address":"0.0.0.0:5902","Tls_certificate":"-----BEGIN CERTIFICATE-----MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDAeFwOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDhDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfsWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSEtJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uAm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEAAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIfU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1RtgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E56m52PyzMNV+2N21IPppKwA==-----END CERTIFICATE-----"}],"registration":{"Address":"0.0.0.0:5000","Tls_certificate":""},"udb":{"Id":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3],"Dsa_public_key":"-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBACvR2lUslz3D\nB/MUo0rHVIHVkhVJCxNjtgTOYgJ9ckArSXQbYzr/fcigcNGjUO2LbK5NFp9GK43C\nrLxMUnJ9nkyIVPaWvquJFZItjcDK3NiNGyD4XyM0eRj4dYeSxQM48hvFbmtbjlXn\n9SQTnGIlr1XnTI4RVHZSQOL6kFJIaLw6wYrQ4w08Ng+p45brp5ercAHnLiftNUWP\nqROhQkdSEpS9LEwfotUSY1jP2AhQfaIMxaeXsZuTU1IYvdhMFRL3DR0r5Ww2Upf8\ng0Ace0mtnsUQ2OG+7MTh2jYIEWRjvuoe3RCz603ujW6g7BfQ1H7f4YFwc5xOOJ3u\nr4dj49dCCjc=\n-----END PUBLIC KEY-----\n"},"E2e":{"Prime":"E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873847AEF49F66E43873","Small_prime":"02","Generator":"02"},"CMIX":{"Prime":"9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B","Small_prime":"F2C3119374CE76C9356990B465374A17F23F9ED35089BD969F61C6DDE9998C1F","Generator":"5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7"}}`
-	retNDF, _, _ := ndf.DecodeNDF(ExampleJSON)
-	/*
-		var grp ndf.Group
-		grp.Prime = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
-			"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
-			"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
-			"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
-			"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
-			"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
-			"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
-			"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
-			"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
-			"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
-			"15728E5A8AACAA68FFFFFFFFFFFFFFFF"
-		grp.Generator = "2"
-		grp.SmallPrime = "2"
-		retNDF := ndf.NetworkDefinition{Timestamp: time.Now(), Registration: reg, Nodes: Nodes, CMIX: grp, E2E: grp}*/
-	return *retNDF
-}
-
-// ------------------------------------ MOCK GATEWAYS ----------------------------------------------------------------
-
-// Blank struct implementing ServerHandler interface for testing purposes (Passing to StartServer)
-type GatewayHandler struct {
-	LastReceivedMessage pb.Slot
-}
-
-func (m *GatewayHandler) PollForNotifications(auth *connect.Auth) ([]*id.ID, error) {
-
-	return nil, nil
-}
-
-func (m *GatewayHandler) Poll(*pb.GatewayPoll) (*pb.GatewayPollResponse, error) {
-	return nil, nil
-}
-
-// Returns message contents for MessageID, or a null/randomized message
-// if that ID does not exist of the same size as a regular message
-func (m *GatewayHandler) GetMessage(userId *id.ID,
-	msgId, ipaddr string) (*pb.Slot, error) {
-	return &pb.Slot{}, nil
-}
-
-// Return any MessageIDs in the globals for this User
-func (m *GatewayHandler) CheckMessages(userId *id.ID,
-	messageID, ipAddress string) ([]string, error) {
-	return make([]string, 0), nil
-}
-
-// PutMessage adds a message to the outgoing queue and
-// calls SendBatch when it's size is the batch size
-func (m *GatewayHandler) PutMessage(msg *pb.Slot, ipaddr string) error {
-	m.LastReceivedMessage = *msg
-	return nil
-}
-
-func (m *GatewayHandler) ConfirmNonce(message *pb.RequestRegistrationConfirmation, ipaddr string) (*pb.RegistrationConfirmation, error) {
-	regConfirmation := &pb.RegistrationConfirmation{
-		ClientSignedByServer: &messages.RSASignature{},
-	}
-
-	return regConfirmation, nil
-}
-
-// Pass-through for Registration Nonce Communication
-func (m *GatewayHandler) RequestNonce(message *pb.NonceRequest, ipaddr string) (*pb.Nonce, error) {
-	dh := getDHPubKey().Bytes()
-	return &pb.Nonce{
-		DHPubKey: dh,
-	}, nil
-}
-
-//Blank struct that has an error path f
-type GatewayHandlerMultipleMessages struct {
-	LastReceivedMessage []pb.Slot
-}
-
-func (m *GatewayHandlerMultipleMessages) GetMessage(userId *id.ID,
-	msgId, ipaddr string) (*pb.Slot, error) {
-	msg := []byte("Hello")
-	payload, err := e2e.Pad(msg, format.PayloadLen)
-	if err != nil {
-		fmt.Println("hello!")
-	}
-	return &pb.Slot{
-		PayloadA: payload,
-		PayloadB: payload,
-	}, nil
-}
-
-func (m *GatewayHandlerMultipleMessages) PollForNotifications(auth *connect.Auth) ([]*id.ID, error) {
-	return nil, nil
-}
-
-func (s *GatewayHandlerMultipleMessages) Poll(*pb.GatewayPoll) (*pb.GatewayPollResponse, error) {
-	return nil, nil
-}
-
-// Return any MessageIDs in the globals for this User
-func (m *GatewayHandlerMultipleMessages) CheckMessages(userId *id.ID,
-	messageID, ipaddr string) ([]string, error) {
-	msgs := []string{"a", "b", "c", "d", "e", "f", "g"}
-	return msgs, nil
-}
-
-// PutMessage adds a message to the outgoing queue and
-// calls SendBatch when it's size is the batch size
-func (m *GatewayHandlerMultipleMessages) PutMessage(msg *pb.Slot, ipaddr string) error {
-	for i := 0; i < BatchSize; i++ {
-		msg.Index = uint32(i)
-		m.LastReceivedMessage = append(m.LastReceivedMessage, *msg)
-	}
-	return nil
-}
-
-func (m *GatewayHandlerMultipleMessages) ConfirmNonce(message *pb.RequestRegistrationConfirmation, ipaddr string) (*pb.RegistrationConfirmation, error) {
-	return nil, nil
-}
-
-// Pass-through for Registration Nonce Communication
-func (m *GatewayHandlerMultipleMessages) RequestNonce(message *pb.NonceRequest, ipaddr string) (*pb.Nonce, error) {
-	return nil, nil
-}
-
-func getDHPubKey() *cyclic.Int {
-	cmixGrp := cyclic.NewGroup(
-		large.NewIntFromString("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48"+
-			"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F"+
-			"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5"+
-			"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2"+
-			"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41"+
-			"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE"+
-			"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15"+
-			"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", 16),
-		large.NewIntFromString("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613"+
-			"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4"+
-			"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472"+
-			"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5"+
-			"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA"+
-			"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
-			"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
-			"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
-
-	dh := cmixGrp.RandomCoprime(cmixGrp.NewMaxInt())
-	return cmixGrp.ExpG(dh, cmixGrp.NewMaxInt())
-}
-
-// --------------------------- MOCK NOTIFICATION BOT -------------------------------------------------------
-
-type MockNotificationHandler struct {
-}
-
-func (nb *MockNotificationHandler) RegisterForNotifications(clientToken []byte, auth *connect.Auth) error {
-	return nil
-}
-
-func (nb *MockNotificationHandler) UnregisterForNotifications(auth *connect.Auth) error {
-	return nil
-}
diff --git a/api/mockserver_test.go b/api/mockserver_test.go
deleted file mode 100644
index 86a26bf01b7db1641ea79003bdfbb33e6782a86f..0000000000000000000000000000000000000000
--- a/api/mockserver_test.go
+++ /dev/null
@@ -1,550 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// This sets up a dummy/mock server instance for testing purposes
-package api
-
-import (
-	"fmt"
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/comms/gateway"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/comms/notificationBot"
-	"gitlab.com/elixxir/comms/registration"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"os"
-	"strings"
-	"testing"
-	"time"
-)
-
-const NumNodes = 3
-const NumGWs = NumNodes
-const RegPort = 5000
-const GWErrorPort = 7800
-const GWsStartPort = 7900
-const PermErrorServerPort = 4000
-const NotificationBotPort = 6500
-const NotificationErrorPort = 6600
-
-var RegHandler = MockRegistration{}
-var RegComms *registration.Comms
-var NDFErrorReg = MockPermNdfErrorCase{}
-var ErrorDef *ndf.NetworkDefinition
-
-const ValidRegCode = "WTROXJ33"
-const InvalidRegCode = "INVALID_REG_CODE_"
-
-var RegGWHandlers [3]*GatewayHandler = [NumGWs]*GatewayHandler{
-	{LastReceivedMessage: pb.Slot{}},
-	{LastReceivedMessage: pb.Slot{}},
-	{LastReceivedMessage: pb.Slot{}},
-}
-var GWComms [NumGWs]*gateway.Comms
-var GWErrComms [NumGWs]*gateway.Comms
-
-var NotificationBotHandler = MockNotificationHandler{}
-var NotificationBotComms *notificationBot.Comms
-
-// Setups general testing params and calls test wrapper
-func TestMain(m *testing.M) {
-
-	// Set logging params
-	jww.SetLogThreshold(jww.LevelTrace)
-	jww.SetStdoutThreshold(jww.LevelTrace)
-
-	os.Exit(testMainWrapper(m))
-}
-
-//Happy path: test message receiver stating up
-func TestClient_StartMessageReceiver_MultipleMessages(t *testing.T) {
-	// Initialize client with dummy storage
-	testDef := getNDF()
-	for i := 0; i < NumNodes; i++ {
-		gwID := id.NewIdFromString("testGateway", id.Gateway, t)
-		gw := ndf.Gateway{
-			Address: string(fmtAddress(GWErrorPort + i)),
-			ID:      gwID.Marshal(),
-		}
-		testDef.Gateways = append(testDef.Gateways, gw)
-		GWErrComms[i] = gateway.StartGateway(gwID, gw.Address,
-			&GatewayHandlerMultipleMessages{}, nil, nil)
-
-	}
-
-	testDef.Nodes = def.Nodes
-
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", testDef)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	err = client.GenerateKeys(nil, "password")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register with a valid registration code
-	_, err = client.RegisterWithPermissioning(true, ValidRegCode)
-
-	if err != nil {
-		t.Errorf("Register failed: %s", err.Error())
-	}
-
-	err = client.RegisterWithNodes()
-	if err != nil && !strings.Contains(err.Error(), "No registration attempted, registration server not known") {
-		t.Error(err)
-	}
-
-	err = client.session.StoreSession()
-	if err != nil {
-		t.Errorf(err.Error())
-	}
-
-	// Login to gateway
-	_, err = client.Login("password")
-
-	if err != nil {
-		t.Errorf("Login failed: %s", err.Error())
-	}
-
-	cb := func(err error) {
-		t.Log(err)
-	}
-
-	err = client.StartMessageReceiver(cb)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-
-	time.Sleep(3 * time.Second)
-	for _, gw := range GWErrComms {
-		gw.DisconnectAll()
-	}
-
-}
-
-func TestRegister_ValidPrecannedRegCodeReturnsZeroID(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-	err = client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register precanned user with all gateways
-	regRes, err := client.RegisterWithPermissioning(true, ValidRegCode)
-
-	// Verify registration succeeds with valid precanned registration code
-	if err != nil {
-		t.Errorf("Registration failed: %s", err.Error())
-	}
-
-	if regRes.Cmp(&id.ZeroUser) {
-		t.Errorf("Invalid registration number received: %v", *regRes)
-	}
-	disconnectServers()
-}
-
-// Verify that registering with an invalid registration code will fail
-func TestRegister_InvalidPrecannedRegCodeReturnsError(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-	//Generate keys s.t. reg status is prepped for registration
-	err = client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register with invalid reg code
-	uid, err := client.RegisterWithPermissioning(true, InvalidRegCode)
-	if err == nil {
-		t.Errorf("Registration worked with invalid registration code! UID: %v", uid)
-	}
-
-	//Disconnect and shutdown servers
-	disconnectServers()
-}
-
-//Test that not running generateKeys results in an error. Without running the aforementioned function,
-// the registration state should be invalid and it should not run
-func TestRegister_InvalidRegState(t *testing.T) {
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-	//Individually run the helper functions for GenerateKeys, put info into client
-	privKey, pubKey, err := generateRsaKeys(nil)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-	cmixGrp, e2eGrp := generateGroups(def)
-	salt, _, usr, err := generateUserInformation(pubKey)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-	e2ePrivKey, e2ePubKey, err := generateE2eKeys(cmixGrp, e2eGrp)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-	cmixPrivKey, cmixPubKey, err := generateCmixKeys(cmixGrp)
-
-	client.session = user.NewSession(nil, usr, pubKey, privKey, cmixPubKey, cmixPrivKey, e2ePubKey, e2ePrivKey, salt, cmixGrp, e2eGrp, "")
-
-	//
-	_, err = client.RegisterWithPermissioning(false, ValidRegCode)
-	if err == nil {
-		t.Errorf("Registration worked with invalid registration state!")
-	}
-
-}
-
-func TestRegister_DeletedUserReturnsErr(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	// ...
-	tempUser, _ := user.Users.GetUser(id.NewIdFromUInt(5, id.User, t))
-	user.Users.DeleteUser(id.NewIdFromUInt(5, id.User, t))
-	err = client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register
-	_, err = client.RegisterWithPermissioning(true, ValidRegCode)
-	if err == nil {
-		t.Errorf("Registration worked with a deleted user: %s", err.Error())
-	}
-
-	// ...
-	user.Users.UpsertUser(tempUser)
-	//Disconnect and shutdown servers
-	disconnectServers()
-}
-
-func TestSend(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	err = client.GenerateKeys(nil, "password")
-
-	// Register with a valid registration code
-	userID, err := client.RegisterWithPermissioning(true, ValidRegCode)
-
-	if err != nil {
-		t.Errorf("Register failed: %s", err.Error())
-	}
-
-	err = client.RegisterWithNodes()
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = client.session.StoreSession()
-	if err != nil {
-		t.Errorf(err.Error())
-	}
-
-	// Login to gateway
-	_, err = client.Login("password")
-
-	if err != nil {
-		t.Errorf("Login failed: %s", err.Error())
-	}
-
-	cb := func(err error) {
-		t.Log(err)
-	}
-
-	err = client.StartMessageReceiver(cb)
-
-	if err != nil {
-		t.Errorf("Could not start message reception: %+v", err)
-	}
-
-	// Test send with invalid sender ID
-	err = client.Send(
-		APIMessage{
-			SenderID:    id.NewIdFromUInt(12, id.User, t),
-			Payload:     []byte("test"),
-			RecipientID: userID,
-		},
-	)
-
-	if err != nil {
-		// TODO: would be nice to catch the sender but we
-		// don't have the interface/mocking for that.
-		t.Errorf("error on first message send: %+v", err)
-	}
-
-	// Test send with valid inputs
-	err = client.Send(APIMessage{SenderID: userID, Payload: []byte("test"),
-		RecipientID: client.GetCurrentUser()})
-
-	if err != nil {
-		t.Errorf("Error sending message: %v", err)
-	}
-
-	err = client.Logout(100 * time.Millisecond)
-
-	if err != nil {
-		t.Errorf("Logout failed: %v", err)
-	}
-	disconnectServers()
-}
-
-func TestLogout(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := globals.RamStorage{}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	// Logout before logging in should return an error
-	err = client.Logout(500 * time.Millisecond)
-
-	if err == nil {
-		t.Errorf("Logout did not throw an error when called on a client that" +
-			" is not currently logged in.")
-	}
-
-	err = client.GenerateKeys(nil, "password")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register with a valid registration code
-	_, err = client.RegisterWithPermissioning(true, ValidRegCode)
-
-	if err != nil {
-		t.Errorf("Register failed: %s", err.Error())
-	}
-
-	err = client.RegisterWithNodes()
-	if err != nil {
-		t.Error(err)
-	}
-
-	// Login to gateway
-	_, err = client.Login("password")
-
-	if err != nil {
-		t.Errorf("Login failed: %s", err.Error())
-	}
-
-	cb := func(err error) {
-		t.Log(err)
-	}
-
-	err = client.StartMessageReceiver(cb)
-
-	if err != nil {
-		t.Errorf("Failed to start message reciever: %s", err.Error())
-	}
-
-	err = client.Logout(500 * time.Millisecond)
-
-	if err != nil {
-		t.Errorf("Logout failed: %v", err)
-	}
-
-	// Logout after logout has been called should return an error
-	err = client.Logout(500 * time.Millisecond)
-
-	if err == nil {
-		t.Errorf("Logout did not throw an error when called on a client that" +
-			" is not currently logged in.")
-	}
-
-	disconnectServers()
-}
-
-// Handles initialization of mock registration server,
-// gateways used for registration and gateway used for session
-func testMainWrapper(m *testing.M) int {
-
-	def = getNDF()
-	ErrorDef = getNDF()
-	// Start mock registration server and defer its shutdown
-	def.Registration = ndf.Registration{
-		Address: fmtAddress(RegPort),
-	}
-	ErrorDef.Registration = ndf.Registration{
-		Address: fmtAddress(PermErrorServerPort),
-	}
-
-	def.Notification = ndf.Notification{
-		Address: fmtAddress(NotificationBotPort),
-	}
-
-	for i := 0; i < NumNodes; i++ {
-		nId := new(id.ID)
-		nId[0] = byte(i)
-		nId.SetType(id.Node)
-		n := ndf.Node{
-			ID: nId[:],
-		}
-		def.Nodes = append(def.Nodes, n)
-		ErrorDef.Nodes = append(ErrorDef.Nodes, n)
-	}
-	startServers()
-	defer testWrapperShutdown()
-	return m.Run()
-}
-
-func testWrapperShutdown() {
-
-	for _, gw := range GWComms {
-		gw.Shutdown()
-
-	}
-	RegComms.Shutdown()
-	NotificationBotComms.Shutdown()
-}
-
-func fmtAddress(port int) string { return fmt.Sprintf("localhost:%d", port) }
-
-func getNDF() *ndf.NetworkDefinition {
-	return &ndf.NetworkDefinition{
-		E2E: ndf.Group{
-			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
-				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
-				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
-				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
-				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
-				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
-				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
-				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
-				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
-				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
-				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
-				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
-				"847AEF49F66E43873",
-			Generator: "2",
-		},
-		CMIX: ndf.Group{
-			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
-				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
-				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
-				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
-				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
-				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
-				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
-				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
-			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
-				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
-				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
-				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
-				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
-				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
-				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
-				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
-		},
-	}
-}
-
-func startServers() {
-	regId := new(id.ID)
-	copy(regId[:], "testServer")
-	regId.SetType(id.Generic)
-	RegComms = registration.StartRegistrationServer(regId, def.Registration.Address, &RegHandler, nil, nil)
-	def.Gateways = make([]ndf.Gateway, 0)
-
-	//Start up gateways
-	for i, handler := range RegGWHandlers {
-
-		gwID := new(id.ID)
-		copy(gwID[:], "testGateway")
-		gwID.SetType(id.Gateway)
-		gw := ndf.Gateway{
-			Address: fmtAddress(GWsStartPort + i),
-			ID:      gwID.Marshal(),
-		}
-
-		def.Gateways = append(def.Gateways, gw)
-		GWComms[i] = gateway.StartGateway(gwID, gw.Address, handler, nil, nil)
-	}
-
-	NotificationBotComms = notificationBot.StartNotificationBot(&id.NotificationBot, def.Notification.Address, &NotificationBotHandler, nil, nil)
-
-}
-
-func disconnectServers() {
-	for _, gw := range GWComms {
-		gw.DisconnectAll()
-
-	}
-	RegComms.DisconnectAll()
-	NotificationBotComms.DisconnectAll()
-}
diff --git a/api/ndf_test.go b/api/ndf_test.go
deleted file mode 100644
index 3a113b52c6cc323f4e478fc3fdf3c24991281823..0000000000000000000000000000000000000000
--- a/api/ndf_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                    /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package api
-
-import (
-	"crypto"
-	"crypto/rand"
-	"encoding/base64"
-	"fmt"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/ndf"
-	"reflect"
-	"testing"
-)
-
-var ExampleJSON = `{"Timestamp": "2019-06-04T20:48:48-07:00", "gateways": [{"Address": "52.25.135.52", "Tls_certificate": "-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIJAKLdZ8UigIAeMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEaMBgGA1UEAwwRZ2F0ZXdheSou\nY21peC5yaXAwHhcNMTkwMzA1MTgzNTU0WhcNMjkwMzAyMTgzNTU0WjBvMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ2xhcmVtb250\nMRswGQYDVQQKDBJQcml2YXRlZ3JpdHkgQ29ycC4xGjAYBgNVBAMMEWdhdGV3YXkq\nLmNtaXgucmlwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9+AaxwDP\nxHbhLmn4HoZu0oUM48Qufc6T5XEZTrpMrqJAouXk+61Jc0EFH96/sbj7VyvnXPRo\ngIENbk2Y84BkB9SkRMIXya/gh9dOEDSgnvj/yg24l3bdKFqBMKiFg00PYB30fU+A\nbe3OI/le0I+v++RwH2AV0BMq+T6PcAGjCC1Q1ZB0wP9/VqNMWq5lbK9wD46IQiSi\n+SgIQeE7HoiAZXrGO0Y7l9P3+VRoXjRQbqfn3ETNL9ZvQuarwAYC9Ix5MxUrS5ag\nOmfjc8bfkpYDFAXRXmdKNISJmtCebX2kDrpP8Bdasx7Fzsx59cEUHCl2aJOWXc7R\n5m3juOVL1HUxjQIDAQABoyAwHjAcBgNVHREEFTATghFnYXRld2F5Ki5jbWl4LnJp\ncDANBgkqhkiG9w0BAQUFAAOCAQEAMu3xoc2LW2UExAAIYYWEETggLNrlGonxteSu\njuJjOR+ik5SVLn0lEu22+z+FCA7gSk9FkWu+v9qnfOfm2Am+WKYWv3dJ5RypW/hD\nNXkOYxVJNYFxeShnHohNqq4eDKpdqSxEcuErFXJdLbZP1uNs4WIOKnThgzhkpuy7\ntZRosvOF1X5uL1frVJzHN5jASEDAa7hJNmQ24kh+ds/Ge39fGD8pK31CWhnIXeDo\nvKD7wivi/gSOBtcRWWLvU8SizZkS3hgTw0lSOf5geuzvasCEYlqrKFssj6cTzbCB\nxy3ra3WazRTNTW4TmkHlCUC9I3oWTTxw5iQxF/I2kQQnwR7L3w==\n-----END CERTIFICATE-----"}, {"Address": "52.25.219.38", "Tls_certificate": "-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIJAKLdZ8UigIAeMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEaMBgGA1UEAwwRZ2F0ZXdheSou\nY21peC5yaXAwHhcNMTkwMzA1MTgzNTU0WhcNMjkwMzAyMTgzNTU0WjBvMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ2xhcmVtb250\nMRswGQYDVQQKDBJQcml2YXRlZ3JpdHkgQ29ycC4xGjAYBgNVBAMMEWdhdGV3YXkq\nLmNtaXgucmlwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9+AaxwDP\nxHbhLmn4HoZu0oUM48Qufc6T5XEZTrpMrqJAouXk+61Jc0EFH96/sbj7VyvnXPRo\ngIENbk2Y84BkB9SkRMIXya/gh9dOEDSgnvj/yg24l3bdKFqBMKiFg00PYB30fU+A\nbe3OI/le0I+v++RwH2AV0BMq+T6PcAGjCC1Q1ZB0wP9/VqNMWq5lbK9wD46IQiSi\n+SgIQeE7HoiAZXrGO0Y7l9P3+VRoXjRQbqfn3ETNL9ZvQuarwAYC9Ix5MxUrS5ag\nOmfjc8bfkpYDFAXRXmdKNISJmtCebX2kDrpP8Bdasx7Fzsx59cEUHCl2aJOWXc7R\n5m3juOVL1HUxjQIDAQABoyAwHjAcBgNVHREEFTATghFnYXRld2F5Ki5jbWl4LnJp\ncDANBgkqhkiG9w0BAQUFAAOCAQEAMu3xoc2LW2UExAAIYYWEETggLNrlGonxteSu\njuJjOR+ik5SVLn0lEu22+z+FCA7gSk9FkWu+v9qnfOfm2Am+WKYWv3dJ5RypW/hD\nNXkOYxVJNYFxeShnHohNqq4eDKpdqSxEcuErFXJdLbZP1uNs4WIOKnThgzhkpuy7\ntZRosvOF1X5uL1frVJzHN5jASEDAa7hJNmQ24kh+ds/Ge39fGD8pK31CWhnIXeDo\nvKD7wivi/gSOBtcRWWLvU8SizZkS3hgTw0lSOf5geuzvasCEYlqrKFssj6cTzbCB\nxy3ra3WazRTNTW4TmkHlCUC9I3oWTTxw5iQxF/I2kQQnwR7L3w==\n-----END CERTIFICATE-----"}, {"Address": "52.41.80.104", "Tls_certificate": "-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIJAKLdZ8UigIAeMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx\nGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEaMBgGA1UEAwwRZ2F0ZXdheSou\nY21peC5yaXAwHhcNMTkwMzA1MTgzNTU0WhcNMjkwMzAyMTgzNTU0WjBvMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJQ2xhcmVtb250\nMRswGQYDVQQKDBJQcml2YXRlZ3JpdHkgQ29ycC4xGjAYBgNVBAMMEWdhdGV3YXkq\nLmNtaXgucmlwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9+AaxwDP\nxHbhLmn4HoZu0oUM48Qufc6T5XEZTrpMrqJAouXk+61Jc0EFH96/sbj7VyvnXPRo\ngIENbk2Y84BkB9SkRMIXya/gh9dOEDSgnvj/yg24l3bdKFqBMKiFg00PYB30fU+A\nbe3OI/le0I+v++RwH2AV0BMq+T6PcAGjCC1Q1ZB0wP9/VqNMWq5lbK9wD46IQiSi\n+SgIQeE7HoiAZXrGO0Y7l9P3+VRoXjRQbqfn3ETNL9ZvQuarwAYC9Ix5MxUrS5ag\nOmfjc8bfkpYDFAXRXmdKNISJmtCebX2kDrpP8Bdasx7Fzsx59cEUHCl2aJOWXc7R\n5m3juOVL1HUxjQIDAQABoyAwHjAcBgNVHREEFTATghFnYXRld2F5Ki5jbWl4LnJp\ncDANBgkqhkiG9w0BAQUFAAOCAQEAMu3xoc2LW2UExAAIYYWEETggLNrlGonxteSu\njuJjOR+ik5SVLn0lEu22+z+FCA7gSk9FkWu+v9qnfOfm2Am+WKYWv3dJ5RypW/hD\nNXkOYxVJNYFxeShnHohNqq4eDKpdqSxEcuErFXJdLbZP1uNs4WIOKnThgzhkpuy7\ntZRosvOF1X5uL1frVJzHN5jASEDAa7hJNmQ24kh+ds/Ge39fGD8pK31CWhnIXeDo\nvKD7wivi/gSOBtcRWWLvU8SizZkS3hgTw0lSOf5geuzvasCEYlqrKFssj6cTzbCB\nxy3ra3WazRTNTW4TmkHlCUC9I3oWTTxw5iQxF/I2kQQnwR7L3w==\n-----END CERTIFICATE-----"}], "nodes": [{"Id": [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "Dsa_public_key": "-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAERwUmUlL9YP\nq6MSn+bUr6qNZPsVYoQAo8nTjZWiuSjJa2XWnh7sftnISWkwkiiRxo7qfq3sAiD5\nB8+tM6kONeICBXukldXJerxoVBspYa+RiPuDWy2pwGRDBpfty3QqJOpu5g2ThYFJ\nD5Xu0yCuX8ZJRj33nliI8dQgKdQQva6p2VuXzyRT8LwXMfRwLuSB6Schc9mF8C\nkWCb4m0ujlEKe1xKoKt2zG9b1o7XyaVhxguSUAuEznifMzsEUfuONJOy+XoQELex\nF0wvLzNzABcyxkM3lx52uG41mKgJiV6Z0ZyuBRvt+V3VL/38tPn9lsTaFi8N6/IH\nRyy0bWP5s44=\n-----END PUBLIC KEY-----\n", "Address": "18.237.147.105", "Tls_certificate": "-----BEGIN CERTIFICATE-----MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDAeFwOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDhDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfsWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSEtJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uAm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEAAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIfU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1RtgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E56m52PyzMNV+2N21IPppKwA==-----END CERTIFICATE-----"}, {"Id": [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "Dsa_public_key": "-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAFbADcqA8KQh\nxzgylW6VS1dYYelO5DjPZVVSjfdcbj1twu4ZHDNZLOexpv4nGY8xS6vesELXcVOR\n/CHXgh/3byBZYm0zkrBi/FsJJ3nP2uZ1+QCRldI2KzqcLOWH/CAYj8koork9k1Dp\nFq7rMSDgw4pktqvFj9Eev8dSZuRnoCfZbt/6vxi1r30AYAjDYOwcysqcVyUa1tPa\nLEh3JksttXUCd5cvfqatWedTs5Vxo7ICW1toGBHABYvSJkwK0YFfi5RLw+Oda1sA\njJ+aLcIxQjrpoRC2alXCdwmZXVb+O6zluQctw6LJjt4J704ueSvR4VNNhr0uLYGW\nk7e+WoQCS98=\n-----END PUBLIC KEY-----\n", "Address": "52.11.136.238", "Tls_certificate": "-----BEGIN CERTIFICATE-----MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDAeFwOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDhDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfsWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSEtJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uAm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEAAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIfU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1RtgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E56m52PyzMNV+2N21IPppKwA==-----END CERTIFICATE-----"}, {"Id": [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "Dsa_public_key": "-----BEGIN PUBLIC KEY-----\nMIIDNTCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAQCN19tTnkS3\nitBQXXR/h8OKl+rliFBLgO6h6GvZL4yQDZFtBAOmkrs3wLoDroJRGCeqz/IUb+JF\njslEr/mpm2kcmK77hr535dq7HsWz1fFl9YyGTaOH055FLSV9QEPAV9j3zWADdQ1v\nuSQll+QfWi6lIibWV4HNQ2ywRFoOY8OBLCJB90UXLeJpaPanpqiM8hjda2VGRDbi\nIixEE2lCOWITydiz2DmvXrLhVGF49+g5MDwbWO65dmasCe//Ff6Z4bJ6n049xv\nVtac8nX6FO3eBsV5d+rG6HZXSG3brCKRCSKYCTX1IkTSiutYxYqvwaluoCjOakh0\nKkqvQ8IeVZ+B\n-----END PUBLIC KEY-----\n", "Address": "34.213.79.31", "Tls_certificate": "-----BEGIN CERTIFICATE-----MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDAeFwOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDhDwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfsWYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSEtJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uAm3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEAAaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAneUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIfU/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1RtgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E56m52PyzMNV+2N21IPppKwA==-----END CERTIFICATE-----"}], "registration": {"Dsa_public_key": "-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBAAlELnrXLG0s\n4yAAn7IsVWwY7swDnbBcsIF2cnef4tjm/nNwrFKp5AxYqgeXCiJM8VkyJrotWG50\nnXQwMCR6BsvYrlVt/RmQvR8BSrir62uSLK7hMKm7dXnFvtyFtjp91UwTRbIjxhUQ\nGYnhAzrkCDWo1m54ysqXEGlrVwvRXrCAXiLKPiTEIS+B4GFH9W26SwBxhFLNYSUk\nZZ7+4qwMf9aTu7kIpXTP3hNIyRCjtuZvo5SnymtbLARwTP943hW8MOj0+Ege+m1P\ntey6rkMUGQ86cgK9/7+7Jb+EwW5UxdQtFPUFeNKdQ6zDPS6qbliecUrsc12tdgeg\nhQyuMbyKUuo=\n-----END PUBLIC KEY-----\n", "Address": "registration.default.cmix.rip", "Tls_certificate": "-----BEGIN CERTIFICATE-----MIIDkDCCAnigAwIBAgIJAJnjosuSsP7gMA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEfMB0GA1UEAwwWcmVnaXN0cmF0aW9uKi5jbWl4LnJpcDAeFwOTAzMDUyMTQ5NTZaFw0yOTAzMDIyMTQ5NTZaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjEfMB0GA1UEAwwWcmVnaXN0cmF0aW9uKi5jbWl4LnJpcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOQKvqjdh35o+MECBhCwopJzPlQNmq2iPbewRNtI02bUNK3kLQUbFlYdzNGZS4GYXGc5O+jdi8Slx82r1kdjz5PPCNFBARIsOP/L8r3DGeW+yeJdgBZjm1s3ylkamt4Ajiq/bNjysS6L/WSOp+sVumDxtBEzO/UTU1O6QRnzUphLaiWENmErGvsH0CZVq38Ia58k/QjCAzpUcYi4j2l1fb07xqFcQD8H6SmUM297UyQosDrp8ukdIo31Koxr4XDnnNNsYStC26tzHMeKuJ2Wl+3YzsSyflfM2YEcKE31sqB9DS36UkJ8J84eLsHNImGg3WodFAviDB67+jXDbB30NkMCAwEAAaMlMCMwIQYDVR0RBBowGIIWcmVnaXN0cmF0aW9uKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEAF9mNzk+g+o626Rllt3f3/1qIyYQrYJ0BjSWCKYEFMCgZ4JibAJjAvIajhVYERtltffM+YKcdE2kTpdzJ0YJuUnRfuv6sVnXlVVugUUnd4IOigmjbCdM32k170CYMm0aiwGxl4FrNa8ei7AIax/s1n+sqWq3HeW5LXjnoVb+s3HeCWIuLfcgrurfye8FnNhy14HFzxVYYefIKmL+DPlcGGGm/PPYt3u4a2+rP3xaihc65dTa0u5tf/XPXtPxTDPFj2JeQDFxo7QRREbPD89CtYnwuP937CrkvCKrL0GkW1FViXKqZY9F5uhxrvLIpzhbNrs/EbtweY35XGLDCCMkg==-----END CERTIFICATE-----"}, "udb": {"Id": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3], "Dsa_public_key": "-----BEGIN PUBLIC KEY-----\nMIIDNDCCAiwCggEBAJ22+1lRtmu2/h4UDx0s5VAjdBYf1lON8WSCGGQvC1xIyPek\nGq36GHMkuHZ0+hgisA8ez4E2lD18VXVyZOWhpE/+AS6ZNuAMHT6TELAcfReYBdMF\niyqfS7b5cWv+YRfGtbPMTZvjQRBK1KgK1slOAF9LmT4U8JHrUXQ78zBQw43iNVZ+\nGzTD1qXAzqoaDzaCE8PRmEPQtLCdy5/HLTnI3kHxvxTUu0Vjyig3FiHK0zJLai05\nIUW+v6x0iAUjb1yi/pK4cc2PnDbTKStVCcqMqneirfx7/XfdpvcRJadFb+oVPkMy\nVqImHGoG7TaTeX55lfrVqrvPvj7aJ0HjdUBK4lsCIQDywxGTdM52yTVpkLRlN0oX\n8j+e01CJvZafYcbd6ZmMHwKCAQBcf/awb48UP+gohDNJPkdpxNmIrOW+JaDiSAln\nBxbGE9ewzuaTL4+qfETSyyRSPaU/vk9uw1lYktGqWMQyigbEahVmLn6qcDod7Pi7\nstBdvi65VsFCozhmHRBGHA0TVHIIUFfzSUMJ/6c8YR94syrbtXQMNhyfNb6QmX2y\nAU4u9apheC9Sq+uL1kMsTdCXvFQjsoXa+2DcNk6BYfSio1rKOhCxxNIDzHakcKM6\n/cvdkpWYWavYtW4XJSUteOrGbnG6muPx3SSHGZh0OTzU2DIYaABlR2Dh40wJ5NFV\nF5+ewNxEc/mWvc5u7Ryr7YtvEW962c9QXfD5mONKsnUUsP/nAoIBACvR2lUslz3D\nB/MUo0rHVIHVkhVJCxNjtgTOYgJ9ckArSXQbYzr/fcigcNGjUO2LbK5NFp9GK43C\nrLxMUnJ9nkyIVPaWvquJFZItjcDK3NiNGyD4XyM0eRj4dYeSxQM48hvFbmtbjlXn\n9SQTnGIlr1XnTI4RVHZSQOL6kFJIaLw6wYrQ4w08Ng+p45brp5ercAHnLiftNUWP\nqROhQkdSEpS9LEwfotUSY1jP2AhQfaIMxaeXsZuTU1IYvdhMFRL3DR0r5Ww2Upf8\ng0Ace0mtnsUQ2OG+7MTh2jYIEWRjvuoe3RCz603ujW6g7BfQ1H7f4YFwc5xOOJ3u\nr4dj49dCCjc=\n-----END PUBLIC KEY-----\n"}, "E2e": {"Prime": "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "Small_prime": "7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68948127044533E63A0105DF531D89CD9128A5043CC71A026EF7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9EE1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AFC1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36B3861AA7255E4C0278BA3604650C10BE19482F23171B671DF1CF3B960C074301CD93C1D17603D147DAE2AEF837A62964EF15E5FB4AAC0B8C1CCAA4BE754AB5728AE9130C4C7D02880AB9472D455655347FFFFFFFFFFFFFFF", "Generator": "02"}, "CMIX": {"Prime": "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "Small_prime": "7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68948127044533E63A0105DF531D89CD9128A5043CC71A026EF7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9EE1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AFC1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36B3861AA7255E4C0278BA3604650C10BE19482F23171B671DF1CF3B960C074301CD93C1D17603D147DAE2AEF837A62964EF15E5FB4AAC0B8C1CCAA4BE754AB5728AE9130C4C7D02880AB9472D455655347FFFFFFFFFFFFFFF", "Generator": "02"}}`
-
-// FIXME: This test is breaking grep in turn breaking our pipeline. we need to write a ticket to fix this.
-// Tests that VerifyNDF() correctly verifies the NDF signature.
-func TestVerifyNDF(t *testing.T) {
-	// Load tls private key
-	privKey, err := rsa.LoadPrivateKeyFromPem([]byte("-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC7Dkb6VXFn4cdp\nU0xh6ji0nTDQUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZr\ntzujFPBRFp9O14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfI\nTVCv8CLE0t1ibiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGes\nkWEFa2VttHqF910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq\n6/OAXCU1JLi3kW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzf\nrarmsGM0LZh6JY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYI\nCqldpt79gaET9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8V\nMKbrCaOkzD5zgnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4S\no9AppDQB41SH3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenP\nel2ApMXp+LVRdDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/u\nSALsU2v9UHBzprdrLSZk2YpozJb+CQIDAQABAoICAARjDFUYpeU6zVNyCauOM7BA\ns4FfQdHReg+zApTfWHosDQ04NIc9CGbM6e5E9IFlb3byORzyevkllf5WuMZVWmF8\nd1YBBeTftKYBn2Gwa42Ql9dl3eD0wQ1gUWBBeEoOVZQ0qskr9ynpr0o6TfciWZ5m\nF50UWmUmvc4ppDKhoNwogNU/pKEwwF3xOv2CW2hB8jyLQnk3gBZlELViX3UiFKni\n/rCfoYYvDFXt+ABCvx/qFNAsQUmerurQ3Ob9igjXRaC34D7F9xQ3CMEesYJEJvc9\nGjvr5DbnKnjx152HS56TKhK8gp6vGHJz17xtWECXD3dIUS/1iG8bqXuhdg2c+2aW\nm3MFpa5jgpAawUWc7c32UnqbKKf+HI7/x8J1yqJyNeU5SySyYSB5qtwTShYzlBW/\nyCYD41edeJcmIp693nUcXzU+UAdtpt0hkXS59WSWlTrB/huWXy6kYXLNocNk9L7g\niyx0cOmkuxREMHAvK0fovXdVyflQtJYC7OjJxkzj2rWO+QtHaOySXUyinkuTb5ev\nxNhs+ROWI/HAIE9buMqXQIpHx6MSgdKOL6P6AEbBan4RAktkYA6y5EtH/7x+9V5E\nQTIz4LrtI6abaKb4GUlZkEsc8pxrkNwCqOAE/aqEMNh91Na1TOj3f0/a6ckGYxYH\npyrvwfP2Ouu6e5FhDcCBAoIBAQDcN8mK99jtrH3q3Q8vZAWFXHsOrVvnJXyHLz9V\n1Rx/7TnMUxvDX1PIVxhuJ/tmHtxrNIXOlps80FCZXGgxfET/YFrbf4H/BaMNJZNP\nag1wBV5VQSnTPdTR+Ijice+/ak37S2NKHt8+ut6yoZjD7sf28qiO8bzNua/OYHkk\nV+RkRkk68Uk2tFMluQOSyEjdsrDNGbESvT+R1Eotupr0Vy/9JRY/TFMc4MwJwOoy\ns7wYr9SUCq/cYn7FIOBTI+PRaTx1WtpfkaErDc5O+nLLEp1yOrfktl4LhU/r61i7\nfdtafUACTKrXG2qxTd3w++mHwTwVl2MwhiMZfxvKDkx0L2gxAoIBAQDZcxKwyZOy\ns6Aw7igw1ftLny/dpjPaG0p6myaNpeJISjTOU7HKwLXmlTGLKAbeRFJpOHTTs63y\ngcmcuE+vGCpdBHQkaCev8cve1urpJRcxurura6+bYaENO6ua5VzF9BQlDYve0YwY\nlbJiRKmEWEAyULjbIebZW41Z4UqVG3MQI750PRWPW4WJ2kDhksFXN1gwSnaM46KR\nPmVA0SL+RCPcAp/VkImCv0eqv9exsglY0K/QiJfLy3zZ8QvAn0wYgZ3AvH3lr9rJ\nT7pg9WDb+OkfeEQ7INubqSthhaqCLd4zwbMRlpyvg1cMSq0zRvrFpwVlSY85lW4F\ng/tgjJ99W9VZAoIBAH3OYRVDAmrFYCoMn+AzA/RsIOEBqL8kaz/Pfh9K4D01CQ/x\naqryiqqpFwvXS4fLmaClIMwkvgq/90ulvuCGXeSG52D+NwW58qxQCxgTPhoA9yM9\nVueXKz3I/mpfLNftox8sskxl1qO/nfnu15cXkqVBe4ouD+53ZjhAZPSeQZwHi05h\nCbJ20gl66M+yG+6LZvXE96P8+ZQV80qskFmGdaPozAzdTZ3xzp7D1wegJpTz3j20\n3ULKAiIb5guZNU0tEZz5ikeOqsQt3u6/pVTeDZR0dxnyFUf/oOjmSorSG75WT3sA\n0ZiR0SH5mhFR2Nf1TJ4JHmFaQDMQqo+EG6lEbAECggEAA7kGnuQ0lSCiI3RQV9Wy\nAa9uAFtyE8/XzJWPaWlnoFk04jtoldIKyzHOsVU0GOYOiyKeTWmMFtTGANre8l51\nizYiTuVBmK+JD/2Z8/fgl8dcoyiqzvwy56kX3QUEO5dcKO48cMohneIiNbB7PnrM\nTpA3OfkwnJQGrX0/66GWrLYP8qmBDv1AIgYMilAa40VdSyZbNTpIdDgfP6bU9Ily\nG7gnyF47HHPt5Cx4ouArbMvV1rof7ytCrfCEhP21Lc46Ryxy81W5ZyzoQfSxfdKb\nGyDR+jkryVRyG69QJf5nCXfNewWbFR4ohVtZ78DNVkjvvLYvr4qxYYLK8PI3YMwL\nsQKCAQB9lo7JadzKVio+C18EfNikOzoriQOaIYowNaaGDw3/9KwIhRsKgoTs+K5O\ngt/gUoPRGd3M2z4hn5j4wgeuFi7HC1MdMWwvgat93h7R1YxiyaOoCTxH1klbB/3K\n4fskdQRxuM8McUebebrp0qT5E0xs2l+ABmt30Dtd3iRrQ5BBjnRc4V//sQiwS1aC\nYi5eNYCQ96BSAEo1dxJh5RI/QxF2HEPUuoPM8iXrIJhyg9TEEpbrEJcxeagWk02y\nOMEoUbWbX07OzFVvu+aJaN/GlgiogMQhb6IiNTyMlryFUleF+9OBA8xGHqGWA6nR\nOaRA5ZbdE7g7vxKRV36jT3wvD7W+\n-----END PRIVATE KEY-----\n"))
-	if err != nil || privKey == nil {
-		t.Error("Failed to load privKey\n")
-	}
-	// Sign the NDF
-	ndfJSON, _, _ := ndf.DecodeNDF(ExampleJSON + "\n")
-	rsaHash := crypto.SHA256.New()
-	rsaHash.Write([]byte(ExampleJSON))
-	signature, _ := rsa.Sign(
-		rand.Reader, privKey, crypto.SHA256, rsaHash.Sum(nil), nil)
-
-	// Print error on panic
-	defer func() {
-		if r := recover(); r != nil {
-			t.Errorf("VerifyNDF() panicked when it was not supposed to"+
-				"\n\treceived: %#v\n\texpected: %#v", r, nil)
-		}
-	}()
-
-	// Compose network definition string
-	ndfString := ExampleJSON + "\n" + base64.StdEncoding.EncodeToString(signature) + "\n"
-
-	// Run VerifyNDF()
-	fmt.Println(ndfString)
-	ndfJSONOutput := VerifyNDF(ndfString, "-----BEGIN CERTIFICATE-----\nMIIGHTCCBAWgAwIBAgIUOcAn9cpH+hyRH8/UfqtbFDoSxYswDQYJKoZIhvcNAQEL\nBQAwgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJQ2xhcmVt\nb250MRAwDgYDVQQKDAdFbGl4eGlyMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEZMBcG\nA1UEAwwQZ2F0ZXdheS5jbWl4LnJpcDEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZWxp\neHhpci5pbzAeFw0xOTA4MTYwMDQ4MTNaFw0yMDA4MTUwMDQ4MTNaMIGSMQswCQYD\nVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUNsYXJlbW9udDEQMA4GA1UE\nCgwHRWxpeHhpcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGTAXBgNVBAMMEGdhdGV3\nYXkuY21peC5yaXAxHzAdBgkqhkiG9w0BCQEWEGFkbWluQGVsaXh4aXIuaW8wggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7Dkb6VXFn4cdpU0xh6ji0nTDQ\nUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZrtzujFPBRFp9O\n14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfITVCv8CLE0t1i\nbiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGeskWEFa2VttHqF\n910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq6/OAXCU1JLi3\nkW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzfrarmsGM0LZh6\nJY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYICqldpt79gaET\n9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8VMKbrCaOkzD5z\ngnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4So9AppDQB41SH\n3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenPel2ApMXp+LVR\ndDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/uSALsU2v9UHBz\nprdrLSZk2YpozJb+CQIDAQABo2kwZzAdBgNVHQ4EFgQUDaTvG7SwgRQ3wcYx4l+W\nMcZjX7owHwYDVR0jBBgwFoAUDaTvG7SwgRQ3wcYx4l+WMcZjX7owDwYDVR0TAQH/\nBAUwAwEB/zAUBgNVHREEDTALgglmb28uY28udWswDQYJKoZIhvcNAQELBQADggIB\nADKz0ST0uS57oC4rT9zWhFqVZkEGh1x1XJ28bYtNUhozS8GmnttV9SnJpq0EBCm/\nr6Ub6+Wmf60b85vCN5WDYdoZqGJEBjGGsFzl4jkYEE1eeMfF17xlNUSdt1qLCE8h\nU0glr32uX4a6nsEkvw1vo1Liuyt+y0cOU/w4lgWwCqyweu3VuwjZqDoD+3DShVzX\n8f1p7nfnXKitrVJt9/uE+AtAk2kDnjBFbRxCfO49EX4Cc5rADUVXMXm0itquGBYp\nMbzSgFmsMp40jREfLYRRzijSZj8tw14c2U9z0svvK9vrLCrx9+CZQt7cONGHpr/C\n/GIrP/qvlg0DoLAtjea73WxjSCbdL3Nc0uNX/ymXVHdQ5husMCZbczc9LYdoT2VP\nD+GhkAuZV9g09COtRX4VP09zRdXiiBvweiq3K78ML7fISsY7kmc8KgVH22vcXvMX\nCgGwbrxi6QbQ80rWjGOzW5OxNFvjhvJ3vlbOT6r9cKZGIPY8IdN/zIyQxHiim0Jz\noavr9CPDdQefu9onizsmjsXFridjG/ctsJxcUEqK7R12zvaTxu/CVYZbYEUFjsCe\nq6ZAACiEJGvGeKbb/mSPvGs2P1kS70/cGp+P5kBCKqrm586FB7BcafHmGFrWhT3E\nLOUYkOV/gADT2hVDCrkPosg7Wb6ND9/mhCVVhf4hLGRh\n-----END CERTIFICATE-----\n")
-
-	// Check that the output is the expected NetworkDefinition structure
-	if !reflect.DeepEqual(ndfJSONOutput, ndfJSON) {
-		t.Errorf("VerifyNDF() did not output the correct "+
-			"NetworkDefinition structure"+
-			"\n\treceived: %#v\n\texpected: %#v",
-			ndfJSONOutput, ndfJSON)
-	}
-}
-
-// Tests that VerifyNDF() panics when given the incorrect RSA public key.
-func TestVerifyNDF_ErrPublicKey(t *testing.T) {
-	// Generate RSA private key and fake RSA public key
-	// Size of 768 is unsafe, but allows the test to run faster
-	privateKey, _ := rsa.GenerateKey(rand.Reader, 768)
-
-	privateKey2, _ := rsa.GenerateKey(rand.Reader, 768)
-	publicKey := &rsa.PublicKey{PublicKey: privateKey2.PublicKey}
-	publicKeyBytes := rsa.CreatePublicKeyPem(publicKey)
-
-	// Sign the NDF
-	ndfJSON, _, _ := ndf.DecodeNDF(ExampleJSON + "\n")
-	opts := rsa.NewDefaultOptions()
-	rsaHash := opts.Hash.New()
-	rsaHash.Write(ndfJSON.Serialize())
-	signature, _ := rsa.Sign(
-		rand.Reader, privateKey, opts.Hash, rsaHash.Sum(nil), nil)
-
-	// Print error on no panic
-	defer func() {
-		if r := recover(); r == nil {
-			t.Errorf("VerifyNDF() did not panic when expected when the "+
-				"public key is invalid"+
-				"\n\treceived: %#v\n\texpected: %#v",
-				r, "Could not verify NDF: crypto/rsa: verification error")
-		}
-	}()
-
-	// Compose network definition string
-	ndfString := ExampleJSON + "\n" + base64.StdEncoding.EncodeToString(signature)
-
-	// Run VerifyNDF()
-	VerifyNDF(ndfString, string(publicKeyBytes))
-}
-
-// Tests that VerifyNDF() panics when given an invalid NDF string.
-func TestVerifyNDF_ErrInvalidNDF(t *testing.T) {
-	// Generate RSA private key and fake RSA public key
-	// Size of 768 is unsafe, but allows the test to run faster
-	privateKey, _ := rsa.GenerateKey(rand.Reader, 768)
-
-	privateKey2, _ := rsa.GenerateKey(rand.Reader, 768)
-	publicKey := &rsa.PublicKey{PublicKey: privateKey2.PublicKey}
-	publicKeyBytes := rsa.CreatePublicKeyPem(publicKey)
-
-	// Sign the NDF
-	ndfJSON, _, _ := ndf.DecodeNDF(ExampleJSON + "\n")
-	opts := rsa.NewDefaultOptions()
-	rsaHash := opts.Hash.New()
-	rsaHash.Write(ndfJSON.Serialize())
-	signature, _ := rsa.Sign(
-		rand.Reader, privateKey, opts.Hash, rsaHash.Sum(nil), nil)
-
-	// Print error on no panic
-	defer func() {
-		if r := recover(); r == nil {
-			t.Errorf("VerifyNDF() did not panic when expected when given "+
-				"invalid NDF"+
-				"\n\treceived: %#v\n\texpected: %#v",
-				r, "Could not decode NDF: unexpected end of JSON input")
-		}
-	}()
-
-	// Compose network definition string
-	ndfString := "   \n" + base64.StdEncoding.EncodeToString(signature)
-
-	// Run VerifyNDF()
-	VerifyNDF(ndfString, string(publicKeyBytes))
-}
-
-// Tests that VerifyNDF() correctly outputs a NetworkDefinition structure and
-// skips verifying the signature when the public key is empty.
-func TestVerifyNDF_EmptyPublicKey(t *testing.T) {
-	// Generate RSA private and public keys
-	// Size of 768 is unsafe, but allows the test to run faster
-	privateKey, _ := rsa.GenerateKey(rand.Reader, 768)
-
-	// Sign the NDF
-	ndfJSON, _, _ := ndf.DecodeNDF(ExampleJSON + "\n")
-	opts := rsa.NewDefaultOptions()
-	rsaHash := opts.Hash.New()
-	rsaHash.Write(ndfJSON.Serialize())
-	signature, _ := rsa.Sign(
-		rand.Reader, privateKey, opts.Hash, rsaHash.Sum(nil), nil)
-
-	// Compose network definition string
-	ndfString := ExampleJSON + "\n" + base64.StdEncoding.EncodeToString(signature)
-
-	// Run VerifyNDF()
-	ndfJSONOutput := VerifyNDF(ndfString, "")
-
-	// Check that the output is the expected NetworkDefinition structure
-	if !reflect.DeepEqual(ndfJSONOutput, ndfJSON) {
-		t.Errorf("VerifyNDF() did not output the correct "+
-			"NetworkDefinition structure"+
-			"\n\treceived: %#v\n\texpected: %#v",
-			ndfJSONOutput, ndfJSON)
-	}
-}
diff --git a/api/notifications.go b/api/notifications.go
index 5d27bf73d22387c378b66f738478dfb927c6ba0f..dff1977765b31b98297feaa75827e1b2edc71fd6 100644
--- a/api/notifications.go
+++ b/api/notifications.go
@@ -1,52 +1,58 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
 package api
 
-import (
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/primitives/id"
-)
-
-// RegisterForNotifications sends a message to notification bot indicating it
-// is registering for notifications
-func (cl *Client) RegisterForNotifications(notificationToken []byte) error {
-	// Pull the host from the manage
-	notificationBotHost, ok := cl.receptionManager.Comms.GetHost(&id.NotificationBot)
-	if !ok {
-		return errors.New("Failed to retrieve host for notification bot")
-	}
-
-	// Send the register message
-	_, err := cl.receptionManager.Comms.RegisterForNotifications(notificationBotHost,
-		&mixmessages.NotificationToken{
-			Token: notificationToken,
-		})
-	if err != nil {
-		err := errors.Errorf(
-			"RegisterForNotifications: Unable to register for notifications! %s", err)
-		return err
-	}
+import jww "github.com/spf13/jwalterweatherman"
+
+// RegisterForNotifications allows a client to register for push
+// notifications.
+// Note that clients are not required to register for push notifications
+// especially as these rely on third parties (i.e., Firebase *cough*
+// *cough* google's palantir *cough*) that may represent a security
+// risk to the user.
+func (c *Client) RegisterForNotifications(token []byte) error {
+	jww.INFO.Printf("RegisterForNotifications(%s)", token)
+	// // Pull the host from the manage
+	// notificationBotHost, ok := cl.receptionManager.Comms.GetHost(&id.NotificationBot)
+	// if !ok {
+	// 	return errors.New("Failed to retrieve host for notification bot")
+	// }
+
+	// // Send the register message
+	// _, err := cl.receptionManager.Comms.RegisterForNotifications(notificationBotHost,
+	// 	&mixmessages.NotificationToken{
+	// 		Token: notificationToken,
+	// 	})
+	// if err != nil {
+	// 	err := errors.Errorf(
+	// 		"RegisterForNotifications: Unable to register for notifications! %s", err)
+	// 	return err
+	// }
 
 	return nil
-
 }
 
-// UnregisterForNotifications sends a message to notification bot indicating it
-// no longer wants to be registered for notifications
-func (cl *Client) UnregisterForNotifications() error {
-	// Pull the host from the manage
-	notificationBotHost, ok := cl.receptionManager.Comms.GetHost(&id.NotificationBot)
-	if !ok {
-		return errors.New("Failed to retrieve host for notification bot")
-	}
-
-	// Send the unregister message
-	_, err := cl.receptionManager.Comms.UnregisterForNotifications(notificationBotHost)
-	if err != nil {
-		err := errors.Errorf(
-			"RegisterForNotifications: Unable to register for notifications! %s", err)
-		return err
-	}
+// UnregisterForNotifications turns of notifications for this client
+func (c *Client) UnregisterForNotifications() error {
+	jww.INFO.Printf("UnregisterForNotifications()")
+	// // Pull the host from the manage
+	// notificationBotHost, ok := cl.receptionManager.Comms.GetHost(&id.NotificationBot)
+	// if !ok {
+	// 	return errors.New("Failed to retrieve host for notification bot")
+	// }
+
+	// // Send the unregister message
+	// _, err := cl.receptionManager.Comms.UnregisterForNotifications(notificationBotHost)
+	// if err != nil {
+	// 	err := errors.Errorf(
+	// 		"RegisterForNotifications: Unable to register for notifications! %s", err)
+	// 	return err
+	// }
 
 	return nil
-
 }
diff --git a/api/notifications_test.go b/api/notifications_test.go
deleted file mode 100644
index 99cf3c5edc57683f42646df5b242531433c8457c..0000000000000000000000000000000000000000
--- a/api/notifications_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package api
-
-import (
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"testing"
-)
-
-var dummy_key = `-----BEGIN PRIVATE KEY-----
-MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCrfJyqwVp2Wz6y
-FlmPtHBdXffUE1qAkVgZJ1GfErYfO/9wHMYfkihjib9ZFRsOBsIdNEK9Pp/nVJAH
-serTLEAwXpvB1EeTXyR/MTh8tVjQuX6RVIiU2gTIKVjZcUs7BaLBLhYXQ8XpjiUT
-BltqHa1pj7iU16941i8TxkR0KM90aXfKZBpcJdf0+hc2K1FPVYrtMsuEhOA3cODv
-pIU4jRejFSFKJeNwaSw9Y0xf/qTLBz5+ddiSw+3x3AwLuuwTzPVTGdKe57dOVzwq
-8N7KTzYprksZgCdEFVeGLy0WahSVYl7IGfzQFHX4J3rrvd5zU/VnL57eqDpB7h57
-pyle1roWNToRCOgSn9euSP+Jcq8Pg20i63djDDaL004U41FNTccnpSEIkd6f74h2
-LqY64Y1AtMbdozAW5d6EjWAGh0tZXc3aHh8pMBqJ9g/PAFdwCTAn2Q9gmI6NzoIu
-F4DFjka0GxrkFyp+f2ijOYmiDwx/ppMnKU4FnTim4FNUT6hKzdob4AvKXhOMEHNe
-AoA33h6AvuYcyHaBaOF2pFlNG+3QZNovw40gALse1/ydRHWqGo1g4B9boXXp2Xq3
-gcVqEShVMJNJeXKEOHQ8DsFncAr77jCN9qEICSUyICZDl/KVlRSe54i4VJ+6Noa9
-S8XmAfzAlsBAdRogFbLHAu45iXipzwIDAQABAoICAEdxL7e3u99JHjKFOySyUIml
-R0U0FuUvKBu6lLeHzRXwIffsFOI8OtVVIsGTGGVcjWwrRI6g029FfIeoKKN3cPp1
-v8AdlwAfiA3xTI4v4uN6E++p3wjcV1eoWhqkp2ncbDS85XklxAMMNAfcAyOPX5p1
-xLlFrhXSbWR4mjYmdl8SPVS1JYI0RecKdbccjtBVW/57xevci6itPxi3WsT3itxn
-Riok5L8FIeglQUFQzgjDaNa4c9SZCb1UJjSQ2B9bqOzI+kU3VdeuYiOlm7t/CpqM
-wT7LdBBaL894Qflvkkm15LTKltd9XrRWhlBGFrHHTZqCbVZnkXW8JTjwqDyZioZd
-Yl1B5VsCZr3TTdJbv4wihtb6gYm7F6uwInWsRqBnfDaEy2Y344SjgTAEGA1JfOOh
-DzQN8UEjSPA/yB3K7v3p05PB6zIQt9NmHOZMhesdZNk9I3oLkkB9VZIVE3dtES4F
-L7iAJTAtgmgj3LsEI0fY1MLr7ffGR/8voyRHrPxvCT5tnxTcpGVok9wWphV0+Udy
-eUxAw5VLsnJotm7syW+zIFHG0VUb7wW3aIYfs9Uc61T1o5kfA2Av9xtHU616pnTh
-WgxCmRadB5NDOLAxBwlup6GlvDf1avwcC0MQtuUD55Qu6pomP2gAguxrK/uMiS/f
-W0WnRDgtEO+ewv/tuxbZAoIBAQDV34plYw3rgitlGlUHuD1pVC8S2Lds2tZ6HLoc
-l4SLVJIQc7jmam37DSjBR/1VW0JpJTMK6VwzbPYSoJpFFHv6sny8NH7y9o3zUq+R
-pHNrvAO343EnYcT+TIGn1VlGi9tXyDPGs+ejsuLSnmq0+wtaRnMcaxa+wUCvrOmK
-0l6xPuYvHrhAcmlYrGr7DXd+bF3SjLL24tuTmFFlW2A3P3Fg/nfBfbaDjEdPr5dV
-vHEsJK9pfMr+tClsZzS4430VFap+WEY58W7Tz7pNJ1/DD0nnyySgPi0I3DVK0p1D
-WLdB0gnKvqUNn0Oo73sDKpSfD7Mwdpwyj+zgvflT6kTvFwvTAoIBAQDNQ8EdtIwq
-X8RMbz7F88ZHHPX4qbtvMEn7kcgnHsdmIe3/ssltWRfB7S5+B3M/8iY4U84Li4ze
-xhjnYK4F9IKsbLUtWCukMjA34W7kaoL+H41NniVTtkIxYBGxmSwMPb9z+WH+5IhD
-Ik3GVTTjGXPPNvF+8LgNlZFONdiypw8JwO95PFVHzSqCghMQIQlPqjgKd9xSKg+p
-DQrs53tkQEoQMBi92zSrh1HQRVH9mD0KWJYAKSdwEtEhoc/kF8QZ4XIhi8/ByLm/
-m/spdoSp9j5Vjy4MRKEYxF1ok9HfwSXsmm2FcJZxeJbiGYruEz4t0kKy0Lmt+8xz
-I3jXOXMvYRiVAoIBAE/Cu0FObK2M8RQWeumTG0wBukCEE/wDrQMDXaE2HJc9pe9+
-yNEdlgCPishySZcgnqbJ2bxTBTCkjSyrOn1Sw13eXMhvp3yC2LOK/bEKLIVcK+LT
-bqqqOqY/8AageVfm5plZL34GL/gLya2UqOTvzu8O4PUTNvtS5QXfLYW5KNlfRMcD
-5OEcCg+o1YjlH9BFJ8RS9pc+SXdE0e5D4qEYBveOTykY8g0jLqEYMg8mZOp6j/R+
-NtJAbEZiQvZE2KwZVWkjEKWhVZymlqsZaQw80mogh3s/VNo+DZ3m6AFqv4VLiJ1U
-9gcbg0cocK7gnWaom0ISqfPtWwEBuE9ESgsEhEMCggEAC29h28jKIjYxllyAL8Dz
-49ROM6spAPm8tWIat2s0ipELVDpelFPpSelvtJ+voPlZfbvVd7kvgN2iV4mASF6l
-xPtNYJhP3hbZrtNFPT5dy9BwK8nKpI47w8ppUe6JkKkD+G8FMZEDslG/6XOnvZsW
-Y43ZCExaxI73iFbhmppJ8S4paSSeT6CzZI/ghf6BKUn/Uz34LS+grbdHS4ldy2j1
-d09moXULyx5/xU2HUsxfYisrOBkS1GCH/AqqrTdRumtf01SZn18SUgVbiaTLoThR
-oqyWUSKlot6VoZTSlVeKSFMWFN//0ZR5O2FW5wp1ZVIYWyPbpECp1CQ+wCa4LwSG
-vQKCAQBcfmjb+R0fVZKXhgig6fjO8LkOFYSYwKnN0ZY53EhPYnGlmD6C9aufihjg
-QKdmqP0yJbaKT+DwZYfCmDk3WOTQ5J7rl8yku+I5dX3oY54J3VgQA1/KABrtPmby
-Byj2iMMkYutn1ffCsptTd06N4PZ+yU/sQVik3/9R0UVQ3eZqI0Hqon7FED8HWXp5
-UJDpahnI/gl8Bl6qtyM17IVh5//VZNMBvZG9cVThlJ3cNfkuuN3CkzWyZM46z/4A
-EN240SdmgfmeGSZ4gGmkSTtV/kC7eChAtW/oB/mRJ1QeORSPB+eThnJHla/plYYd
-jR+QSAa9eEozCngV6LUagC0YYWDZ
------END PRIVATE KEY-----`
-
-// Happy path
-func TestClient_RegisterForNotifications(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-		return
-	}
-
-	privKey, err := rsa.LoadPrivateKeyFromPem([]byte(dummy_key))
-	if err != nil {
-		t.Errorf("Failed to load private key: %+v", err)
-	}
-
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	err = client.GenerateKeys(privKey, "test")
-	if err != nil {
-		t.Errorf("Failed to properly set up keys: %+v", err)
-	}
-
-	token := make([]byte, 32)
-
-	err = client.RegisterForNotifications(token)
-	if err != nil {
-		t.Errorf("Expected happy path, received error: %+v", err)
-	}
-}
-
-// Happy path
-func TestClient_UnregisterForNotifications(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-
-	privKey, err := rsa.LoadPrivateKeyFromPem([]byte(dummy_key))
-	if err != nil {
-		t.Errorf("Failed to load private key: %+v", err)
-		return
-	}
-	err = client.GenerateKeys(privKey, "test")
-	if err != nil {
-		t.Errorf("Failed to properly set up keys: %+v", err)
-		return
-	}
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	err = client.UnregisterForNotifications()
-	if err != nil {
-		t.Errorf("Expected happy path, received error: %+v", err)
-	}
-}
diff --git a/api/permissioning.go b/api/permissioning.go
new file mode 100644
index 0000000000000000000000000000000000000000..b4691291befcb12e7b28b06948980ecc76143c78
--- /dev/null
+++ b/api/permissioning.go
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage"
+)
+
+// Returns an error if registration fails.
+func (c *Client) registerWithPermissioning() error {
+	userData := c.storage.User()
+	//get the users public key
+	transmissionPubKey := userData.GetCryptographicIdentity().GetTransmissionRSA().GetPublic()
+	receptionPubKey := userData.GetCryptographicIdentity().GetReceptionRSA().GetPublic()
+
+	//load the registration code
+	regCode, err := c.storage.GetRegCode()
+	if err != nil {
+		return errors.WithMessage(err, "failed to register with "+
+			"permissioning")
+	}
+
+	//register with permissioning
+	transmissionRegValidationSignature, receptionRegValidationSignature, err := c.permissioning.Register(transmissionPubKey, receptionPubKey, regCode)
+	if err != nil {
+		return errors.WithMessage(err, "failed to register with "+
+			"permissioning")
+	}
+
+	//store the signature
+	userData.SetTransmissionRegistrationValidationSignature(transmissionRegValidationSignature)
+	userData.SetReceptionRegistrationValidationSignature(receptionRegValidationSignature)
+
+	//update the registration status
+	err = c.storage.ForwardRegistrationStatus(storage.PermissioningComplete)
+	if err != nil {
+		return errors.WithMessage(err, "failed to update local state "+
+			"after registration with permissioning")
+	}
+	return nil
+}
diff --git a/api/private.go b/api/private.go
deleted file mode 100644
index bc76f0f36d6f02080c16698ceab5c73a6f6b7caa..0000000000000000000000000000000000000000
--- a/api/private.go
+++ /dev/null
@@ -1,415 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-package api
-
-import (
-	"crypto"
-	"crypto/rand"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/io"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/client/user"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/csprng"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/crypto/xx"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/xx_network/comms/messages"
-)
-
-const PermissioningAddrID = "Permissioning"
-
-// precannedRegister is a helper function for Register
-// It handles the precanned registration case
-func (cl *Client) precannedRegister(registrationCode string) (*user.User, *id.ID, map[id.ID]user.NodeKeys, error) {
-	var successLook bool
-	var UID *id.ID
-	var u *user.User
-	var err error
-
-	nk := make(map[id.ID]user.NodeKeys)
-
-	UID, successLook = user.Users.LookupUser(registrationCode)
-
-	globals.Log.DEBUG.Printf("UID: %+v, success: %+v", UID, successLook)
-
-	if !successLook {
-		return nil, nil, nil, errors.New("precannedRegister: could not register due to invalid HUID")
-	}
-
-	var successGet bool
-	u, successGet = user.Users.GetUser(UID)
-
-	if !successGet {
-		err = errors.New("precannedRegister: could not register due to ID lookup failure")
-		return nil, nil, nil, err
-	}
-
-	nodekeys, successKeys := user.Users.LookupKeys(u.User)
-
-	if !successKeys {
-		err = errors.New("precannedRegister: could not register due to missing user keys")
-		return nil, nil, nil, err
-	}
-
-	for i := 0; i < len(cl.ndf.Gateways); i++ {
-		nk[*cl.topology.GetNodeAtIndex(i)] = *nodekeys
-	}
-	return u, UID, nk, nil
-}
-
-// sendRegistrationMessage is a helper for the Register function
-// It sends a registration message and returns the registration signature
-func (cl *Client) sendRegistrationMessage(registrationCode string,
-	publicKeyRSA *rsa.PublicKey) ([]byte, error) {
-	err := addPermissioningHost(cl.receptionManager, cl.ndf)
-
-	if err != nil {
-		if err == ErrNoPermissioning {
-			return nil, errors.New("Didn't connect to permissioning to send registration message. Check the NDF")
-		}
-		return nil, errors.Wrap(err, "Couldn't connect to permissioning to send registration message")
-	}
-
-	regValidationSignature := make([]byte, 0)
-	// Send registration code and public key to RegistrationServer
-	host, ok := cl.receptionManager.Comms.GetHost(&id.Permissioning)
-	if !ok {
-		return nil, errors.New("Failed to find permissioning host")
-	}
-
-	response, err := cl.receptionManager.Comms.
-		SendRegistrationMessage(host,
-			&pb.UserRegistration{
-				RegistrationCode: registrationCode,
-				ClientRSAPubKey:  string(rsa.CreatePublicKeyPem(publicKeyRSA)),
-			})
-	if err != nil {
-		err = errors.Wrap(err, "sendRegistrationMessage: Unable to contact Registration Server!")
-		return nil, err
-	}
-	if response.Error != "" {
-		return nil, errors.Wrapf(err, "sendRegistrationMessage: error handling message: %s", response.Error)
-	}
-	regValidationSignature = response.ClientSignedByServer.Signature
-	// Disconnect from regServer here since it will not be needed
-	return regValidationSignature, nil
-}
-
-// requestNonce is a helper for the Register function
-// It sends a request nonce message containing the client's keys for signing
-// Returns nonce if successful
-func (cl *Client) requestNonce(salt, regHash []byte,
-	publicKeyDH *cyclic.Int, publicKeyRSA *rsa.PublicKey,
-	privateKeyRSA *rsa.PrivateKey, gwID *id.ID) ([]byte, []byte, error) {
-	dhPub := publicKeyDH.Bytes()
-	sha := crypto.SHA256
-	opts := rsa.NewDefaultOptions()
-	opts.Hash = sha
-	h := sha.New()
-	h.Write(dhPub)
-	data := h.Sum(nil)
-
-	// Sign DH pubkey
-	rng := csprng.NewSystemRNG()
-	signed, err := rsa.Sign(rng, privateKeyRSA, sha, data, opts)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Send signed public key and salt for UserID to Server
-	host, ok := cl.receptionManager.Comms.GetHost(gwID)
-	if !ok {
-		return nil, nil, errors.Errorf("Failed to find host with ID %s", gwID.String())
-	}
-
-	nonceResponse, err := cl.receptionManager.Comms.
-		SendRequestNonceMessage(host,
-			&pb.NonceRequest{
-				Salt:            salt,
-				ClientRSAPubKey: string(rsa.CreatePublicKeyPem(publicKeyRSA)),
-				ClientSignedByServer: &messages.RSASignature{
-					Signature: regHash,
-				},
-				ClientDHPubKey: publicKeyDH.Bytes(),
-				RequestSignature: &messages.RSASignature{
-					Signature: signed,
-				},
-			}) // TODO: modify this to return server DH
-	if err != nil {
-		err := errors.New(fmt.Sprintf(
-			"requestNonce: Unable to request nonce! %s", err))
-		return nil, nil, err
-	}
-	if nonceResponse.Error != "" {
-		err := errors.New(fmt.Sprintf("requestNonce: nonceResponse error: %s", nonceResponse.Error))
-		return nil, nil, err
-	}
-	// Use Client keypair to sign Server nonce
-	return nonceResponse.Nonce, nonceResponse.DHPubKey, nil
-
-}
-
-// confirmNonce is a helper for the Register function
-// It signs a nonce and sends it for confirmation
-// Returns nil if successful, error otherwise
-func (cl *Client) confirmNonce(UID, nonce []byte,
-	privateKeyRSA *rsa.PrivateKey, gwID *id.ID) error {
-	sha := crypto.SHA256
-	opts := rsa.NewDefaultOptions()
-	opts.Hash = sha
-	h := sha.New()
-	h.Write(nonce)
-	data := h.Sum(nil)
-
-	// Hash nonce & sign
-	sig, err := rsa.Sign(rand.Reader, privateKeyRSA, sha, data, opts)
-	if err != nil {
-		globals.Log.ERROR.Printf(
-			"Register: Unable to sign nonce! %s", err)
-		return err
-	}
-
-	// 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,
-		},
-	}
-
-	host, ok := cl.receptionManager.Comms.GetHost(gwID)
-	if !ok {
-		return errors.Errorf("Failed to find host with ID %s", gwID.String())
-	}
-	confirmResponse, err := cl.receptionManager.Comms.
-		SendConfirmNonceMessage(host, msg)
-	if err != nil {
-		err := errors.New(fmt.Sprintf(
-			"confirmNonce: Unable to send signed nonce! %s", err))
-		return err
-	}
-	if confirmResponse.Error != "" {
-		err := errors.New(fmt.Sprintf(
-			"confirmNonce: Error confirming nonce: %s", confirmResponse.Error))
-		return err
-	}
-	return nil
-}
-
-func (cl *Client) registerUserE2E(partnerID *id.ID,
-	partnerPubKey []byte) error {
-
-	// Check that the returned user is valid
-	if partnerKeyStore := cl.session.GetKeyStore().GetSendManager(partnerID); partnerKeyStore != nil {
-		return errors.New(fmt.Sprintf("UDB searched failed for %v because user has "+
-			"been searched for before", partnerID))
-	}
-
-	if cl.session.GetCurrentUser().User.Cmp(partnerID) {
-		return errors.New("cannot search for yourself on UDB")
-	}
-
-	// Get needed variables from session
-	grp := cl.session.GetE2EGroup()
-	userID := cl.session.GetCurrentUser().User
-
-	// Create user private key and partner public key
-	// in the group
-	privKeyCyclic := cl.session.GetE2EDHPrivateKey()
-	partnerPubKeyCyclic := grp.NewIntFromBytes(partnerPubKey)
-
-	// Generate baseKey
-	baseKey, _ := diffieHellman.CreateDHSessionKey(
-		partnerPubKeyCyclic,
-		privKeyCyclic,
-		grp)
-
-	// Generate key TTL and number of keys
-	params := cl.session.GetKeyStore().GetKeyParams()
-	keysTTL, numKeys := e2e.GenerateKeyTTL(baseKey.GetLargeInt(),
-		params.MinKeys, params.MaxKeys, params.TTLParams)
-
-	// Create Send KeyManager
-	km := keyStore.NewManager(baseKey, privKeyCyclic,
-		partnerPubKeyCyclic, partnerID, true,
-		numKeys, keysTTL, params.NumRekeys)
-
-	// Generate Send Keys
-	km.GenerateKeys(grp, userID)
-	cl.session.GetKeyStore().AddSendManager(km)
-
-	// Create Receive KeyManager
-	km = keyStore.NewManager(baseKey, privKeyCyclic,
-		partnerPubKeyCyclic, partnerID, false,
-		numKeys, keysTTL, params.NumRekeys)
-
-	// Generate Receive Keys
-	newE2eKeys := km.GenerateKeys(grp, userID)
-	cl.session.GetKeyStore().AddRecvManager(km)
-	cl.session.GetKeyStore().AddReceiveKeysByFingerprint(newE2eKeys)
-
-	// Create RekeyKeys and add to RekeyManager
-	rkm := cl.session.GetRekeyManager()
-
-	keys := &keyStore.RekeyKeys{
-		CurrPrivKey: privKeyCyclic,
-		CurrPubKey:  partnerPubKeyCyclic,
-	}
-
-	rkm.AddKeys(partnerID, keys)
-
-	return nil
-}
-
-//GenerateKeys generates the keys and user information used in the session object
-func (cl *Client) GenerateKeys(rsaPrivKey *rsa.PrivateKey,
-	password string) error {
-
-	cl.opStatus(globals.REG_KEYGEN)
-
-	//Generate keys and other necessary session information
-	cmixGrp, e2eGrp := generateGroups(cl.ndf)
-	privKey, pubKey, err := generateRsaKeys(rsaPrivKey)
-	if err != nil {
-		return err
-	}
-	cmixPrivKey, cmixPubKey, err := generateCmixKeys(cmixGrp)
-	if err != nil {
-		return err
-	}
-	e2ePrivKey, e2ePubKey, err := generateE2eKeys(cmixGrp, e2eGrp)
-	if err != nil {
-		return err
-	}
-
-	//Set callback status to user generation & generate user
-	cl.opStatus(globals.REG_UID_GEN)
-	salt, _, usr, err := generateUserInformation(pubKey)
-	if err != nil {
-		return err
-	}
-
-	cl.session = user.NewSession(cl.storage, usr, pubKey, privKey, cmixPubKey,
-		cmixPrivKey, e2ePubKey, e2ePrivKey, salt, cmixGrp, e2eGrp, password)
-
-	newRm, err := io.NewReceptionManager(cl.rekeyChan, cl.session.GetCurrentUser().User,
-		rsa.CreatePrivateKeyPem(privKey), rsa.CreatePublicKeyPem(pubKey), salt)
-	if err != nil {
-		return errors.Wrap(err, "Failed to create new reception manager")
-	}
-	if cl.receptionManager != nil {
-		// Use the old comms manager if it exists
-		newRm.Comms.Manager = cl.receptionManager.Comms.Manager
-	}
-	cl.receptionManager = newRm
-	//store the session
-	return cl.session.StoreSession()
-}
-
-//GenerateGroups serves as a helper function for RegisterUser.
-// It generates the cmix and e2e groups from the ndf
-func generateGroups(clientNdf *ndf.NetworkDefinition) (cmixGrp, e2eGrp *cyclic.Group) {
-	largeIntBits := 16
-
-	//Generate the cmix group
-	cmixGrp = cyclic.NewGroup(
-		large.NewIntFromString(clientNdf.CMIX.Prime, largeIntBits),
-		large.NewIntFromString(clientNdf.CMIX.Generator, largeIntBits))
-	//Generate the e2e group
-	e2eGrp = cyclic.NewGroup(
-		large.NewIntFromString(clientNdf.E2E.Prime, largeIntBits),
-		large.NewIntFromString(clientNdf.E2E.Generator, largeIntBits))
-
-	return cmixGrp, e2eGrp
-}
-
-//GenerateRsaKeys serves as a helper function for RegisterUser.
-// It generates a private key if the one passed in is nil and a public key from said private key
-func generateRsaKeys(rsaPrivKey *rsa.PrivateKey) (*rsa.PrivateKey, *rsa.PublicKey, error) {
-	var err error
-	//Generate client RSA keys
-	if rsaPrivKey == nil {
-		rsaPrivKey, err = rsa.GenerateKey(csprng.NewSystemRNG(), rsa.DefaultRSABitLen)
-		if err != nil {
-			return nil, nil, errors.Errorf("Could not generate RSA keys: %+v", err)
-		}
-	}
-	//Pull the public key from the private key
-	publicKeyRSA := rsaPrivKey.GetPublic()
-
-	return rsaPrivKey, publicKeyRSA, nil
-}
-
-//GenerateCmixKeys serves as a helper function for RegisterUser.
-// It generates private and public keys within the cmix group
-func generateCmixKeys(cmixGrp *cyclic.Group) (cmixPrivateKeyDH, cmixPublicKeyDH *cyclic.Int, err error) {
-	if cmixGrp == nil {
-		return nil, nil, errors.New("Cannot have a nil CMix group")
-	}
-
-	//Generate the private key
-	cmixPrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG())
-	if err != nil {
-		return nil, nil,
-			errors.Errorf("Could not generate CMix DH keys: %+v", err)
-	}
-	//Convert the keys into cyclic Ints and return
-	cmixPrivateKeyDH = cmixGrp.NewIntFromBytes(cmixPrivKeyDHByte)
-	cmixPublicKeyDH = cmixGrp.ExpG(cmixPrivateKeyDH, cmixGrp.NewMaxInt())
-
-	return cmixPrivateKeyDH, cmixPublicKeyDH, nil
-}
-
-//GenerateE2eKeys serves as a helper function for RegisterUser.
-// It generates public and private keys used in e2e communications
-func generateE2eKeys(cmixGrp, e2eGrp *cyclic.Group) (e2ePrivateKey, e2ePublicKey *cyclic.Int, err error) {
-	if cmixGrp == nil || e2eGrp == nil {
-		return nil, nil, errors.New("Cannot have a nil group")
-	}
-	//Generate the private key in group
-	e2ePrivKeyDHByte, err := csprng.GenerateInGroup(cmixGrp.GetPBytes(), 256, csprng.NewSystemRNG())
-	if err != nil {
-		return nil, nil,
-			errors.Errorf("Could not generate E2E DH keys: %s", err)
-	}
-	//Convert the keys into cyclic Ints and return
-	e2ePrivateKeyDH := e2eGrp.NewIntFromBytes(e2ePrivKeyDHByte)
-	e2ePublicKeyDH := e2eGrp.ExpG(e2ePrivateKeyDH, e2eGrp.NewMaxInt())
-
-	return e2ePrivateKeyDH, e2ePublicKeyDH, nil
-}
-
-//generateUserInformation serves as a helper function for RegisterUser.
-// It generates a salt s.t. it can create a user and their ID
-func generateUserInformation(publicKeyRSA *rsa.PublicKey) ([]byte, *id.ID, *user.User, error) {
-	//Generate salt for UserID
-	salt := make([]byte, SaltSize)
-	_, err := csprng.NewSystemRNG().Read(salt)
-	if err != nil {
-		return nil, nil, nil,
-			errors.Errorf("Register: Unable to generate salt! %s", err)
-	}
-
-	//Generate UserID by hashing salt and public key
-	userId, err := xx.NewID(publicKeyRSA, salt, id.User)
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
-	usr := user.Users.NewUser(userId, "")
-	user.Users.UpsertUser(usr)
-
-	return salt, userId, usr, nil
-}
diff --git a/api/private_test.go b/api/private_test.go
deleted file mode 100644
index 514a19a500da8b0c04cfc5d16b6e0a5065590709..0000000000000000000000000000000000000000
--- a/api/private_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                    /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-package api
-
-import (
-	"bytes"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/csprng"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"reflect"
-	"testing"
-)
-
-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
-}
-
-//Happy path: test it generates key when passed nil
-func TestGenerateKeys_NilPrivateKey(t *testing.T) {
-	privKey, pubKey, err := generateRsaKeys(nil)
-	if privKey == nil {
-		t.Errorf("Failed to generate private key when generateRsaKeys() is passed nil")
-	}
-	if pubKey == nil {
-		t.Errorf("Failed to pull public key from private key")
-	}
-
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-}
-
-//Tests it generates keys based on an existing privateKey
-func TestGenerateKeys(t *testing.T) {
-	notRand := &CountingReader{count: uint8(0)}
-
-	privKey, err := rsa.GenerateKey(notRand, 1024)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-	expected_N := privKey.N.Bytes()
-	privKey, pubKey, err := generateRsaKeys(privKey)
-	if err != nil {
-		t.Errorf("Failecd to generate keys: %+v", err)
-	}
-	if bytes.Compare(expected_N, privKey.N.Bytes()) != 0 {
-		t.Errorf("Private key overwritten in generateKeys() despite privateKey not being nil")
-	}
-
-	if bytes.Compare(pubKey.GetN().Bytes(), expected_N) != 0 {
-		t.Logf("N: %v", pubKey.GetN().Bytes())
-		t.Errorf("Bad N-val, expected: %v", expected_N)
-	}
-}
-
-//Tests GenerateCmixKeys cases
-func TestGenerateCmixKeys(t *testing.T) {
-	//Test generateCmixKeys
-	cmixGrp, _ := generateGroups(def)
-	cmixPrivKey, _, err := generateCmixKeys(cmixGrp)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-
-	if !csprng.InGroup(cmixPrivKey.Bytes(), cmixGrp.GetPBytes()) {
-		t.Errorf("Generated cmix private key is not in the cmix group!")
-	}
-	//Error case
-	_, _, err = generateCmixKeys(nil)
-	if err == nil {
-		t.Errorf("Expected error case, should not pass nil into GenerateCmixKeys()")
-	}
-
-}
-
-//Happy/error path: Tests generation of e2e keys
-func TestGenerateE2eKeys(t *testing.T) {
-	//Test generateCmixKeys
-	cmixGrp, e2eGrp := generateGroups(def)
-
-	//Test e2e key generation
-	e2ePrivKey, _, err := generateE2eKeys(cmixGrp, e2eGrp)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-
-	if !csprng.InGroup(e2ePrivKey.Bytes(), cmixGrp.GetPBytes()) {
-		t.Errorf("Generated cmix private key is not in the cmix group!")
-	}
-
-	//Error case
-	_, _, err = generateE2eKeys(nil, nil)
-	if err == nil {
-		t.Errorf("Expected error case, should not pass nil into GenerateE2eKeys()")
-	}
-
-}
-
-//Happy path: tests that it generates a user and puts in the registry
-func TestGenerateUserInformation_EmptyNick(t *testing.T) {
-	grp, _ := generateGroups(def)
-	user.InitUserRegistry(grp)
-	_, pubkey, _ := generateRsaKeys(nil)
-	_, uid, usr, err := generateUserInformation(pubkey)
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-	retrievedUser, ok := user.Users.GetUser(uid)
-	if !ok {
-		t.Errorf("UserId not inserted into registry")
-	}
-
-	if !reflect.DeepEqual(usr, retrievedUser) {
-		t.Errorf("Did not retrieve correct user. \n\treceived: %v\n\texpected: %v", retrievedUser, usr)
-	}
-
-	if usr.Username != "" {
-		t.Errorf("User's username should be initally")
-	}
-
-}
-
-//Happy path: test GenerateUser with a nickname and puts in registry
-func TestGenerateUserInformation(t *testing.T) {
-	grp, _ := generateGroups(def)
-	user.InitUserRegistry(grp)
-	nickName := "test"
-	_, pubkey, _ := generateRsaKeys(nil)
-	_, uid, usr, err := generateUserInformation(pubkey)
-	usr.Username = nickName
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-
-	retrievedUser, ok := user.Users.GetUser(uid)
-	if !ok {
-		t.Errorf("UserId not inserted into registry")
-	}
-	if !reflect.DeepEqual(usr, retrievedUser) {
-		t.Errorf("Did not retrieve correct user. \n\treceived: %v\n\texpected: %v", retrievedUser, usr)
-	}
-
-	if usr.Username != nickName {
-		t.Errorf("User's nickname was overwritten\nreceived: %v\n\texpected: %v", usr.Username, nickName)
-	}
-
-}
diff --git a/api/processies.go b/api/processies.go
new file mode 100644
index 0000000000000000000000000000000000000000..ad4a714992660faf386ea72e23a6e1de1d90b9f7
--- /dev/null
+++ b/api/processies.go
@@ -0,0 +1,48 @@
+package api
+
+import (
+	"gitlab.com/elixxir/client/stoppable"
+	"sync"
+)
+
+// a service process starts itself in a new thread, returning from the
+// originator a stopable to control it
+type ServiceProcess func() stoppable.Stoppable
+
+type serviceProcessiesList struct {
+	serviceProcessies []ServiceProcess
+	multiStopable     *stoppable.Multi
+	mux               sync.Mutex
+}
+
+// newServiceProcessiesList creates a new processies list which will add its
+// processies to the passed mux
+func newServiceProcessiesList(m *stoppable.Multi) *serviceProcessiesList {
+	return &serviceProcessiesList{
+		serviceProcessies: make([]ServiceProcess, 0),
+		multiStopable:     m,
+	}
+}
+
+// Add adds the service process to the list and adds it to the multi-stopable
+func (spl serviceProcessiesList) Add(sp ServiceProcess) {
+	spl.mux.Lock()
+	defer spl.mux.Unlock()
+
+	spl.serviceProcessies = append(spl.serviceProcessies, sp)
+	// starts the process and adds it to the stopable
+	// there can be a race condition between the execution of the process and
+	// the stopable.
+	spl.multiStopable.Add(sp())
+}
+
+// Runs all processies, to be used after a stop. Must use a new stopable
+func (spl serviceProcessiesList) run(m *stoppable.Multi) {
+	spl.mux.Lock()
+	defer spl.mux.Unlock()
+
+	spl.multiStopable = m
+	for _, sp := range spl.serviceProcessies {
+		spl.multiStopable.Add(sp())
+	}
+}
diff --git a/api/register.go b/api/register.go
deleted file mode 100644
index 921738be22150f4fa06a187a0f375a622ce2aaa7..0000000000000000000000000000000000000000
--- a/api/register.go
+++ /dev/null
@@ -1,360 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package api
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/bots"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/crypto/registration"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/crypto/tls"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"sync"
-	"time"
-)
-
-const SaltSize = 32
-
-//RegisterWithPermissioning registers the user and returns the User ID.
-// Returns an error if registration fails.
-func (cl *Client) RegisterWithPermissioning(preCan bool, registrationCode string) (*id.ID, error) {
-	//Check the regState is in proper state for registration
-	if cl.session.GetRegState() != user.KeyGenComplete {
-		return nil, errors.Errorf("Attempting to register before key generation!")
-	}
-	usr := cl.session.GetCurrentUser()
-	UID := usr.User
-	var err error
-
-	//Initialized response from Registration Server
-	regValidationSignature := make([]byte, 0)
-
-	//Handle registration
-	if preCan {
-		// Either perform a precanned registration for a precanned user
-		cl.opStatus(globals.REG_PRECAN)
-		globals.Log.INFO.Printf("Registering precanned user...")
-		var nodeKeyMap map[id.ID]user.NodeKeys
-		usr, UID, nodeKeyMap, err = cl.precannedRegister(registrationCode)
-		if err != nil {
-			globals.Log.ERROR.Printf("Unable to complete precanned registration: %+v", err)
-			return &id.ZeroUser, err
-		}
-
-		//overwrite the user object
-		cl.session.(*user.SessionObj).CurrentUser = usr
-
-		//store the node keys
-		for n, k := range nodeKeyMap {
-			cl.session.PushNodeKey(&n, k)
-		}
-		//update the state
-		err := cl.session.SetRegState(user.PermissioningComplete)
-		if err != nil {
-			return nil, errors.Wrap(err, "Could not do precanned registration")
-		}
-
-	} else {
-		// Or register with the permissioning server and generate user information
-		regValidationSignature, err = cl.registerWithPermissioning(registrationCode, cl.session.GetRSAPublicKey())
-		if err != nil {
-			globals.Log.INFO.Printf(err.Error())
-			return &id.ZeroUser, err
-		}
-		//update the session with the registration
-		err = cl.session.RegisterPermissioningSignature(regValidationSignature)
-
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	//Set the registration secure state
-	cl.opStatus(globals.REG_SECURE_STORE)
-
-	//store the updated session
-	err = cl.session.StoreSession()
-
-	if err != nil {
-		return nil, err
-	}
-
-	return UID, nil
-}
-
-//RegisterWithUDB uses the account's email to register with the UDB for
-// User discovery.  Must be called after Register and InitNetwork.
-// It will fail if the user has already registered with UDB
-func (cl *Client) RegisterWithUDB(username string, timeout time.Duration) error {
-
-	regState := cl.GetSession().GetRegState()
-
-	if regState != user.PermissioningComplete {
-		return errors.New("Cannot register with UDB when registration " +
-			"state is not PermissioningComplete")
-	}
-
-	var err error
-
-	if username != "" {
-		err := cl.session.ChangeUsername(username)
-		if err != nil {
-			return err
-		}
-
-		globals.Log.INFO.Printf("Registering user as %s with UDB", username)
-
-		valueType := "EMAIL"
-
-		publicKeyBytes := cl.session.GetE2EDHPublicKey().Bytes()
-		err = bots.Register(valueType, username, publicKeyBytes, cl.opStatus, timeout)
-		if err != nil {
-			return errors.Errorf("Could not register with UDB: %s", err)
-		}
-		globals.Log.INFO.Printf("Registered with UDB!")
-	} else {
-		globals.Log.INFO.Printf("Not registering with UDB because no " +
-			"email found")
-	}
-
-	//set the registration state
-	err = cl.session.SetRegState(user.UDBComplete)
-
-	if err != nil {
-		return errors.Wrap(err, "UDB Registration Failed")
-	}
-
-	cl.opStatus(globals.REG_SECURE_STORE)
-
-	errStore := cl.session.StoreSession()
-
-	// FIXME If we have an error here, the session that gets created
-	// doesn't get immolated. Immolation should happen in a deferred
-	//  call instead.
-	if errStore != nil {
-		err = errors.New(fmt.Sprintf(
-			"UDB Register: could not register due to failed session save"+
-				": %s", errStore.Error()))
-		return err
-	}
-
-	return nil
-}
-
-//RegisterWithNodes registers the client with all the nodes within the ndf
-func (cl *Client) RegisterWithNodes() error {
-	cl.opStatus(globals.REG_NODE)
-	session := cl.GetSession()
-	//Load Cmix keys & group
-	cmixDHPrivKey := session.GetCMIXDHPrivateKey()
-	cmixDHPubKey := session.GetCMIXDHPublicKey()
-	cmixGrp := session.GetCmixGroup()
-
-	//Load the rsa keys
-	rsaPubKey := session.GetRSAPublicKey()
-	rsaPrivKey := session.GetRSAPrivateKey()
-
-	//Load the user ID
-	UID := session.GetCurrentUser().User
-	usr := session.GetCurrentUser()
-	//Load the registration signature
-	regSignature := session.GetRegistrationValidationSignature()
-
-	// Storage of the registration signature was broken in previous releases.
-	// get the signature again from permissioning if it is absent
-	var regPubKey *rsa.PublicKey
-	if cl.ndf.Registration.TlsCertificate != "" {
-		var err error
-		regPubKey, err = extractPublicKeyFromCert(cl.ndf)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Storage of the registration signature was broken in previous releases.
-	// get the signature again from permissioning if it is absent
-	if !usr.Precan && !rsa.IsValidSignature(regPubKey, regSignature) {
-		// Or register with the permissioning server and generate user information
-		regSignature, err := cl.registerWithPermissioning("", cl.session.GetRSAPublicKey())
-		if err != nil {
-			globals.Log.INFO.Printf(err.Error())
-			return err
-		}
-		//update the session with the registration
-		//HACK HACK HACK
-		sesObj := cl.session.(*user.SessionObj)
-		sesObj.RegValidationSignature = regSignature
-		err = sesObj.StoreSession()
-
-		if err != nil {
-			return err
-		}
-	}
-
-	//make the wait group to wait for all node registrations to complete
-	var wg sync.WaitGroup
-	errChan := make(chan error, len(cl.ndf.Gateways))
-
-	//Get the registered node keys
-	registeredNodes := session.GetNodes()
-
-	salt := session.GetSalt()
-
-	// This variable keeps track of whether there were new registrations
-	// required, thus requiring the state file to be saved again
-	newRegistrations := false
-
-	for i := range cl.ndf.Gateways {
-		localI := i
-		nodeID, err := id.Unmarshal(cl.ndf.Nodes[i].ID)
-		if err != nil {
-			return err
-		}
-		//Register with node if the node has not been registered with already
-		if _, ok := registeredNodes[*nodeID]; !ok {
-			wg.Add(1)
-			newRegistrations = true
-			go func() {
-				cl.registerWithNode(localI, salt, regSignature, UID, rsaPubKey, rsaPrivKey,
-					cmixDHPubKey, cmixDHPrivKey, cmixGrp, errChan)
-				wg.Done()
-			}()
-		}
-	}
-	wg.Wait()
-	//See if the registration returned errors at all
-	var errs error
-	for len(errChan) > 0 {
-		err := <-errChan
-		if errs != nil {
-			errs = errors.Wrap(errs, err.Error())
-		} else {
-			errs = err
-		}
-
-	}
-	//If an error every occurred, return with error
-	if errs != nil {
-		cl.opStatus(globals.REG_FAIL)
-		return errs
-	}
-
-	// Store the user session if there were changes during node registration
-	if newRegistrations {
-		cl.opStatus(globals.REG_SECURE_STORE)
-		errStore := session.StoreSession()
-		if errStore != nil {
-			err := errors.New(fmt.Sprintf(
-				"Register: could not register due to failed session save"+
-					": %s", errStore.Error()))
-			return err
-		}
-	}
-
-	return nil
-}
-
-//registerWithNode serves as a helper for RegisterWithNodes
-// It registers a user with a specific in the client's ndf.
-func (cl *Client) registerWithNode(index int, salt, registrationValidationSignature []byte, UID *id.ID,
-	publicKeyRSA *rsa.PublicKey, privateKeyRSA *rsa.PrivateKey,
-	cmixPublicKeyDH, cmixPrivateKeyDH *cyclic.Int,
-	cmixGrp *cyclic.Group, errorChan chan error) {
-
-	gatewayID, err := id.Unmarshal(cl.ndf.Gateways[index].ID)
-	if err != nil {
-		errorChan <- err
-		return
-	}
-
-	// Initialise blake2b hash for transmission keys and sha256 for reception
-	// keys
-	transmissionHash, _ := hash.NewCMixHash()
-	receptionHash := sha256.New()
-
-	// Request nonce message from gateway
-	globals.Log.INFO.Printf("Register: Requesting nonce from gateway %v/%v",
-		index+1, len(cl.ndf.Gateways))
-	nonce, dhPub, err := cl.requestNonce(salt, registrationValidationSignature, cmixPublicKeyDH,
-		publicKeyRSA, privateKeyRSA, gatewayID)
-
-	if err != nil {
-		errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err)
-		errorChan <- errors.New(errMsg)
-		return
-	}
-
-	// Load server DH pubkey
-	serverPubDH := cmixGrp.NewIntFromBytes(dhPub)
-
-	// Confirm received nonce
-	globals.Log.INFO.Println("Register: Confirming received nonce")
-	err = cl.confirmNonce(UID.Bytes(), nonce, privateKeyRSA, gatewayID)
-	if err != nil {
-		errMsg := fmt.Sprintf("Register: Unable to confirm nonce: %v", err)
-		errorChan <- errors.New(errMsg)
-		return
-	}
-	nodeID := cl.topology.GetNodeAtIndex(index)
-	key := user.NodeKeys{
-		TransmissionKey: registration.GenerateBaseKey(cmixGrp,
-			serverPubDH, cmixPrivateKeyDH, transmissionHash),
-		ReceptionKey: registration.GenerateBaseKey(cmixGrp, serverPubDH,
-			cmixPrivateKeyDH, receptionHash),
-	}
-	cl.session.PushNodeKey(nodeID, key)
-}
-
-//registerWithPermissioning serves as a helper function for RegisterWithPermissioning.
-// It sends the registration message containing the regCode to permissioning
-func (cl *Client) registerWithPermissioning(registrationCode string,
-	publicKeyRSA *rsa.PublicKey) (regValidSig []byte, err error) {
-	//Set the opStatus and log registration
-	globals.Log.INFO.Printf("Registering dynamic user...")
-
-	// If Registration Server is not specified return an error
-	if cl.ndf.Registration.Address == "" {
-		return nil, errors.New("No registration attempted, " +
-			"registration server not known")
-	}
-
-	// attempt to register with registration
-	globals.Log.INFO.Println("Register: Registering with registration server")
-	cl.opStatus(globals.REG_PERM)
-	regValidSig, err = cl.sendRegistrationMessage(registrationCode, publicKeyRSA)
-	if err != nil {
-		return nil, errors.Errorf("Register: Unable to send registration message: %+v", err)
-	}
-
-	globals.Log.INFO.Println("Register: successfully registered")
-
-	return regValidSig, nil
-}
-
-// extractPublicKeyFromCert is a utility function which pulls out the public key from a certificate
-func extractPublicKeyFromCert(definition *ndf.NetworkDefinition) (*rsa.PublicKey, error) {
-	// Load certificate object
-	cert, err := tls.LoadCertificate(definition.Registration.TlsCertificate)
-	if err != nil {
-		return nil, errors.Errorf("Failed to parse certificate: %+v", err)
-	}
-	//Extract public key from cert
-	regPubKey, err := tls.ExtractPublicKey(cert)
-	if err != nil {
-		return nil, errors.Errorf("Failed to pull key from cert: %+v", err)
-	}
-
-	return regPubKey, nil
-
-}
diff --git a/api/register_test.go b/api/register_test.go
deleted file mode 100644
index 5c0ab24a56d6029c89c04e737dfc2e39dc4641eb..0000000000000000000000000000000000000000
--- a/api/register_test.go
+++ /dev/null
@@ -1,165 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                    /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-package api
-
-import (
-	"crypto/sha256"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"testing"
-)
-
-//Test that a registered session may be stored & recovered
-func TestRegistrationGob(t *testing.T) {
-	// Get a Client
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Error(err)
-	}
-	err = testClient.GenerateKeys(nil, "1234")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// populate a gob in the store
-	_, err = testClient.RegisterWithPermissioning(true, "WTROXJ33")
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.session.StoreSession()
-	if err != nil {
-		t.Error(err)
-	}
-
-	// get the gob out of there again
-	Session, err := user.LoadSession(testClient.storage,
-		"1234")
-	if err != nil {
-		t.Error(err)
-	}
-
-	VerifyRegisterGobUser(Session, t)
-	VerifyRegisterGobKeys(Session, testClient.topology, t)
-
-	disconnectServers()
-}
-
-//Happy path for a non precen user
-func TestClient_Register(t *testing.T) {
-	//Make mock client
-	testClient, err := NewClient(&globals.RamStorage{}, "", "", def)
-
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.GenerateKeys(nil, "password")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// populate a gob in the store
-	_, err = testClient.RegisterWithPermissioning(true, "WTROXJ33")
-	if err != nil {
-		t.Error(err)
-	}
-
-	err = testClient.RegisterWithNodes()
-	if err != nil {
-		t.Error(err)
-	}
-
-	// get the gob out of there again
-	Session, err := user.LoadSession(testClient.storage,
-		"password")
-	if err != nil {
-		t.Error(err)
-	}
-
-	VerifyRegisterGobUser(Session, t)
-
-	VerifyRegisterGobKeys(Session, testClient.topology, t)
-	disconnectServers()
-}
-
-//Verify the user from the session make in the registration above matches expected user
-func VerifyRegisterGobUser(session user.Session, t *testing.T) {
-
-	expectedUser := id.NewIdFromUInt(5, id.User, t)
-
-	if !session.GetCurrentUser().User.Cmp(expectedUser) {
-		t.Errorf("Incorrect User ID; \n   expected: %q \n   recieved: %q",
-			expectedUser, session.GetCurrentUser().User)
-	}
-}
-
-//Verify that the keys from the session in the registration above match the expected keys
-func VerifyRegisterGobKeys(session user.Session, topology *connect.Circuit, t *testing.T) {
-	cmixGrp, _ := getGroups()
-	h := sha256.New()
-	h.Write([]byte(string(40005)))
-	expectedTransmissionBaseKey := cmixGrp.NewIntFromBytes(h.Sum(nil))
-
-	if session.GetNodeKeys(topology)[0].TransmissionKey.Cmp(
-		expectedTransmissionBaseKey) != 0 {
-		t.Errorf("Transmission base key was %v, expected %v",
-			session.GetNodeKeys(topology)[0].TransmissionKey.Text(16),
-			expectedTransmissionBaseKey.Text(16))
-	}
-
-}
-
-// Verify that a valid precanned user can register
-func TestRegister_ValidRegParams___(t *testing.T) {
-	// Initialize client with dummy storage
-	storage := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&storage, "hello", "", def)
-	if err != nil {
-		t.Errorf("Failed to initialize dummy client: %s", err.Error())
-	}
-
-	// InitNetwork to gateways and reg server
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Client failed of connect: %+v", err)
-	}
-
-	err = client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("%+v", err)
-	}
-
-	// Register precanned user with all gateways
-	regRes, err := client.RegisterWithPermissioning(false, ValidRegCode)
-	if err != nil {
-		t.Errorf("Registration failed: %s", err.Error())
-	}
-
-	if *regRes == *&id.ZeroUser {
-		t.Errorf("Invalid registration number received: %+v", *regRes)
-	}
-	err = client.RegisterWithNodes()
-	if err != nil {
-		t.Error(err)
-	}
-
-	//Disconnect and shutdown servers
-	disconnectServers()
-}
diff --git a/api/results.go b/api/results.go
new file mode 100644
index 0000000000000000000000000000000000000000..885458fe2974d22b16592ba9e3f3ce2f5d4154a2
--- /dev/null
+++ b/api/results.go
@@ -0,0 +1,209 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package api
+
+import (
+	"fmt"
+	"time"
+
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/network/gateway"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// Enum of possible round results to pass back
+type RoundResult uint
+
+const (
+	TimeOut RoundResult = iota
+	Failed
+	Succeeded
+)
+
+func (rr RoundResult) String() string {
+	switch rr {
+	case TimeOut:
+		return "TimeOut"
+	case Failed:
+		return "Failed"
+	case Succeeded:
+		return "Succeeded"
+	default:
+		return fmt.Sprintf("UNKNOWN RESULT: %d", rr)
+	}
+}
+
+// Callback interface which reports the requested rounds.
+// Designed such that the caller may decide how much detail they need.
+// allRoundsSucceeded:
+//   Returns false if any rounds in the round map were unsuccessful.
+//   Returns true if ALL rounds were successful
+// timedOut:
+//    Returns true if any of the rounds timed out while being monitored
+//	  Returns false if all rounds statuses were returned
+// rounds contains a mapping of all previously requested rounds to
+//   their respective round results
+type RoundEventCallback func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult)
+
+// Comm interface for RequestHistoricalRounds.
+// Constructed for testability with getRoundResults
+type historicalRoundsComm interface {
+	RequestHistoricalRounds(host *connect.Host,
+		message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error)
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+}
+
+// Adjudicates on the rounds requested. Checks if they are
+// older rounds or in progress rounds.
+func (c *Client) GetRoundResults(roundList []id.Round, timeout time.Duration,
+	roundCallback RoundEventCallback) error {
+
+	sendResults := make(chan ds.EventReturn, len(roundList))
+
+	return c.getRoundResults(roundList, timeout, roundCallback,
+		sendResults, c.comms)
+}
+
+// Helper function which does all the logic for GetRoundResults
+func (c *Client) getRoundResults(roundList []id.Round, timeout time.Duration,
+	roundCallback RoundEventCallback, sendResults chan ds.EventReturn,
+	commsInterface historicalRoundsComm) error {
+
+	networkInstance := c.network.GetInstance()
+
+	// Generate a message to track all older rounds
+	historicalRequest := &pb.HistoricalRounds{
+		Rounds: []uint64{},
+	}
+
+	// Generate all tracking structures for rounds
+	roundEvents := c.GetRoundEvents()
+	roundsResults := make(map[id.Round]RoundResult)
+	allRoundsSucceeded := true
+	numResults := 0
+
+	// Parse and adjudicate every round
+	for _, rnd := range roundList {
+		// Every round is timed out by default, until proven to have finished
+		roundsResults[rnd] = TimeOut
+		roundInfo, err := networkInstance.GetRound(rnd)
+		// If we have the round in the buffer
+		if err == nil {
+			// Check if the round is done (completed or failed) or in progress
+			if states.Round(roundInfo.State) == states.COMPLETED {
+				roundsResults[rnd] = Succeeded
+			} else if states.Round(roundInfo.State) == states.FAILED {
+				roundsResults[rnd] = Failed
+				allRoundsSucceeded = false
+			} else {
+				// If in progress, add a channel monitoring its status
+				roundEvents.AddRoundEventChan(rnd, sendResults,
+					timeout-time.Millisecond, states.COMPLETED, states.FAILED)
+				numResults++
+			}
+		} else {
+			jww.DEBUG.Printf("Failed to ger round [%d] in buffer: %v", rnd, err)
+			// Update oldest round (buffer may have updated externally)
+			oldestRound := networkInstance.GetOldestRoundID()
+			if rnd < oldestRound {
+				// If round is older that oldest round in our buffer
+				// Add it to the historical round request (performed later)
+				historicalRequest.Rounds = append(historicalRequest.Rounds, uint64(rnd))
+				numResults++
+			} else {
+				// Otherwise, monitor it's progress
+				roundEvents.AddRoundEventChan(rnd, sendResults,
+					timeout-time.Millisecond, states.COMPLETED, states.FAILED)
+				numResults++
+			}
+		}
+	}
+
+	// Find out what happened to old (historical) rounds if any are needed
+	if len(historicalRequest.Rounds) > 0 {
+		go c.getHistoricalRounds(historicalRequest, networkInstance, sendResults, commsInterface)
+	}
+
+	// Determine the results of all rounds requested
+	go func() {
+		// Create the results timer
+		timer := time.NewTimer(timeout)
+		for {
+
+			// If we know about all rounds, return
+			if numResults == 0 {
+				roundCallback(allRoundsSucceeded, false, roundsResults)
+				return
+			}
+
+			// Wait for info about rounds or the timeout to occur
+			select {
+			case <-timer.C:
+				roundCallback(false, true, roundsResults)
+				return
+			case roundReport := <-sendResults:
+				numResults--
+				// Skip if the round is nil (unknown from historical rounds)
+				// they default to timed out, so correct behavior is preserved
+				if roundReport.RoundInfo == nil || roundReport.TimedOut {
+					allRoundsSucceeded = false
+				} else {
+					// If available, denote the result
+					roundId := id.Round(roundReport.RoundInfo.ID)
+					if states.Round(roundReport.RoundInfo.State) == states.COMPLETED {
+						roundsResults[roundId] = Succeeded
+					} else {
+						roundsResults[roundId] = Failed
+						allRoundsSucceeded = false
+
+					}
+				}
+			}
+		}
+	}()
+
+	return nil
+}
+
+// Helper function which asynchronously pings a random gateway until
+// it gets information on it's requested historical rounds
+func (c *Client) getHistoricalRounds(msg *pb.HistoricalRounds,
+	instance *network.Instance, sendResults chan ds.EventReturn,
+	comms historicalRoundsComm) {
+
+	var resp *pb.HistoricalRoundsResponse
+
+	for {
+		// Find a gateway to request about the roundRequests
+		gwHost, err := gateway.Get(instance.GetPartialNdf().Get(), comms, c.rng.GetStream())
+		if err != nil {
+			jww.FATAL.Panicf("Failed to track network, NDF has corrupt "+
+				"data: %s", err)
+		}
+
+		// If an error, retry with (potentially) a different gw host.
+		// If no error from received gateway request, exit loop
+		// and process rounds
+		resp, err = comms.RequestHistoricalRounds(gwHost, msg)
+		if err == nil {
+			break
+		}
+	}
+
+	// Process historical rounds, sending back to the caller thread
+	for _, ri := range resp.Rounds {
+		sendResults <- ds.EventReturn{
+			ri,
+			false,
+		}
+	}
+}
diff --git a/api/results_test.go b/api/results_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..54433f2e9b8bcfd91e3d83e5745bdc1d3ceecc75
--- /dev/null
+++ b/api/results_test.go
@@ -0,0 +1,249 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package api
+
+import (
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+const numRounds = 10
+
+// Happy path
+func TestClient_GetRoundResults(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Pre-populate the results channel with successful rounds
+	sendResults := make(chan ds.EventReturn, len(roundList))
+	for i := 0; i < numRounds; i++ {
+		sendResults <- ds.EventReturn{
+			RoundInfo: &pb.RoundInfo{
+				ID:    uint64(i),
+				State: uint32(states.COMPLETED),
+			},
+			TimedOut: false,
+		}
+	}
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewNoHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(1 * time.Second)
+
+	// If any rounds timed out or any round failed, the happy path has failed
+	if timeout || !successfulRounds {
+		t.Errorf("Unexpected round failures in happy path. "+
+			"Expected all rounds to succeed with no timeouts."+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+
+}
+
+// Checks that an two failed rounds (one timed out, one failure)
+// affects the values in the report.
+// Kept separately to ensure uncoupled failed rounds
+// affect both report booleans
+func TestClient_GetRoundResults_FailedRounds(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Pre-populate the results channel with mostly successful rounds
+	sendResults := make(chan ds.EventReturn, len(roundList))
+	for i := 0; i < numRounds; i++ {
+		// Last two rounds will have a failure and a timeout respectively
+		result := ds.EventReturn{
+			RoundInfo: &pb.RoundInfo{
+				ID:    uint64(i),
+				State: uint32(states.COMPLETED),
+			},
+			TimedOut: false,
+		}
+		if i == numRounds-2 {
+			result.RoundInfo.State = uint32(states.FAILED)
+		}
+
+		sendResults <- result
+
+	}
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewNoHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(2 * time.Second)
+
+	// If no rounds have failed, this test has failed
+	if successfulRounds {
+		t.Errorf("Expected some rounds to fail. "+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+
+}
+
+// Use the historical rounds interface which actually sends back rounds
+func TestClient_GetRoundResults_HistoricalRounds(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Pre-populate the results channel with successful rounds
+	sendResults := make(chan ds.EventReturn, len(roundList)-2)
+	for i := 0; i < numRounds; i++ {
+		// Skip sending rounds intended for historical rounds comm
+		if i == failedHistoricalRoundID ||
+			i == completedHistoricalRoundID {
+			continue
+		}
+
+		sendResults <- ds.EventReturn{
+			RoundInfo: &pb.RoundInfo{
+				ID:    uint64(i),
+				State: uint32(states.COMPLETED),
+			},
+			TimedOut: false,
+		}
+	}
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Overpopulate the round buffer, ensuring a circle back of the ring buffer
+	for i := 1; i <= ds.RoundInfoBufLen+completedHistoricalRoundID+1; i++ {
+		ri := &pb.RoundInfo{ID: uint64(i)}
+		if err = signRoundInfo(ri); err != nil {
+			t.Errorf("Failed to sign round in set up: %v", err)
+		}
+
+		err = client.network.GetInstance().RoundUpdate(ri)
+		if err != nil {
+			t.Errorf("Failed to upsert round in set up: %v", err)
+		}
+
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(2 * time.Second)
+
+	// If no round failed, this test has failed
+	if successfulRounds {
+		t.Errorf("Expected historical rounds to have round failures"+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+}
+
+// Force some timeouts by not populating the entire results channel
+func TestClient_GetRoundResults_Timeout(t *testing.T) {
+	// Populate a round list to request
+	var roundList []id.Round
+	for i := 0; i < numRounds; i++ {
+		roundList = append(roundList, id.Round(i))
+	}
+
+	// Create a broken channel which will never send,
+	// forcing a timeout
+	var sendResults chan ds.EventReturn
+	sendResults = nil
+
+	// Create a new copy of the test client for this test
+	client, err := newTestingClient(t)
+	if err != nil {
+		t.Errorf("Failed in setup: %v", err)
+	}
+
+	// Construct the round call back function signature
+	var successfulRounds, timeout bool
+	receivedRCB := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]RoundResult) {
+		successfulRounds = allRoundsSucceeded
+		timeout = timedOut
+	}
+
+	// Call the round results
+	err = client.getRoundResults(roundList, time.Duration(10)*time.Millisecond,
+		receivedRCB, sendResults, NewNoHistoricalRoundsComm())
+	if err != nil {
+		t.Errorf("Error in happy path: %v", err)
+	}
+
+	// Sleep to allow the report to come through the pipeline
+	time.Sleep(2 * time.Second)
+
+	// If no rounds have timed out , this test has failed
+	if !timeout {
+		t.Errorf("Expected all rounds to timeout with no valid round reporter."+
+			"\n\tTimedOut: %v"+
+			"\n\tallRoundsSucceeded: %v", timeout, successfulRounds)
+	}
+
+}
diff --git a/api/send.go b/api/send.go
new file mode 100644
index 0000000000000000000000000000000000000000..19d6a54b853f911e47af7d2d9bcafa46e2b7bd4d
--- /dev/null
+++ b/api/send.go
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+//This holds all functions to send messages over the network
+
+// SendE2E sends an end-to-end payload to the provided recipient with
+// the provided msgType. Returns the list of rounds in which parts of
+// the message were sent or an error if it fails.
+func (c *Client) SendE2E(m message.Send, param params.E2E) ([]id.Round,
+	e2e.MessageID, error) {
+	jww.INFO.Printf("SendE2E(%s, %d. %v)", m.Recipient,
+		m.MessageType, m.Payload)
+	return c.network.SendE2E(m, param)
+}
+
+// SendUnsafe sends an unencrypted payload to the provided recipient
+// with the provided msgType. Returns the list of rounds in which parts
+// of the message were sent or an error if it fails.
+// NOTE: Do not use this function unless you know what you are doing.
+// This function always produces an error message in client logging.
+func (c *Client) SendUnsafe(m message.Send, param params.Unsafe) ([]id.Round,
+	error) {
+	jww.INFO.Printf("SendUnsafe(%s, %d. %v)", m.Recipient,
+		m.MessageType, m.Payload)
+	return c.network.SendUnsafe(m, param)
+}
+
+// SendCMIX sends a "raw" CMIX message payload to the provided
+// recipient. Note that both SendE2E and SendUnsafe call SendCMIX.
+// Returns the round ID of the round the payload was sent or an error
+// if it fails.
+func (c *Client) SendCMIX(msg format.Message, recipientID *id.ID,
+	param params.CMIX) (id.Round, ephemeral.Id, error) {
+	jww.INFO.Printf("SendCMIX(%s)", string(msg.GetContents()))
+	return c.network.SendCMIX(msg, recipientID, param)
+}
+
+// NewCMIXMessage Creates a new cMix message with the right properties
+// for the current cMix network.
+// FIXME: this is weird and shouldn't be necessary, but it is.
+func (c *Client) NewCMIXMessage(contents []byte) (format.Message, error) {
+	primeSize := len(c.storage.Cmix().GetGroup().GetPBytes())
+	msg := format.NewMessage(primeSize)
+	if len(contents) > msg.ContentsSize() {
+		return format.Message{}, errors.New("Contents to long for cmix")
+	}
+	msg.SetContents(contents)
+	return msg, nil
+}
diff --git a/api/status.go b/api/status.go
new file mode 100644
index 0000000000000000000000000000000000000000..7fd31ae937955d12cb76e8471182c7a406df5b76
--- /dev/null
+++ b/api/status.go
@@ -0,0 +1,87 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	"sync/atomic"
+)
+
+type Status int
+
+const (
+	Stopped  Status = 0
+	Starting Status = 1000
+	Running  Status = 2000
+	Stopping Status = 3000
+)
+
+func (s Status) String() string {
+	switch s {
+	case Stopped:
+		return "Stopped"
+	case Starting:
+		return "Starting"
+	case Running:
+		return "Running"
+	case Stopping:
+		return "Stopping"
+	default:
+		return fmt.Sprintf("Unknown status %d", s)
+	}
+}
+
+type statusTracker struct {
+	s *uint32
+}
+
+func newStatusTracker() *statusTracker {
+	s := uint32(Stopped)
+	return &statusTracker{s: &s}
+}
+
+func (s *statusTracker) toStarting() error {
+	if !atomic.CompareAndSwapUint32(s.s, uint32(Stopped), uint32(Starting)) {
+		return errors.Errorf("Failed to move to '%s' status, at '%s', "+
+			"must be at '%s' for transition", Starting,
+			Status(atomic.LoadUint32(s.s)), Stopped)
+	}
+	return nil
+}
+
+func (s *statusTracker) toRunning() error {
+	if !atomic.CompareAndSwapUint32(s.s, uint32(Starting), uint32(Running)) {
+		return errors.Errorf("Failed to move to '%s' status, at '%s', "+
+			"must be at '%s' for transition",
+			Running, Status(atomic.LoadUint32(s.s)), Starting)
+	}
+	return nil
+}
+
+func (s *statusTracker) toStopping() error {
+	if !atomic.CompareAndSwapUint32(s.s, uint32(Running), uint32(Stopping)) {
+		return errors.Errorf("Failed to move to '%s' status, at '%s',"+
+			" must be at '%s' for transition", Stopping,
+			Status(atomic.LoadUint32(s.s)), Running)
+	}
+	return nil
+}
+
+func (s *statusTracker) toStopped() error {
+	if !atomic.CompareAndSwapUint32(s.s, uint32(Stopping), uint32(Stopped)) {
+		return errors.Errorf("Failed to move to '%s' status, at '%s',"+
+			" must be at '%s' for transition", Stopped,
+			Status(atomic.LoadUint32(s.s)), Stopping)
+	}
+	return nil
+}
+
+func (s *statusTracker) get() Status {
+	return Status(atomic.LoadUint32(s.s))
+}
diff --git a/api/user.go b/api/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..892d84c63c8fb0e29001774c6c645f351b3d00b8
--- /dev/null
+++ b/api/user.go
@@ -0,0 +1,136 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"encoding/binary"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/user"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/crypto/xx"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+)
+
+const (
+	// SaltSize size of user salts
+	SaltSize = 32
+)
+
+// createNewUser generates an identity for cMix
+func createNewUser(rng csprng.Source, cmix, e2e *cyclic.Group) user.User {
+	// CMIX Keygen
+	// FIXME: Why 256 bits? -- this is spec but not explained, it has
+	// to do with optimizing operations on one side and still preserves
+	// decent security -- cite this.
+	cMixKeyBytes, err := csprng.GenerateInGroup(cmix.GetPBytes(), 256, rng)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	// DH Keygen
+	// FIXME: Why 256 bits? -- this is spec but not explained, it has
+	// to do with optimizing operations on one side and still preserves
+	// decent security -- cite this. Why valid for BOTH e2e and cmix?
+	e2eKeyBytes, err := csprng.GenerateInGroup(e2e.GetPBytes(), 256, rng)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	// RSA Keygen (4096 bit defaults)
+	transmissionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+	receptionRsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	// Salt, UID, etc gen
+	transmissionSalt := make([]byte, SaltSize)
+	n, err := csprng.NewSystemRNG().Read(transmissionSalt)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+	if n != SaltSize {
+		jww.FATAL.Panicf("transmissionSalt size too small: %d", n)
+	}
+	transmissionID, err := xx.NewID(transmissionRsaKey.GetPublic(), transmissionSalt, id.User)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	// Salt, UID, etc gen
+	receptionSalt := make([]byte, SaltSize)
+	n, err = csprng.NewSystemRNG().Read(receptionSalt)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+	if n != SaltSize {
+		jww.FATAL.Panicf("receptionSalt size too small: %d", n)
+	}
+	receptionID, err := xx.NewID(receptionRsaKey.GetPublic(), receptionSalt, id.User)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	return user.User{
+		TransmissionID:   transmissionID.DeepCopy(),
+		TransmissionSalt: transmissionSalt,
+		TransmissionRSA:  transmissionRsaKey,
+		ReceptionID:      receptionID.DeepCopy(),
+		ReceptionSalt:    receptionSalt,
+		ReceptionRSA:     receptionRsaKey,
+		Precanned:        false,
+		CmixDhPrivateKey: cmix.NewIntFromBytes(cMixKeyBytes),
+		E2eDhPrivateKey:  e2e.NewIntFromBytes(e2eKeyBytes),
+	}
+}
+
+// TODO: Add precanned user code structures here.
+// creates a precanned user
+func createPrecannedUser(precannedID uint, rng csprng.Source, cmix, e2e *cyclic.Group) user.User {
+	// DH Keygen
+	// FIXME: Why 256 bits? -- this is spec but not explained, it has
+	// to do with optimizing operations on one side and still preserves
+	// decent security -- cite this. Why valid for BOTH e2e and cmix?
+	prng := rand.New(rand.NewSource(int64(precannedID)))
+	e2eKeyBytes, err := csprng.GenerateInGroup(e2e.GetPBytes(), 256, prng)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	// Salt, UID, etc gen
+	salt := make([]byte, SaltSize)
+
+	userID := id.ID{}
+	binary.BigEndian.PutUint64(userID[:], uint64(precannedID))
+	userID.SetType(id.User)
+
+	// NOTE: not used... RSA Keygen (4096 bit defaults)
+	rsaKey, err := rsa.GenerateKey(rng, rsa.DefaultRSABitLen)
+	if err != nil {
+		jww.FATAL.Panicf(err.Error())
+	}
+
+	return user.User{
+		TransmissionID:   &userID,
+		TransmissionSalt: salt,
+		ReceptionID:      &userID,
+		ReceptionSalt:    salt,
+		Precanned:        true,
+		E2eDhPrivateKey:  e2e.NewIntFromBytes(e2eKeyBytes),
+		// NOTE: These are dummy/not used
+		CmixDhPrivateKey: cmix.NewInt(1),
+		TransmissionRSA:  rsaKey,
+		ReceptionRSA:     rsaKey,
+	}
+}
diff --git a/api/utilsInterfaces_test.go b/api/utilsInterfaces_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffa5154795045f361afd70d8cb67d0c45eb6e4c4
--- /dev/null
+++ b/api/utilsInterfaces_test.go
@@ -0,0 +1,121 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package api
+
+import (
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	cE2e "gitlab.com/elixxir/crypto/e2e"
+	"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"
+)
+
+// Mock comm struct which returns no historical round data
+type noHistoricalRounds struct{}
+
+// Constructor for noHistoricalRounds
+func NewNoHistoricalRoundsComm() *noHistoricalRounds {
+	return &noHistoricalRounds{}
+}
+
+// Returns no rounds back
+func (ht *noHistoricalRounds) RequestHistoricalRounds(host *connect.Host,
+	message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error) {
+	return nil, nil
+}
+
+// Built for interface adherence
+func (ht *noHistoricalRounds) GetHost(hostId *id.ID) (*connect.Host, bool) {
+	return nil, false
+}
+
+// Generate a mock comm which returns some historical round data
+type historicalRounds struct{}
+
+// Constructor for historicalRounds comm interface
+func NewHistoricalRoundsComm() *historicalRounds {
+	return &historicalRounds{}
+}
+
+// Round IDs to return on mock historicalRounds comm
+const failedHistoricalRoundID = 7
+const completedHistoricalRoundID = 8
+
+//  Mock comms endpoint which returns historical rounds
+func (ht *historicalRounds) RequestHistoricalRounds(host *connect.Host,
+	message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error) {
+	// Return one successful and one failed mock round
+	failedRound := &pb.RoundInfo{
+		ID:    failedHistoricalRoundID,
+		State: uint32(states.FAILED),
+	}
+
+	completedRound := &pb.RoundInfo{
+		ID:    completedHistoricalRoundID,
+		State: uint32(states.COMPLETED),
+	}
+
+	return &pb.HistoricalRoundsResponse{
+		Rounds: []*pb.RoundInfo{failedRound, completedRound},
+	}, nil
+}
+
+// Build for interface adherence
+func (ht *historicalRounds) GetHost(hostId *id.ID) (*connect.Host, bool) {
+	return nil, true
+}
+
+// Contains a test implementation of the networkManager interface.
+type testNetworkManagerGeneric struct {
+	instance *network.Instance
+}
+
+/* Below methods built for interface adherence */
+func (t *testNetworkManagerGeneric) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+func (t *testNetworkManagerGeneric) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	return nil, nil
+}
+func (t *testNetworkManagerGeneric) CheckGarbledMessages() {
+	return
+}
+func (t *testNetworkManagerGeneric) SendE2E(m message.Send, p params.E2E) (
+	[]id.Round, cE2e.MessageID, error) {
+	rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
+	return rounds, cE2e.MessageID{}, nil
+
+}
+func (t *testNetworkManagerGeneric) SendUnsafe(m message.Send, p params.Unsafe) ([]id.Round, error) {
+	return nil, nil
+}
+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
+}
+func (t *testNetworkManagerGeneric) GetInstance() *network.Instance {
+	return t.instance
+}
+func (t *testNetworkManagerGeneric) RegisterWithPermissioning(string) ([]byte, error) {
+	return nil, nil
+}
+func (t *testNetworkManagerGeneric) GetRemoteVersion() (string, error) {
+	return "test", nil
+}
+func (t *testNetworkManagerGeneric) GetStoppable() stoppable.Stoppable {
+	return &stoppable.Multi{}
+}
+
+func (t *testNetworkManagerGeneric) InProgressRegistrations() int {
+	return 0
+}
diff --git a/api/utils_test.go b/api/utils_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..31e09e2d4f5b87a16e434ef1c6eef58423a903b3
--- /dev/null
+++ b/api/utils_test.go
@@ -0,0 +1,152 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+import (
+	"testing"
+
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/comms/testkeys"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/crypto/tls"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/utils"
+)
+
+func newTestingClient(face interface{}) (*Client, error) {
+	switch face.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", face)
+	}
+
+	def := getNDF(face)
+	marshalledDef, _ := def.Marshal()
+	storageDir := "ignore.1"
+	password := []byte("hunter2")
+	err := NewClient(string(marshalledDef), storageDir, password, "AAAA")
+	if err != nil {
+		return nil, errors.Errorf("Could not construct a mock client: %v", err)
+	}
+
+	c, err := OpenClient(storageDir, password, params.GetDefaultNetwork())
+	if err != nil {
+		return nil, errors.Errorf("Could not open a mock client: %v", err)
+	}
+
+	commsManager := connect.NewManagerTesting(face)
+
+	cert, err := utils.ReadFile(testkeys.GetNodeCertPath())
+	if err != nil {
+		jww.FATAL.Panicf("Failed to create new test Instance: %v", err)
+	}
+
+	commsManager.AddHost(&id.Permissioning, "", cert, connect.GetDefaultHostParams())
+	instanceComms := &connect.ProtoComms{
+		Manager: commsManager,
+	}
+
+	thisInstance, err := network.NewInstanceTesting(instanceComms, def, def, nil, nil, face)
+	if err != nil {
+		return nil, nil
+	}
+
+	c.network = &testNetworkManagerGeneric{instance: thisInstance}
+
+	return c, nil
+}
+
+// Helper function which generates an ndf for testing
+func getNDF(face interface{}) *ndf.NetworkDefinition {
+	switch face.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", face)
+	}
+
+	cert, _ := utils.ReadFile(testkeys.GetNodeCertPath())
+	nodeID := id.NewIdFromBytes([]byte("gateway"), face)
+	return &ndf.NetworkDefinition{
+		Registration: ndf.Registration{
+			TlsCertificate: string(cert),
+		},
+		Nodes: []ndf.Node{
+			{
+				ID:             nodeID.Bytes(),
+				Address:        "",
+				TlsCertificate: string(cert),
+			},
+		},
+		Gateways: []ndf.Gateway{
+			{
+				ID:             nodeID.Bytes(),
+				Address:        "",
+				TlsCertificate: string(cert),
+			},
+		},
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}
+
+// Signs a passed round info with the key tied to the test node cert
+// used throughout utils and other tests
+func signRoundInfo(ri *pb.RoundInfo) error {
+	privKeyFromFile := testkeys.LoadFromPath(testkeys.GetNodeKeyPath())
+
+	pk, err := tls.LoadRSAPrivateKey(string(privKeyFromFile))
+	if err != nil {
+		return errors.Errorf("Couldn't load private key: %+v", err)
+	}
+
+	ourPrivateKey := &rsa.PrivateKey{PrivateKey: *pk}
+
+	return signature.Sign(ri, ourPrivateKey)
+
+}
diff --git a/api/version.go b/api/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..57d607b585497d1c4e73ef104536e19b8ab82ed2
--- /dev/null
+++ b/api/version.go
@@ -0,0 +1,42 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package api
+
+/*
+func (c *Client) Version() version.Version {
+	v, err := version.ParseVersion(SEMVER)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to parse the client version: %s", err)
+	}
+	return v
+}
+
+func (c *Client) checkVersion() error {
+	clientVersion := c.Version()
+	jww.INFO.Printf("Client Version: %s", clientVersion.String())
+
+	has, netVersion, err := c.permissioning.GetNetworkVersion()
+	if err != nil {
+		return errors.WithMessage(err, "failed to get check "+
+			"version compatibility")
+	}
+	if has {
+		jww.INFO.Printf("Minimum Network Version: %v", netVersion)
+		if !version.IsCompatible(netVersion, clientVersion) {
+			return errors.Errorf("Client and Minimum Network Version are "+
+				"incompatible\n"+
+				"\tMinimum Network: %s\n"+
+				"\tClient: %s", netVersion.String(), clientVersion.String())
+		}
+	} else {
+		jww.INFO.Printf("Network requires no minimum version")
+	}
+
+	return nil
+}
+*/
diff --git a/api/version_vars.go b/api/version_vars.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc333c8a688faddebc96c5de0f589d9137801308
--- /dev/null
+++ b/api/version_vars.go
@@ -0,0 +1,44 @@
+// Code generated by go generate; DO NOT EDIT.
+// This file was generated by robots at
+// 2021-03-10 14:16:47.093264 -0800 PST m=+0.046129936
+package api
+
+const GITVERSION = `2096d6ae Merge branch 'Anne/v2' into 'release'`
+const SEMVER = "2.0.0"
+const DEPENDENCIES = `module gitlab.com/elixxir/client
+
+go 1.13
+
+require (
+	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
+	github.com/golang/protobuf v1.4.3
+	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
+	github.com/magiconair/properties v1.8.4 // indirect
+	github.com/mitchellh/mapstructure v1.4.0 // indirect
+	github.com/pelletier/go-toml v1.8.1 // indirect
+	github.com/pkg/errors v0.9.1
+	github.com/smartystreets/assertions v1.0.1 // indirect
+	github.com/spf13/afero v1.5.1 // indirect
+	github.com/spf13/cast v1.3.1 // indirect
+	github.com/spf13/cobra v1.1.1
+	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.20210310191636-1bca0ddac665
+	gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2
+	gitlab.com/elixxir/ekv v0.1.4
+	gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b
+	gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01
+	gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96
+	gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a
+	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
+	golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
+	google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
+	google.golang.org/grpc v1.34.0 // indirect
+	google.golang.org/protobuf v1.25.0
+	gopkg.in/ini.v1 v1.62.0 // indirect
+)
+
+replace google.golang.org/grpc => github.com/grpc/grpc-go v1.27.1
+`
diff --git a/auth/callback.go b/auth/callback.go
new file mode 100644
index 0000000000000000000000000000000000000000..a304905f4bef5e6177d04d1b9bb7693fe36be24d
--- /dev/null
+++ b/auth/callback.go
@@ -0,0 +1,322 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage/auth"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/elixxir/primitives/format"
+	"strings"
+)
+
+func (m *Manager) StartProcessies() stoppable.Stoppable {
+
+	stop := stoppable.NewSingle("Auth")
+	authStore := m.storage.Auth()
+	grp := m.storage.E2e().GetGroup()
+
+	go func() {
+		select {
+		case <-stop.Quit():
+			return
+		case msg := <-m.rawMessages:
+			//lookup the message, check if it is an auth request
+			cmixMsg := format.Unmarshal(msg.Payload)
+			fp := cmixMsg.GetKeyFP()
+			jww.INFO.Printf("RAW AUTH FP: %v", fp)
+			// this takes the request lock if it is a specific fp,
+			// all exits after this need to call fail or delete if it is
+			// specific
+			fpType, sr, myHistoricalPrivKey, err := authStore.GetFingerprint(fp)
+			if err != nil {
+				jww.TRACE.Printf("FINGERPRINT FAILURE: %s", err.Error())
+				// if the lookup fails, ignore the message. It is likely
+				// garbled or for a different protocol
+				break
+			}
+
+			//denote that the message is not garbled
+			m.storage.GetGarbledMessages().Remove(cmixMsg)
+
+			switch fpType {
+			// if it is general, that means a new request has been received
+			case auth.General:
+				m.handleRequest(cmixMsg, myHistoricalPrivKey, grp)
+			// if it is specific, that means the original request was sent
+			// by this users and a confirmation has been received
+			case auth.Specific:
+				jww.INFO.Printf("Received AutConfirm from %s,"+
+					" msgDigest: %s", sr.GetPartner(), cmixMsg.Digest())
+				m.handleConfirm(cmixMsg, sr, grp)
+			}
+		}
+	}()
+	return stop
+}
+
+func (m *Manager) handleRequest(cmixMsg format.Message,
+	myHistoricalPrivKey *cyclic.Int, grp *cyclic.Group) {
+	//decode the outer format
+	baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp)
+	if err != nil {
+		jww.WARN.Printf("Failed to handle auth request: %s", err)
+		return
+	}
+
+	myPubKey := diffieHellman.GeneratePublicKey(myHistoricalPrivKey, grp)
+
+	jww.TRACE.Printf("handleRequest MYPUBKEY: %v", myPubKey.Bytes())
+	jww.TRACE.Printf("handleRequest PARTNERPUBKEY: %v", partnerPubKey.Bytes())
+
+	//decrypt the message
+	success, payload := cAuth.Decrypt(myHistoricalPrivKey,
+		partnerPubKey, baseFmt.GetSalt(), baseFmt.GetEcrPayload(),
+		cmixMsg.GetMac(), grp)
+
+	if !success {
+		jww.WARN.Printf("Recieved auth request failed " +
+			"its mac check")
+		return
+	}
+
+	//decode the ecr format
+	ecrFmt, err := unmarshalEcrFormat(payload)
+	if err != nil {
+		jww.WARN.Printf("Failed to unmarshal auth "+
+			"request's encrypted payload: %s", err)
+		return
+	}
+
+	//decode the request format
+	requestFmt, err := newRequestFormat(ecrFmt)
+	if err != nil {
+		jww.WARN.Printf("Failed to unmarshal auth "+
+			"request's internal payload: %s", err)
+		return
+	}
+
+	partnerID, err := requestFmt.GetID()
+	if err != nil {
+		jww.WARN.Printf("Failed to unmarshal auth "+
+			"request's sender ID: %s", err)
+		return
+	}
+
+	jww.INFO.Printf("Received AuthRequest from %s,"+
+		" msgDigest: %s", partnerID, cmixMsg.Digest())
+
+	/*do state edge checks*/
+	// check if a relationship already exists.
+	// if it does and the keys used are the same as we have, send a
+	// confirmation in case there are state issues.
+	// do not store
+	if _, err := m.storage.E2e().GetPartner(partnerID); err == nil {
+		jww.WARN.Printf("Recieved Auth request for %s, "+
+			"channel already exists. Ignoring", partnerID)
+		//exit
+		return
+	} else {
+		//check if the relationship already exists,
+		rType, sr2, _, err := m.storage.Auth().GetRequest(partnerID)
+		if err != nil && !strings.Contains(err.Error(), auth.NoRequest) {
+			// if another error is recieved, print it and exit
+			jww.WARN.Printf("Recieved new Auth request for %s, "+
+				"internal lookup produced bad result: %+v",
+				partnerID, err)
+			return
+		} else {
+			//handle the events where the relationship already exists
+			switch rType {
+			// if this is a duplicate, ignore the message
+			case auth.Receive:
+				jww.WARN.Printf("Recieved new Auth request for %s, "+
+					"is a duplicate", partnerID)
+				return
+			// if we sent a request, then automatically confirm
+			// then exit, nothing else needed
+			case auth.Sent:
+				jww.INFO.Printf("Received AuthRequest from %s,"+
+					" msgDigest: %s which has been requested, auto-confirming",
+					partnerID, cmixMsg.Digest())
+				// do the confirmation
+				if err := m.doConfirm(sr2, grp, partnerPubKey, myPubKey,
+					ecrFmt.GetOwnership()); err != nil {
+					jww.WARN.Printf("Auto Confirmation with %s failed: %s",
+						partnerID, err)
+				}
+				//exit
+				return
+			}
+		}
+	}
+
+	//process the inner payload
+	facts, msg, err := fact.UnstringifyFactList(
+		string(requestFmt.msgPayload))
+	if err != nil {
+		jww.WARN.Printf("failed to parse facts and message "+
+			"from Auth Request: %s", err)
+		return
+	}
+
+	//create the contact
+	c := contact.Contact{
+		ID:             partnerID,
+		DhPubKey:       partnerPubKey,
+		OwnershipProof: copySlice(ecrFmt.ownership),
+		Facts:          facts,
+	}
+
+	// fixme: the client will never be notified of the channel creation if a
+	// crash occurs after the store but before the conclusion of the callback
+	//create the auth storage
+	if err = m.storage.Auth().AddReceived(c); err != nil {
+		jww.WARN.Printf("failed to store contact Auth "+
+			"Request: %s", err)
+		return
+	}
+
+	//  fixme: if a crash occurs before or during the calls, the notification
+	//  will never be sent.
+	cbList := m.requestCallbacks.Get(c.ID)
+	for _, cb := range cbList {
+		rcb := cb.(interfaces.RequestCallback)
+		go rcb(c, msg)
+	}
+	return
+}
+
+func (m *Manager) handleConfirm(cmixMsg format.Message, sr *auth.SentRequest,
+	grp *cyclic.Group) {
+	// check if relationship already exists
+	if mgr, err := m.storage.E2e().GetPartner(sr.GetPartner()); mgr != nil || err == nil {
+		jww.WARN.Printf("Cannot confirm auth for %s, channel already "+
+			"exists.", sr.GetPartner())
+		m.storage.Auth().Fail(sr.GetPartner())
+		return
+	}
+
+	// extract the message
+	baseFmt, partnerPubKey, err := handleBaseFormat(cmixMsg, grp)
+	if err != nil {
+		jww.WARN.Printf("Failed to handle auth confirm: %s", err)
+		m.storage.Auth().Fail(sr.GetPartner())
+		return
+	}
+
+	jww.TRACE.Printf("handleConfirm PARTNERPUBKEY: %v", partnerPubKey.Bytes())
+	jww.TRACE.Printf("handleConfirm SRMYPUBKEY: %v", sr.GetMyPubKey().Bytes())
+
+	// decrypt the payload
+	success, payload := cAuth.Decrypt(sr.GetMyPrivKey(),
+		partnerPubKey, baseFmt.GetSalt(), baseFmt.GetEcrPayload(),
+		cmixMsg.GetMac(), grp)
+
+	if !success {
+		jww.WARN.Printf("Recieved auth confirmation failed its mac " +
+			"check")
+		m.storage.Auth().Fail(sr.GetPartner())
+		return
+	}
+
+	ecrFmt, err := unmarshalEcrFormat(payload)
+	if err != nil {
+		jww.WARN.Printf("Failed to unmarshal auth confirmation's "+
+			"encrypted payload: %s", err)
+		m.storage.Auth().Fail(sr.GetPartner())
+		return
+	}
+
+	// finalize the confirmation
+	if err := m.doConfirm(sr, grp, partnerPubKey, sr.GetPartnerHistoricalPubKey(),
+		ecrFmt.GetOwnership()); err != nil {
+		jww.WARN.Printf("Confirmation failed: %s", err)
+		m.storage.Auth().Fail(sr.GetPartner())
+		return
+	}
+}
+
+func (m *Manager) doConfirm(sr *auth.SentRequest, grp *cyclic.Group,
+	partnerPubKey, myPubKeyOwnershipProof *cyclic.Int, ownershipProof []byte) error {
+	// verify the message came from the intended recipient
+	if !cAuth.VerifyOwnershipProof(sr.GetMyPrivKey(),
+		myPubKeyOwnershipProof, grp, ownershipProof) {
+		return errors.Errorf("Failed authenticate identity for auth "+
+			"confirmation of %s", sr.GetPartner())
+	}
+
+	// fixme: channel can get into a bricked state if the first save occurs and
+	// the second does not
+	p := m.storage.E2e().GetE2ESessionParams()
+	if err := m.storage.E2e().AddPartner(sr.GetPartner(),
+		partnerPubKey, sr.GetMyPrivKey(), p, p); err != nil {
+		return errors.Errorf("Failed to create channel with partner (%s) "+
+			"after confirmation: %+v",
+			sr.GetPartner(), err)
+	}
+
+	// delete the in progress negotiation
+	// this undoes the request lock
+	if err := m.storage.Auth().Delete(sr.GetPartner()); err != nil {
+		return errors.Errorf("UNRECOVERABLE! Failed to delete in "+
+			"progress negotiation with partner (%s) after confirmation: %+v",
+			sr.GetPartner(), err)
+	}
+
+	//notify the end point
+	c := contact.Contact{
+		ID:             sr.GetPartner().DeepCopy(),
+		DhPubKey:       partnerPubKey.DeepCopy(),
+		OwnershipProof: copySlice(ownershipProof),
+		Facts:          make([]fact.Fact, 0),
+	}
+
+	//  fixme: if a crash occurs before or during the calls, the notification
+	//  will never be sent.
+	cbList := m.confirmCallbacks.Get(c.ID)
+	for _, cb := range cbList {
+		ccb := cb.(interfaces.ConfirmCallback)
+		go ccb(c)
+	}
+
+	m.net.CheckGarbledMessages()
+
+	return nil
+}
+
+func copySlice(s []byte) []byte {
+	c := make([]byte, len(s))
+	copy(c, s)
+	return c
+}
+
+func handleBaseFormat(cmixMsg format.Message, grp *cyclic.Group) (baseFormat,
+	*cyclic.Int, error) {
+
+	baseFmt, err := unmarshalBaseFormat(cmixMsg.GetContents(),
+		grp.GetP().ByteLen())
+	if err != nil {
+		return baseFormat{}, nil, errors.WithMessage(err, "Failed to"+
+			" unmarshal auth")
+	}
+
+	if !grp.BytesInside(baseFmt.pubkey) {
+		return baseFormat{}, nil, errors.WithMessage(err, "Received "+
+			"auth confirmation public key is not in the e2e cyclic group")
+	}
+	partnerPubKey := grp.NewIntFromBytes(baseFmt.pubkey)
+	return baseFmt, partnerPubKey, nil
+}
diff --git a/auth/callbacks.go b/auth/callbacks.go
new file mode 100644
index 0000000000000000000000000000000000000000..2168c82d3636bdd9ff25c9d1c4c4cddf606b2445
--- /dev/null
+++ b/auth/callbacks.go
@@ -0,0 +1,77 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+)
+
+type callbackMap struct {
+	generalCallback  []interface{}
+	specificCallback map[id.ID]interface{}
+	overrideCallback []interface{}
+	mux              sync.RWMutex
+}
+
+func newCallbackMap() *callbackMap {
+	return &callbackMap{
+		generalCallback:  make([]interface{}, 0),
+		specificCallback: make(map[id.ID]interface{}),
+		overrideCallback: make([]interface{}, 0),
+	}
+}
+
+//adds a general callback. This will be preempted by any specific callback
+func (cm *callbackMap) AddGeneral(cb interface{}) {
+	cm.mux.Lock()
+	cm.generalCallback = append(cm.generalCallback, cb)
+	cm.mux.Unlock()
+}
+
+//adds an override callback. This will NOT be preempted by any callback
+func (cm *callbackMap) AddOverride(cb interface{}) {
+	cm.mux.Lock()
+	cm.overrideCallback = append(cm.overrideCallback, cb)
+	cm.mux.Unlock()
+}
+
+// adds a callback for a specific user ID. Only only callback can exist for a
+// user ID. False will be returned if a callback already exists and the new
+// one was not added
+func (cm *callbackMap) AddSpecific(id *id.ID, cb interface{}) bool {
+	cm.mux.Lock()
+	defer cm.mux.Unlock()
+	if _, ok := cm.specificCallback[*id]; ok {
+		return false
+	}
+	cm.specificCallback[*id] = cb
+	return true
+}
+
+// removes a callback for a specific user ID if it exists.
+func (cm *callbackMap) RemoveSpecific(id *id.ID) {
+	cm.mux.Lock()
+	defer cm.mux.Unlock()
+	delete(cm.specificCallback, *id)
+}
+
+//get all callback which fit with the passed id
+func (cm *callbackMap) Get(id *id.ID) []interface{} {
+	cm.mux.RLock()
+	defer cm.mux.RUnlock()
+	cbList := cm.overrideCallback
+
+	if specific, ok := cm.specificCallback[*id]; ok {
+		cbList = append(cbList, specific)
+	} else {
+		cbList = append(cbList, cm.generalCallback...)
+	}
+
+	return cbList
+}
diff --git a/auth/confirm.go b/auth/confirm.go
new file mode 100644
index 0000000000000000000000000000000000000000..d1b88bf99d9a1fe2871850b5d7c810ea39f7f6a6
--- /dev/null
+++ b/auth/confirm.go
@@ -0,0 +1,172 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	"gitlab.com/elixxir/client/storage"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"io"
+	"time"
+)
+
+func ConfirmRequestAuth(partner contact.Contact, rng io.Reader,
+	storage *storage.Session, net interfaces.NetworkManager) error {
+
+	/*edge checking*/
+
+	// check that messages can be sent over the network
+	if !net.GetHealthTracker().IsHealthy() {
+		return errors.New("Cannot confirm authenticated message " +
+			"when the network is not healthy")
+	}
+
+	// check if the partner has an auth in progress
+	// this takes the lock, from this point forward any errors need to release
+	// the lock
+	storedContact, err := storage.Auth().GetReceivedRequest(partner.ID)
+	if err != nil {
+		return errors.Errorf("failed to find a pending Auth Request: %s",
+			err)
+	}
+
+	// verify the passed contact matches what is stored
+	if storedContact.DhPubKey.Cmp(partner.DhPubKey) != 0 {
+		storage.Auth().Fail(partner.ID)
+		return errors.WithMessage(err, "Pending Auth Request has different "+
+			"pubkey than stored")
+	}
+
+	grp := storage.E2e().GetGroup()
+
+	/*cryptographic generation*/
+
+	//generate ownership proof
+	ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(),
+		partner.DhPubKey, storage.E2e().GetGroup())
+
+	//generate new keypair
+	newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng)
+	newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp)
+
+	//generate salt
+	salt := make([]byte, saltSize)
+	_, err = rng.Read(salt)
+	if err != nil {
+		storage.Auth().Fail(partner.ID)
+		return errors.Wrap(err, "Failed to generate salt for "+
+			"confirmation")
+	}
+
+	/*construct message*/
+	// we build the payload before we save because it is technically fallible
+	// which can get into a bricked state if it fails
+	cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen())
+	baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen())
+	ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
+
+	// setup the encrypted payload
+	ecrFmt.SetOwnership(ownership)
+	// confirmation has no custom payload
+
+	//encrypt the payload
+	ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey,
+		salt, ecrFmt.data, grp)
+
+	//get the fingerprint from the old ownership proof
+	fp := cAuth.MakeOwnershipProofFP(storedContact.OwnershipProof)
+
+	//final construction
+	baseFmt.SetEcrPayload(ecrPayload)
+	baseFmt.SetSalt(salt)
+	baseFmt.SetPubKey(newPubKey)
+
+	cmixMsg.SetKeyFP(fp)
+	cmixMsg.SetMac(mac)
+	cmixMsg.SetContents(baseFmt.Marshal())
+
+	// fixme: channel can get into a bricked state if the first save occurs and
+	// the second does not or the two occur and the storage into critical
+	// messages does not occur
+
+	//create local relationship
+	p := storage.E2e().GetE2ESessionParams()
+	if err := storage.E2e().AddPartner(partner.ID, partner.DhPubKey, newPrivKey,
+		p, p); err != nil {
+		storage.Auth().Fail(partner.ID)
+		return errors.Errorf("Failed to create channel with partner (%s) "+
+			"on confirmation: %+v",
+			partner.ID, err)
+	}
+
+	// delete the in progress negotiation
+	// this unlocks the request lock
+	if err := storage.Auth().Delete(partner.ID); err != nil {
+		return errors.Errorf("UNRECOVERABLE! Failed to delete in "+
+			"progress negotiation with partner (%s) after creating confirmation: %+v",
+			partner.ID, err)
+	}
+
+	//store the message as a critical message so it will always be sent
+	storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID)
+
+	jww.INFO.Printf("Confirming Auth with %s, msgDigest: %s",
+		partner.ID, cmixMsg.Digest())
+
+	/*send message*/
+	round, _, err := net.SendCMIX(cmixMsg, partner.ID, params.GetDefaultCMIX())
+	if err != nil {
+		// if the send fails just set it to failed, it will but automatically
+		// retried
+		jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+
+			"to transmit: %+v", partner.ID, cmixMsg.Digest(), err)
+		storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID)
+		return errors.WithMessage(err, "Auth Confirm Failed to transmit")
+	}
+
+	jww.INFO.Printf("Confirm Request with %s (msgDigest: %s) sent on round %d",
+		partner.ID, cmixMsg.Digest(), round)
+
+	/*check message delivery*/
+	sendResults := make(chan ds.EventReturn, 1)
+	roundEvents := net.GetInstance().GetRoundEvents()
+
+	roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute,
+		states.COMPLETED, states.FAILED)
+
+	success, numFailed, _ := utility.TrackResults(sendResults, 1)
+	if !success {
+		if numFailed > 0 {
+			jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+
+				"delivery due to round failure, will retry on reconnect",
+				partner.ID, cmixMsg.Digest())
+		} else {
+			jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) failed "+
+				"delivery due to timeout, will retry on reconnect",
+				partner.ID, cmixMsg.Digest())
+		}
+		jww.ERROR.Printf("auth confirm failed to transmit, will be " +
+			"handled on reconnect")
+		storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID)
+	} else {
+		jww.INFO.Printf("Auth Confirm with %s (msgDigest: %s) delivered "+
+			"sucesfully", partner.ID, cmixMsg.Digest())
+		storage.GetCriticalRawMessages().Succeeded(cmixMsg, partner.ID)
+	}
+
+	return nil
+}
diff --git a/auth/fmt.go b/auth/fmt.go
new file mode 100644
index 0000000000000000000000000000000000000000..deb1dad1acf77f196fbda05c90153758cb3bc289
--- /dev/null
+++ b/auth/fmt.go
@@ -0,0 +1,214 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+//Basic Format//////////////////////////////////////////////////////////////////
+const saltSize = 32
+
+type baseFormat struct {
+	data       []byte
+	pubkey     []byte
+	salt       []byte
+	ecrPayload []byte
+}
+
+func newBaseFormat(payloadSize, pubkeySize int) baseFormat {
+
+	if payloadSize < pubkeySize+saltSize {
+		jww.FATAL.Panicf("Size of baseFormat is too small, must be big " +
+			"enough to contain public key and salt")
+	}
+
+	f := buildBaseFormat(make([]byte, payloadSize), pubkeySize)
+
+	return f
+}
+
+func buildBaseFormat(data []byte, pubkeySize int) baseFormat {
+	f := baseFormat{
+		data: data,
+	}
+
+	f.pubkey = f.data[:pubkeySize]
+	f.salt = f.data[pubkeySize : pubkeySize+saltSize]
+	f.ecrPayload = f.data[pubkeySize+saltSize:]
+	return f
+}
+
+func unmarshalBaseFormat(b []byte, pubkeySize int) (baseFormat, error) {
+	if len(b) < pubkeySize+saltSize {
+		return baseFormat{}, errors.New("Received baseFormat too small")
+	}
+
+	return buildBaseFormat(b, pubkeySize), nil
+}
+
+func (f baseFormat) Marshal() []byte {
+	return f.data
+}
+
+func (f baseFormat) GetPubKey(grp *cyclic.Group) *cyclic.Int {
+	return grp.NewIntFromBytes(f.pubkey)
+}
+
+func (f baseFormat) SetPubKey(pubKey *cyclic.Int) {
+	pubKeyBytes := pubKey.LeftpadBytes(uint64(len(f.pubkey)))
+	copy(f.pubkey, pubKeyBytes)
+}
+
+func (f baseFormat) GetSalt() []byte {
+	return f.salt
+}
+
+func (f baseFormat) SetSalt(salt []byte) {
+	if len(salt) != saltSize {
+		jww.FATAL.Panicf("Salt incorrect size")
+	}
+
+	copy(f.salt, salt)
+}
+
+func (f baseFormat) GetEcrPayload() []byte {
+	return f.ecrPayload
+}
+
+func (f baseFormat) GetEcrPayloadLen() int {
+	return len(f.ecrPayload)
+}
+
+func (f baseFormat) SetEcrPayload(ecr []byte) {
+	if len(ecr) != len(f.ecrPayload) {
+		jww.FATAL.Panicf("Passed ecr payload incorrect lengh. Expected:"+
+			" %v, Recieved: %v", len(f.ecrPayload), len(ecr))
+	}
+
+	copy(f.ecrPayload, ecr)
+}
+
+//Encrypted Format//////////////////////////////////////////////////////////////
+const ownershipSize = 32
+
+type ecrFormat struct {
+	data      []byte
+	ownership []byte
+	payload   []byte
+}
+
+func newEcrFormat(size int) ecrFormat {
+	if size < ownershipSize {
+		jww.FATAL.Panicf("Size too small to hold")
+	}
+
+	f := buildEcrFormat(make([]byte, size))
+
+	return f
+
+}
+
+func buildEcrFormat(data []byte) ecrFormat {
+	f := ecrFormat{
+		data: data,
+	}
+
+	f.ownership = f.data[:ownershipSize]
+	f.payload = f.data[ownershipSize:]
+	return f
+}
+
+func unmarshalEcrFormat(b []byte) (ecrFormat, error) {
+	if len(b) < ownershipSize {
+		return ecrFormat{}, errors.New("Received ecr baseFormat too small")
+	}
+
+	return buildEcrFormat(b), nil
+}
+
+func (f ecrFormat) Marshal() []byte {
+	return f.data
+}
+
+func (f ecrFormat) GetOwnership() []byte {
+	return f.ownership
+}
+
+func (f ecrFormat) SetOwnership(ownership []byte) {
+	if len(ownership) != ownershipSize {
+		jww.FATAL.Panicf("ownership proof is the wrong size")
+	}
+
+	copy(f.ownership, ownership)
+}
+
+func (f ecrFormat) GetPayload() []byte {
+	return f.payload
+}
+
+func (f ecrFormat) PayloadLen() int {
+	return len(f.payload)
+}
+
+func (f ecrFormat) SetPayload(p []byte) {
+	if len(p) != len(f.payload) {
+		jww.FATAL.Panicf("Payload is the wrong length")
+	}
+
+	copy(f.payload, p)
+}
+
+//Request Format////////////////////////////////////////////////////////////////
+type requestFormat struct {
+	ecrFormat
+	id         []byte
+	msgPayload []byte
+}
+
+func newRequestFormat(ecrFmt ecrFormat) (requestFormat, error) {
+	if len(ecrFmt.payload) < id.ArrIDLen {
+		return requestFormat{}, errors.New("Payload is not long enough")
+	}
+
+	rf := requestFormat{
+		ecrFormat: ecrFmt,
+	}
+
+	rf.id = rf.payload[:id.ArrIDLen]
+	rf.msgPayload = rf.payload[id.ArrIDLen:]
+
+	return rf, nil
+}
+
+func (rf requestFormat) GetID() (*id.ID, error) {
+	return id.Unmarshal(rf.id)
+}
+
+func (rf requestFormat) SetID(myId *id.ID) {
+	copy(rf.id, myId.Marshal())
+}
+
+func (rf requestFormat) SetMsgPayload(b []byte) {
+	if len(b) > len(rf.msgPayload) {
+		jww.FATAL.Panicf("Message Payload is too long")
+	}
+
+	copy(rf.msgPayload, b)
+}
+
+func (rf requestFormat) MsgPayloadLen() int {
+	return len(rf.msgPayload)
+}
+
+func (rf requestFormat) GetMsgPayload() []byte {
+	return rf.msgPayload
+}
diff --git a/auth/manager.go b/auth/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..b5803d69feb7ae67073e351b1c7fd24c3ac5b597
--- /dev/null
+++ b/auth/manager.go
@@ -0,0 +1,91 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type Manager struct {
+	requestCallbacks *callbackMap
+	confirmCallbacks *callbackMap
+
+	rawMessages chan message.Receive
+
+	storage *storage.Session
+	net     interfaces.NetworkManager
+}
+
+func NewManager(sw interfaces.Switchboard, storage *storage.Session,
+	net interfaces.NetworkManager) *Manager {
+	m := &Manager{
+		requestCallbacks: newCallbackMap(),
+		confirmCallbacks: newCallbackMap(),
+		rawMessages:      make(chan message.Receive, 1000),
+		storage:          storage,
+		net:              net,
+	}
+
+	sw.RegisterChannel("Auth", switchboard.AnyUser(), message.Raw, m.rawMessages)
+
+	return m
+}
+
+// Adds a general callback to be used on auth requests. This will be preempted
+// by any specific callback
+func (m *Manager) AddGeneralRequestCallback(cb interfaces.RequestCallback) {
+	m.requestCallbacks.AddGeneral(cb)
+}
+
+// Adds a general callback to be used on auth requests. This will not be
+// preempted by any specific callback. It is recommended that the specific
+// callbacks are used, this is primarily for debugging.
+func (m *Manager) AddOverrideRequestCallback(cb interfaces.RequestCallback) {
+	m.requestCallbacks.AddOverride(cb)
+}
+
+// Adds a specific callback to be used on auth requests. This will preempt a
+// general callback, meaning the request will be heard on this callback and not
+// the general. Request will still be heard on override callbacks.
+func (m *Manager) AddSpecificRequestCallback(id *id.ID, cb interfaces.RequestCallback) {
+	m.requestCallbacks.AddSpecific(id, cb)
+}
+
+// Removes a specific callback to be used on auth requests.
+func (m *Manager) RemoveSpecificRequestCallback(id *id.ID) {
+	m.requestCallbacks.RemoveSpecific(id)
+}
+
+// Adds a general callback to be used on auth confirms. This will be preempted
+// by any specific callback
+func (m *Manager) AddGeneralConfirmCallback(cb interfaces.ConfirmCallback) {
+	m.confirmCallbacks.AddGeneral(cb)
+}
+
+// Adds a general callback to be used on auth confirms. This will not be
+// preempted by any specific callback. It is recommended that the specific
+// callbacks are used, this is primarily for debugging.
+func (m *Manager) AddOverrideConfirmCallback(cb interfaces.ConfirmCallback) {
+	m.confirmCallbacks.AddOverride(cb)
+}
+
+// Adds a specific callback to be used on auth confirms. This will preempt a
+// general callback, meaning the request will be heard on this callback and not
+// the general. Request will still be heard on override callbacks.
+func (m *Manager) AddSpecificConfirmCallback(id *id.ID, cb interfaces.ConfirmCallback) {
+	m.confirmCallbacks.AddSpecific(id, cb)
+}
+
+// Removes a specific callback to be used on auth confirm.
+func (m *Manager) RemoveSpecificConfirmCallback(id *id.ID) {
+	m.confirmCallbacks.RemoveSpecific(id)
+}
diff --git a/auth/request.go b/auth/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..8e9c1c58e206bfb51ca952cbec66227497f593f7
--- /dev/null
+++ b/auth/request.go
@@ -0,0 +1,191 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/auth"
+	"gitlab.com/elixxir/client/storage/e2e"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"io"
+	"strings"
+	"time"
+)
+
+const terminator = ";"
+
+func RequestAuth(partner, me contact.Contact, message string, rng io.Reader,
+	storage *storage.Session, net interfaces.NetworkManager) error {
+	/*edge checks generation*/
+
+	// check that messages can be sent over the network
+	if !net.GetHealthTracker().IsHealthy() {
+		return errors.New("Cannot create authenticated message " +
+			"when the network is not healthy")
+	}
+
+	// check that an authenticated channel does not already exists
+	if _, err := storage.E2e().GetPartner(partner.ID); err == nil ||
+		!strings.Contains(err.Error(), e2e.NoPartnerErrorStr) {
+		return errors.Errorf("Authenticated channel already " +
+			"established with partner")
+	}
+
+	// check that the request is being sent from the proper ID
+	if !me.ID.Cmp(storage.GetUser().ReceptionID) {
+		return errors.Errorf("Authenticated channel request " +
+			"can only be sent from user's identity")
+	}
+
+	// check that the message is properly formed
+	if strings.Contains(message, terminator) {
+		return errors.Errorf("Message cannot contain '%s'", terminator)
+	}
+
+	//lookup if an ongoing request is occurring
+	rqType, _, _, err := storage.Auth().GetRequest(partner.ID)
+	if err != nil && strings.Contains(err.Error(), auth.NoRequest) {
+		err = nil
+	}
+	if err != nil {
+		if rqType == auth.Receive {
+			return errors.WithMessage(err,
+				"Cannot send a request after "+
+					"receiving a request")
+		} else if rqType == auth.Sent {
+			return errors.WithMessage(err,
+				"Cannot send a request after "+
+					"already sending one")
+		}
+	}
+
+	grp := storage.E2e().GetGroup()
+
+	/*generate embedded message structures and check payload*/
+	cmixMsg := format.NewMessage(storage.Cmix().GetGroup().GetP().ByteLen())
+	baseFmt := newBaseFormat(cmixMsg.ContentsSize(), grp.GetP().ByteLen())
+	ecrFmt := newEcrFormat(baseFmt.GetEcrPayloadLen())
+	requestFmt, err := newRequestFormat(ecrFmt)
+	if err != nil {
+		return errors.Errorf("failed to make request format: %+v", err)
+	}
+
+	//check the payload fits
+	facts := me.Facts.Stringify()
+	msgPayload := facts + message + terminator
+	msgPayloadBytes := []byte(msgPayload)
+
+	if len(msgPayloadBytes) > requestFmt.MsgPayloadLen() {
+		return errors.Errorf("Combined message longer than space "+
+			"available in payload; available: %v, length: %v",
+			requestFmt.MsgPayloadLen(), len(msgPayloadBytes))
+	}
+
+	/*cryptographic generation*/
+	//generate salt
+	salt := make([]byte, saltSize)
+	_, err = rng.Read(salt)
+	if err != nil {
+		return errors.Wrap(err, "Failed to generate salt")
+	}
+
+	//generate ownership proof
+	ownership := cAuth.MakeOwnershipProof(storage.E2e().GetDHPrivateKey(),
+		partner.DhPubKey, storage.E2e().GetGroup())
+
+	//generate new keypair
+	newPrivKey := diffieHellman.GeneratePrivateKey(256, grp, rng)
+	newPubKey := diffieHellman.GeneratePublicKey(newPrivKey, grp)
+
+	jww.TRACE.Printf("RequestAuth MYPUBKEY: %v", newPubKey.Bytes())
+	jww.TRACE.Printf("RequestAuth THEIRPUBKEY: %v", partner.DhPubKey.Bytes())
+
+	/*encrypt payload*/
+	requestFmt.SetID(storage.GetUser().ReceptionID)
+	requestFmt.SetMsgPayload(msgPayloadBytes)
+	ecrFmt.SetOwnership(ownership)
+	ecrPayload, mac := cAuth.Encrypt(newPrivKey, partner.DhPubKey,
+		salt, ecrFmt.data, grp)
+	confirmFp := cAuth.MakeOwnershipProofFP(ownership)
+	requestfp := cAuth.MakeRequestFingerprint(partner.DhPubKey)
+
+	/*construct message*/
+	baseFmt.SetEcrPayload(ecrPayload)
+	baseFmt.SetSalt(salt)
+	baseFmt.SetPubKey(newPubKey)
+
+	cmixMsg.SetKeyFP(requestfp)
+	cmixMsg.SetMac(mac)
+	cmixMsg.SetContents(baseFmt.Marshal())
+
+	/*store state*/
+	//fixme: channel is bricked if the first store succedes but the second fails
+	//store the in progress auth
+	err = storage.Auth().AddSent(partner.ID, partner.DhPubKey, newPrivKey,
+		newPrivKey, confirmFp)
+	if err != nil {
+		return errors.Errorf("Failed to store auth request: %s", err)
+	}
+
+	//store the message as a critical message so it will always be sent
+	storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID)
+
+	jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s",
+		partner.ID, cmixMsg.Digest())
+
+	/*send message*/
+	round, _, err := net.SendCMIX(cmixMsg, partner.ID, params.GetDefaultCMIX())
+	if err != nil {
+		// if the send fails just set it to failed, it will but automatically
+		// retried
+		jww.INFO.Printf("Auth Request with %s (msgDigest: %s) failed "+
+			"to transmit: %+v", partner.ID, cmixMsg.Digest(), err)
+		storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID)
+		return errors.WithMessage(err, "Auth Request Failed to transmit")
+	}
+
+	jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent on round %d",
+		partner.ID, cmixMsg.Digest(), round)
+
+	/*check message delivery*/
+	sendResults := make(chan ds.EventReturn, 1)
+	roundEvents := net.GetInstance().GetRoundEvents()
+
+	roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute,
+		states.COMPLETED, states.FAILED)
+
+	success, numFailed, _ := utility.TrackResults(sendResults, 1)
+	if !success {
+		if numFailed > 0 {
+			jww.INFO.Printf("Auth Request with %s (msgDigest: %s) failed "+
+				"delivery due to round failure, will retry on reconnect",
+				partner.ID, cmixMsg.Digest())
+		} else {
+			jww.INFO.Printf("Auth Request with %s (msgDigest: %s) failed "+
+				"delivery due to timeout, will retry on reconnect",
+				partner.ID, cmixMsg.Digest())
+		}
+		storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID)
+	} else {
+		jww.INFO.Printf("Auth Request with %s (msgDigest: %s) delivered "+
+			"sucesfully", partner.ID, cmixMsg.Digest())
+		storage.GetCriticalRawMessages().Succeeded(cmixMsg, partner.ID)
+	}
+
+	return nil
+}
diff --git a/auth/verify.go b/auth/verify.go
new file mode 100644
index 0000000000000000000000000000000000000000..76850a17dfc6728c10b5c7304e04d7f2437f2545
--- /dev/null
+++ b/auth/verify.go
@@ -0,0 +1,20 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/storage"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+)
+
+func VerifyOwnership(received, verified contact.Contact, storage *storage.Session) bool {
+	myHistoricalPrivKey := storage.E2e().GetDHPrivateKey()
+	return cAuth.VerifyOwnershipProof(myHistoricalPrivKey, verified.DhPubKey,
+		storage.E2e().GetGroup(), received.OwnershipProof)
+}
diff --git a/bindings/README.md b/bindings/README.md
index fa8554e5ed47c313d89c50d100de7ca9e5eb684e..6aebd389abc87365d4a9c003c8e4fbdfea0f3658 100644
--- a/bindings/README.md
+++ b/bindings/README.md
@@ -1,185 +1,8 @@
-# Supporting Elixxir payments
-
-### Protocol buffer crash course for Elixxir
-
-#### Why use protocol buffers?
-
-Protocol buffer definitions (written in a .proto file) can generate code for serializing and deserializing byte sequences in a huge variety of languages. This means that, if you decide to standardize your message format with a protocol buffer declaration, you can decode and encode the messages in any language without having to interact with the Go client library at all.  Just as important is the ability to define enumerations in protocol buffers. In particular, the message types that are currently used for the CUI and the command-line client are defined in the Types enum in `client/cmixproto/types.proto`. If you have any questions about the way that the data are serialized for any message type, you should read this file and its comments.
-
-#### Generating protocol buffer code
-
-To generate the code, use the `protoc` tool, requesting output in the target language of your choice. For Go:
-
-`protoc --go_out=. types.proto`
-
-For Java:
-
-`protoc --java_out=/path/to/android/project/source/code types.proto`
-
-You can download and install the protocol buffer compiler for your preferred language from [its downloads page](https://developers.google.com/protocol-buffers/docs/downloads).
-
-#### Message types used to implement payments UI
-
-The payments portion of the user interface should only need to register its listeners with the wallet. To get the wallet (currently there's only one), call `Bindings.getActiveWallet()`. You can listen to the wallet with `Bindings.getActiveWallet().Listen(...)`. When the client has received a message that the UI needs to respond to, it should listen to the wallet for messages of these types: `PAYMENT_INVOICE_UI`, `PAYMENT_RESPONSE`, and `PAYMENT_RECEIPT_UI`. See cmixproto/types.proto for some documentation about these file formats.
-
-### What must client implementers do for the minimal payment implementation?
-
-There are three parties that must participate to complete a payment: the payer, the payee, and the payment bots. Payer and payee are both normal, human users using some Elixxir client program, and the payment bots are automatic users that arbitrate exchanges of which tokens have value.
-A payment happens like this: payee sends invoice with some payee-owned tokens that don't yet have value, payer receives invoice and decides to pay it, payer sends payment message to bots with payee-owned tokens and some tokens of his own that are worth the same and have value, bots locally see that the payer's tokens have value, bots store value in the payee's new tokens and destroy the payer's old tokens, bots reply to the payer with a message saying that the payment was successful, payer responds to the payee with a message asserting that they made the payment.
-
-Under the hood, the client library updates a lot of state to perform the necessary cryptography to update the wallet to its correct state. At the time, though, the mechanism for rolling back failed transactions is relatively untested and needs some ironing out. In the meantime, if a transaction fails and further transactions are messed up, you should restart the whole server infrastructure--server, gateway, and payment bot--to reset the tokens that are available on the payment bot, and wipe the stored sessions of the clients to register with a fresh set of tokens.
-
-In short, if you're implementing a client, your client must be able to do the following things to deliver the baseline payments experience:
-
-- send an invoice to another user on the payee's client
-- receive and display an incoming invoice on the payer's client
-- pay the received invoice on the payer's client
-- receive and display the payment bots' response on the payer's client
-- receive and display the payer's receipt on the payee's client
-
-How to do each of these things with the current payments API follows. Assume that `w` is a reference or pointer to the active wallet. Assume that `CmixProto` is an imported package with proto buffer code generated in Java. The code is written in Java-like pseudocode. Of course, you should structure your own code in the best way for your own application.
-
-#### 0. Actually having tokens to spend
-
-To actually have tokens to spend, you must mint the same tokens as are on the payment bot. Currently, the tokens are hard-coded. To do this, pass `true` to the last parameter of `Bindings.Register()`. Then, there will be tokens that are stored in the wallet that happen to be the same as the tokens that are stored on the payment bot (when the payment bot is run with `--mint`), and the client will be able to spend them.
-
-#### 1. Send an invoice to another user on the payee's client
-
-First, generate the invoice, then send it.
-
-```java
-public static void sendInvoice() throws Throwable {
-    // Generate the invoice message: Request 500 tokens from the payer
-    Message invoiceMessage = w.Invoice(payerId.bytes(), 500,
-        "for creating a completely new flavor of ice cream");
-
-    // Send the invoice message to the payer
-    Bindings.send(invoiceMessage); 
-}
-```
-
-#### 2. Receive and display an incoming invoice on the payer's client
-
-During client startup, register a listener with the wallet. The wallet has a separate listener matching structure from the main switchboard, and you can use it to receive messages from the wallet rather than from the network.
-
-```java
-public static void setup() throws Throwable {
-    Bindings.InitClient(...);
-    Bindings.Register(...);
-
-    // The wallet and listener data structure (switchboard) are both ready after Register.
-    // On the other hand, the client begins receiving messages after Login. So, if
-    // you want to receive all messages that the client receives, it's best to register
-    // listeners between Register and Login.
-    registerListeners();
-
-    Bindings.Login(...);
-}
-
-public static void registerListeners() {
-    // Listen for messages of type PAYMENT_INVOICE_UI originating from all users
-    w.Listen(zeroId.bytes(), CmixProto.Type.PAYMENT_INVOICE_UI, new PaymentInvoiceListener());
-    // and so on...
-}
-```
-
-The message you'll get contains an invoice ID. You can use it to query the invoice's transaction for display. You should also store the invoice ID as it's the parameter for the Pay method, the next phase.
-
-```java
-public class PaymentInvoiceListener implements Bindings.Listener {
-    @Override
-    public void Hear(Bindings.Message msg, bool isHeardElsewhere) {
-        // Keep this ID around somewhere for the next step
-        byte[] invoiceID = msg.GetPayload();
-        bindings.Transaction invoice = w.GetInboundRequests.Get(invoiceID);
-
-        // Display the transaction somehow
-        invoiceDisplay.setTime(invoice.timestamp);
-        invoiceDisplay.setValue(invoice.value);
-        invoiceDisplay.setMemo(invoice.memo);
-        invoiceDisplay.show();
-    }
-}
-```
-
-#### 3. Pay the received invoice on the payer's client
-
-When the payer approves the invoice's payment, call the Pay() method with the invoice ID. This will generate a message that the payer can send to the payment bot. The message contains the payee's tokens, which the payment bot will vest, and the payer's proof of ownership of tokens of equal value, which the payment will destroy to facilitate the exchange.
-
-```java
-public static void sendPayment() throws Throwable {
-    Message msg = Bindings.pay(invoiceID);
-    Bindings.send(msg);
-}
-```
-
-#### 4. Receive and display the payment bots' response on the payer's client
-
-When you register listeners, listen to the wallet for the type `PAYMENT_RESPONSE`.
-
-```java
-public static void registerListeners() {
-    // ...
-    w.Listen(zeroId.bytes(), CmixProto.Type.PAYMENT_RESPONSE, new PaymentResponseListener());
-    // ...
-}
-```
-
-The payment response is a serialized protocol buffer with a few fields. You should deserialize it using the protocol buffer code you generated.
-
-```java
-public class PaymentResponseListener implements Binding.Listener {
-    @Override
-    public void Hear(Bindings.Message msg, bool isHeardElsewhere) {
-        // Parse the payment bot's response
-        PaymentResponse response = PaymentResponse.parseFrom(msg.GetPayload());
-
-        // Then, show it to the user somehow
-        responseDisplay.setSuccess(response.getSuccess());
-        responseDisplay.setText(response.getResponse());
-        responseDisplay.show();
-    }
-}
-```
-
-The client automatically converts the payment bot's response to a receipt and sends it on to the payee.
-
-#### 5. Receive and display the payer's receipt on the payee's client
-
-When you register listeners, listen to the wallet for the type `PAYMENT_RECEIPT_UI`.
-
-```java
-public static void registerListeners() {
-    // ...
-    w.Listen(zeroId.bytes(), CmixProto.Type.PAYMENT_RECEIPT_UI, new PaymentReceiptListener());
-    // ...
-}
-```
-
-The payment receipt UI message is the ID of the original invoice that was paid. You can get the transaction itself from the CompletedOutboundPayments transaction list, then display the transaction information of the completed payment.
-
-```java
-public class PaymentReceiptListener implements Bindings.Listener {
-    @Override
-    public void Hear(bindings.Message msg, bool isHeardElsewhere) {
-        // Get the relevant transaction
-        bindings.Transaction completedTransaction = w.getCompletedOutboundTransaction(msg.GetPayload());
-
-        // Show the receipt, including the time that the original invoice was sent.
-        receiptDisplay.setTime(completedTransaction.time);
-        receiptDisplay.setMemo(completedTransaction.memo);
-        receiptDisplay.setValue(completedTransaction.value);
-        receiptDisplay.show();
-    }
-}
-```
-
-### Cyclic group for registration JSON format: 
-
-```json
-{
-  "gen": "5c7ff6b06f8f143fe8288433493e4769c4d988ace5be25a0e24809670716c613d7b0cee6932f8faa7c44d2cb24523da53fbe4f6ec3595892d1aa58c4328a06c46a15662e7eaa703a1decf8bbb2d05dbe2eb956c142a338661d10461c0d135472085057f3494309ffa73c611f78b32adbb5740c361c9f35be90997db2014e2ef5aa61782f52abeb8bd6432c4dd097bc5423b285dafb60dc364e8161f4a2a35aca3a10b1c4d203cc76a470a33afdcbdd92959859abd8b56e1725252d78eac66e71ba9ae3f1dd2487199874393cd4d832186800654760e1e34c09e4d155179f9ec0dc4473f996bdce6eed1cabed8b6f116f7ad9cf505df0f998e34ab27514b0ffe7",
-  "prime": "9db6fb5951b66bb6fe1e140f1d2ce5502374161fd6538df1648218642f0b5c48c8f7a41aadfa187324b87674fa1822b00f1ecf8136943d7c55757264e5a1a44ffe012e9936e00c1d3e9310b01c7d179805d3058b2a9f4bb6f9716bfe6117c6b5b3cc4d9be341104ad4a80ad6c94e005f4b993e14f091eb51743bf33050c38de235567e1b34c3d6a5c0ceaa1a0f368213c3d19843d0b4b09dcb9fc72d39c8de41f1bf14d4bb4563ca28371621cad3324b6a2d392145bebfac748805236f5ca2fe92b871cd8f9c36d3292b5509ca8caa77a2adfc7bfd77dda6f71125a7456fea153e433256a2261c6a06ed3693797e7995fad5aabbcfbe3eda2741e375404ae25b",
-  "primeQ": "f2c3119374ce76c9356990b465374a17f23f9ed35089bd969f61c6dde9998c1f"
-}
-```
+# Client Bindings
+
+"bindings" is the client bindings which can be used to generate Android
+and iOS client libraries for apps using gomobile. Gomobile is
+limited to int, string, []byte, interfaces, and only a couple other types, so
+it is necessary to define several interfaces to support passing more complex
+data across the boundary (see `interfaces.go`). The rest of the logic
+is located in `api.go`
diff --git a/bindings/authenticatedChannels.go b/bindings/authenticatedChannels.go
new file mode 100644
index 0000000000000000000000000000000000000000..70ff0285173740f5ef302e1a678ffe127318a002
--- /dev/null
+++ b/bindings/authenticatedChannels.go
@@ -0,0 +1,115 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"errors"
+	"fmt"
+	"gitlab.com/elixxir/client/interfaces/contact"
+)
+
+// Create an insecure e2e relationship with a precanned user
+func (c *Client) MakePrecannedAuthenticatedChannel(precannedID int) (*Contact, error) {
+	precannedContact, err := c.api.MakePrecannedAuthenticatedChannel(uint(precannedID))
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to "+
+			"MakePrecannedAuthenticatedChannel: %+v", err))
+	}
+	return &Contact{c: &precannedContact}, nil
+}
+
+// RequestAuthenticatedChannel sends a request to another party to establish an
+// authenticated channel
+// It will not run if the network status is not healthy
+// An error will be returned if a channel already exists, if a request was
+// already received, or if a request was already sent
+// When a confirmation occurs, the channel will be created and the callback
+// will be called
+//
+// This function takes the marshaled send report to ensure a memory leak does
+// not occur as a result of both sides of the bindings holding a refrence to
+// the same pointer.
+func (c *Client) RequestAuthenticatedChannel(recipientMarshaled,
+	meMarshaled []byte, message string) error {
+	recipent, err := contact.Unmarshal(recipientMarshaled)
+
+	if err != nil {
+		return errors.New(fmt.Sprintf("Failed to "+
+			"RequestAuthenticatedChannel: Failed to Unmarshal Recipent: "+
+			"%+v", err))
+	}
+
+	me, err := contact.Unmarshal(meMarshaled)
+
+	if err != nil {
+		return errors.New(fmt.Sprintf("Failed to "+
+			"RequestAuthenticatedChannel: Failed to Unmarshal Me: %+v", err))
+	}
+
+	return c.api.RequestAuthenticatedChannel(recipent, me, message)
+}
+
+// RegisterAuthCallbacks registers both callbacks for authenticated channels.
+// This can only be called once
+func (c *Client) RegisterAuthCallbacks(request AuthRequestCallback,
+	confirm AuthConfirmCallback) {
+
+	requestFunc := func(requestor contact.Contact, message string) {
+		requestorBind := &Contact{c: &requestor}
+		request.Callback(requestorBind, message)
+	}
+
+	confirmFunc := func(partner contact.Contact) {
+		partnerBind := &Contact{c: &partner}
+		confirm.Callback(partnerBind)
+	}
+
+	c.api.GetAuthRegistrar().AddGeneralConfirmCallback(confirmFunc)
+	c.api.GetAuthRegistrar().AddGeneralRequestCallback(requestFunc)
+
+	return
+}
+
+// ConfirmAuthenticatedChannel creates an authenticated channel out of a valid
+// received request and sends a message to the requestor that the request has
+// been confirmed
+// It will not run if the network status is not healthy
+// An error will be returned if a channel already exists, if a request doest
+// exist, or if the passed in contact does not exactly match the received
+// request
+func (c *Client) ConfirmAuthenticatedChannel(recipientMarshaled []byte) error {
+	recipent, err := contact.Unmarshal(recipientMarshaled)
+
+	if err != nil {
+		return errors.New(fmt.Sprintf("Failed to "+
+			"ConfirmAuthenticatedChannel: Failed to Unmarshal Recipient: "+
+			"%+v", err))
+	}
+
+	return c.api.ConfirmAuthenticatedChannel(recipent)
+}
+
+// VerifyOwnership checks if the ownership proof on a passed contact matches the
+// identity in a verified contact
+func (c *Client) VerifyOwnership(receivedMarshaled, verifiedMarshaled []byte) (bool, error) {
+	received, err := contact.Unmarshal(receivedMarshaled)
+
+	if err != nil {
+		return false, errors.New(fmt.Sprintf("Failed to "+
+			"VerifyOwnership: Failed to Unmarshal Received: %+v", err))
+	}
+
+	verified, err := contact.Unmarshal(verifiedMarshaled)
+
+	if err != nil {
+		return false, errors.New(fmt.Sprintf("Failed to "+
+			"VerifyOwnership: Failed to Unmarshal Verified: %+v", err))
+	}
+
+	return c.api.VerifyOwnership(received, verified), nil
+}
diff --git a/bindings/callback.go b/bindings/callback.go
new file mode 100644
index 0000000000000000000000000000000000000000..1237e7fcf981f763fcf8dcb3be233d37e48741fd
--- /dev/null
+++ b/bindings/callback.go
@@ -0,0 +1,98 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// Listener provides a callback to hear a message
+// An object implementing this interface can be called back when the client
+// gets a message of the type that the regi    sterer specified at registration
+// time.
+type Listener interface {
+	// Hear is called to receive a message in the UI
+	Hear(message *Message)
+	// Returns a name, used for debugging
+	Name() string
+}
+
+// A callback when which is used to receive notification if network health
+// changes
+type NetworkHealthCallback interface {
+	Callback(bool)
+}
+
+// RoundEventHandler handles round events happening on the cMix network.
+type RoundEventCallback interface {
+	EventCallback(rid, state int, timedOut bool)
+}
+
+// RoundEventHandler handles round events happening on the cMix network.
+type MessageDeliveryCallback interface {
+	EventCallback(msgID []byte, delivered, timedOut bool, roundResults []byte)
+}
+
+// AuthRequestCallback notifies the register whenever they receive an auth
+// request
+type AuthRequestCallback interface {
+	Callback(requestor *Contact, message string)
+}
+
+// AuthConfirmCallback notifies the register whenever they receive an auth
+// request confirmation
+type AuthConfirmCallback interface {
+	Callback(partner *Contact)
+}
+
+// Generic Unregister - a generic return used for all callbacks which can be
+// unregistered
+// Interface which allows the un-registration of a listener
+type Unregister struct {
+	f func()
+}
+
+//Call unregisters a callback
+func (u *Unregister) Unregister() {
+	u.f()
+}
+
+//creates an unregister interface for listeners
+func newListenerUnregister(lid switchboard.ListenerID, sw interfaces.Switchboard) *Unregister {
+	f := func() {
+		sw.Unregister(lid)
+	}
+	return &Unregister{f: f}
+}
+
+//creates an unregister interface for round events
+func newRoundUnregister(rid id.Round, ec *dataStructures.EventCallback,
+	re interfaces.RoundEvents) *Unregister {
+	f := func() {
+		re.Remove(rid, ec)
+	}
+	return &Unregister{f: f}
+}
+
+//creates an unregister interface for round events
+func newRoundListUnregister(rounds []id.Round, ec []*dataStructures.EventCallback,
+	re interfaces.RoundEvents) *Unregister {
+	f := func() {
+		for i, r := range rounds {
+			re.Remove(r, ec[i])
+		}
+	}
+	return &Unregister{f: f}
+}
+
+type ClientError interface {
+	Report(source, message, trace string)
+}
diff --git a/bindings/client.go b/bindings/client.go
index 480a55fe8ec810be1e112cb91265b90f8e99d13f..04e2eba6e55645dfe1cd9f7fb7a14378dde5486d 100644
--- a/bindings/client.go
+++ b/bindings/client.go
@@ -1,483 +1,375 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
 
 package bindings
 
 import (
-	"crypto/rand"
+	"encoding/json"
 	"errors"
-	"github.com/spf13/jwalterweatherman"
+	"fmt"
+	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/api"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/crypto/csprng"
-	"gitlab.com/elixxir/primitives/id"
-	"io"
-	"math/big"
-	"strings"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
-type Client struct {
-	client         *api.Client
-	statusCallback ConnectionStatusCallback
-}
-
-// Returns listener handle as a string.
-// You can use it to delete the listener later.
-// Please ensure userId has the correct length (256 bits)
-// User IDs are informally big endian. If you want compatibility with the demo
-// user names, set the last byte and leave all other bytes zero for userId.
-// If you pass the zero user ID (256 bits of zeroes) to Listen() you will hear
-// messages sent from all users.
-// If you pass the zero type (just zero) to Listen() you will hear messages of
-// all types.
-func (cl *Client) Listen(userId []byte, messageType int32, newListener Listener) (string, error) {
-	typedUserId, err := id.Unmarshal(userId)
-	if err != nil {
-		return "", err
-	}
-
-	listener := &listenerProxy{proxy: newListener}
-
-	return cl.client.Listen(typedUserId, messageType, listener), nil
+// sets the log level
+func init() {
+	jww.SetLogThreshold(jww.LevelInfo)
+	jww.SetStdoutThreshold(jww.LevelInfo)
 }
 
-// Pass the listener handle that Listen() returned to delete the listener
-func (cl *Client) StopListening(listenerHandle string) {
-	cl.client.StopListening(listenerHandle)
-}
-
-func FormatTextMessage(message string) []byte {
-	return api.FormatTextMessage(message)
+// BindingsClient wraps the api.Client, implementing additional functions
+// to support the gomobile Client interface
+type Client struct {
+	api api.Client
 }
 
-// Initializes the client by registering a storage mechanism and a reception
-// callback.
-// For the mobile interface, one must be provided
-// The loc can be empty, it is only necessary if the passed storage interface
-// requires it to be passed via "SetLocation"
-//
-// Parameters: storage implements Storage.
-// Implement this interface to store the user session data locally.
-// You must give us something for this parameter.
+// NewClient creates client storage, generates keys, connects, and registers
+// with the network. Note that this does not register a username/identity, but
+// merely creates a new cryptographic identity for adding such information
+// at a later date.
 //
-// loc is a string. If you're using DefaultStorage for your storage,
-// this would be the filename of the file that you're storing the user
-// session in.
-func NewClient(storage Storage, locA, locB string, ndfStr, ndfPubKey string) (*Client, error) {
-	globals.Log.INFO.Printf("Binding call: NewClient()")
-	if storage == nil {
-		return nil, errors.New("could not init client: Storage was nil")
-	}
-
-	ndf := api.VerifyNDF(ndfStr, ndfPubKey)
-
-	proxy := &storageProxy{boundStorage: storage}
-
-	cl, err := api.NewClient(globals.Storage(proxy), locA, locB, ndf)
-
-	return &Client{client: cl}, err
-}
-
-func NewClient_deprecated(storage Storage, locA, locB string, ndfStr, ndfPubKey string,
-	csc ConnectionStatusCallback) (*Client, error) {
-
-	return &Client{client: nil, statusCallback: csc}, nil
-}
-
-func (cl *Client) EnableDebugLogs() {
-	globals.Log.INFO.Printf("Binding call: EnableDebugLogs()")
-	globals.Log.SetStdoutThreshold(jwalterweatherman.LevelDebug)
-	globals.Log.SetLogThreshold(jwalterweatherman.LevelDebug)
-}
-
-// Connects to gateways and registration server (if needed)
-// using tls filepaths to create credential information
-// for connection establishment
-func (cl *Client) InitNetwork() error {
-	globals.Log.INFO.Printf("Binding call: InitNetwork()")
-	return cl.client.InitNetwork()
-}
-
-// Sets a callback which receives a strings describing the current status of
-// Registration or UDB Registration, or UDB Search
-func (cl *Client) SetOperationProgressCallback(rpcFace OperationProgressCallback) {
-	rpc := func(i int) {
-		rpcFace.Callback(i)
+// Users of this function should delete the storage directory on error.
+func NewClient(network, storageDir string, password []byte, regCode string) error {
+	if err := api.NewClient(network, storageDir, password, regCode); err != nil {
+		return errors.New(fmt.Sprintf("Failed to create new client: %+v",
+			err))
 	}
-	cl.client.SetOperationProgressCallback(rpc)
-}
-
-// Generate Keys generates the user identity for the network and stores it
-func (cl *Client) GenerateKeys(password string) error {
-
-	globals.Log.INFO.Printf("Binding call: GenerateKeys()\n" +
-		"  Password: ********")
-	return cl.client.GenerateKeys(nil, password)
-}
-
-// Registers user and returns the User ID bytes.
-// Returns null if registration fails and error
-// If preCan set to true, registration is attempted assuming a pre canned user
-// registrationCode is a one time use string
-// registrationAddr is the address of the registration server
-// gwAddressesList is CSV of gateway addresses
-// grp is the CMIX group needed for keys generation in JSON string format
-func (cl *Client) RegisterWithPermissioning(preCan bool, registrationCode string) ([]byte, error) {
-
-	globals.Log.INFO.Printf("Binding call: RegisterWithPermissioning()\n"+
-		"   preCan: %v\n   registrationCode: %s\n  "+
-		"   Password: ********", preCan, registrationCode)
-	UID, err := cl.client.RegisterWithPermissioning(preCan, registrationCode)
-
-	if err != nil {
-		return id.ZeroUser[:], err
-	}
-
-	return UID[:], nil
-}
-
-// Registers user with all nodes it has not been registered with.
-// Returns error if registration fails
-func (cl *Client) RegisterWithNodes() error {
-	globals.Log.INFO.Printf("Binding call: RegisterWithNodes()")
-	err := cl.client.RegisterWithNodes()
-	return err
-}
-
-// Register with UDB uses the account's email to register with the UDB for
-// User discovery.  Must be called after Register and InitNetwork.
-// It will fail if the user has already registered with UDB
-func (cl *Client) RegisterWithUDB(username string, timeoutMS int) error {
-	globals.Log.INFO.Printf("Binding call: RegisterWithUDB()\n")
-	return cl.client.RegisterWithUDB(username, time.Duration(timeoutMS)*time.Millisecond)
-}
-
-// Logs in the user based on User ID and returns the nickname of that user.
-// Returns an empty string and an error
-// UID is a uint64 BigEndian serialized into a byte slice
-func (cl *Client) Login(UID []byte, password string) ([]byte, error) {
-	globals.Log.INFO.Printf("Binding call: Login()\n"+
-		"   UID: %v\n   Password: ********", UID)
-
-	uid, err := cl.client.Login(password)
-
-	if uid == nil {
-		return make([]byte, 0), err
-	}
-
-	return (*uid)[:], err
-}
-
-func (cl *Client) GetUsername() string {
-	globals.Log.INFO.Printf("Binding call: GetUsername()")
-
-	return cl.client.GetSession().GetCurrentUser().Username
-}
-
-func (cl *Client) GetUserID() []byte {
-	globals.Log.INFO.Printf("Binding call: GetUserID()")
-
-	return cl.client.GetSession().GetCurrentUser().User[:]
-}
-
-type MessageReceiverCallback interface {
-	Callback(err error)
-}
-
-// Starts the polling of the external servers.
-// Must be done after listeners are set up.
-func (cl *Client) StartMessageReceiver(mrc MessageReceiverCallback) error {
-	globals.Log.INFO.Printf("Binding call: StartMessageReceiver()")
-	return cl.client.StartMessageReceiver(mrc.Callback)
+	return nil
 }
 
-func (cl *Client) StartMessageReceiver_deprecated() error {
-	receiverCallback := func(err error) {
-		cl.backoff(0)
+// NewPrecannedClient creates an insecure user with predetermined keys with nodes
+// It creates client storage, generates keys, connects, and registers
+// with the network. Note that this does not register a username/identity, but
+// merely creates a new cryptographic identity for adding such information
+// at a later date.
+//
+// Users of this function should delete the storage directory on error.
+func NewPrecannedClient(precannedID int, network, storageDir string, password []byte) error {
+	if precannedID < 0 {
+		return errors.New("Cannot create precanned client with negative ID")
 	}
 
-	err := cl.client.StartMessageReceiver(receiverCallback)
-	if err != nil {
-		return err
+	if err := api.NewPrecannedClient(uint(precannedID), network, storageDir, password); err != nil {
+		return errors.New(fmt.Sprintf("Failed to create new precanned "+
+			"client: %+v", err))
 	}
 	return nil
 }
 
-func (cl *Client) backoff(backoffCount int) {
-	receiverCallback := func(err error) {
-		cl.backoff(backoffCount + 1)
+// Login will load an existing client from the storageDir
+// using the password. This will fail if the client doesn't exist or
+// the password is incorrect.
+// The password is passed as a byte array so that it can be cleared from
+// memory and stored as securely as possible using the memguard library.
+// Login does not block on network connection, and instead loads and
+// starts subprocesses to perform network operations.
+func Login(storageDir string, password []byte, parameters string) (*Client, error) {
+	p, err := params.GetNetworkParameters(parameters)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to login: %+v", err))
 	}
 
-	// Compute backoff time
-	var delay time.Duration
-	var block = false
-	if backoffCount > 15 {
-		delay = time.Hour
-		block = true
+	client, err := api.Login(storageDir, password, p)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to login: %+v", err))
 	}
-	wait := 2 ^ backoffCount
-	if wait > 180 {
-		wait = 180
+	return &Client{*client}, nil
+}
+
+// sets level of logging. All logs the set level and above will be displayed
+// options are:
+//	TRACE		- 0
+//	DEBUG		- 1
+//	INFO 		- 2
+//	WARN		- 3
+//	ERROR		- 4
+//	CRITICAL	- 5
+//	FATAL		- 6
+// The default state without updates is: INFO
+func LogLevel(level int) error {
+	if level < 0 || level > 6 {
+		return errors.New(fmt.Sprintf("log level is not valid: log level: %d", level))
 	}
-	jitter, _ := rand.Int(csprng.NewSystemRNG(), big.NewInt(1000))
-	delay = time.Second*time.Duration(wait) + time.Millisecond*time.Duration(jitter.Int64())
-
-	cl.statusCallback.Callback(0, int(delay.Seconds()))
 
-	// Start timer, or stop if max attempts reached
-	timer := time.NewTimer(delay)
-	if block {
-		timer.Stop()
+	threshold := jww.Threshold(level)
+	jww.SetLogThreshold(threshold)
+	jww.SetStdoutThreshold(threshold)
+
+	switch threshold {
+	case jww.LevelTrace:
+		fallthrough
+	case jww.LevelDebug:
+		fallthrough
+	case jww.LevelInfo:
+		jww.INFO.Printf("Log level set to: %s", threshold)
+	case jww.LevelWarn:
+		jww.WARN.Printf("Log level set to: %s", threshold)
+	case jww.LevelError:
+		jww.ERROR.Printf("Log level set to: %s", threshold)
+	case jww.LevelCritical:
+		jww.CRITICAL.Printf("Log level set to: %s", threshold)
+	case jww.LevelFatal:
+		jww.FATAL.Printf("Log level set to: %s", threshold)
 	}
 
-	select {
-	case <-timer.C:
-		backoffCount = 0
-	}
+	return nil
+}
 
-	// attempt to start the message receiver
-	cl.statusCallback.Callback(1, 0)
-	err := cl.client.StartMessageReceiver(receiverCallback)
+//Unmarshals a marshaled contact object, returns an error if it fails
+func UnmarshalContact(b []byte) (*Contact, error) {
+	c, err := contact.Unmarshal(b)
 	if err != nil {
-		cl.statusCallback.Callback(0, 0)
+		return nil, errors.New(fmt.Sprintf("Failed to Unmarshal "+
+			"Contact: %+v", err))
 	}
-	cl.statusCallback.Callback(2, 0)
-}
-
-// Overwrites the username in registration. Only succeeds if the client
-// has registered with permissioning but not UDB
-func (cl *Client) ChangeUsername(un string) error {
-	globals.Log.INFO.Printf("Binding call: ChangeUsername()\n"+
-		"   username: %s", un)
-	return cl.client.GetSession().ChangeUsername(un)
-}
-
-// gets the curent registration status.  they cane be:
-//  0 - NotStarted
-//	1 - PermissioningComplete
-//	2 - UDBComplete
-func (cl *Client) GetRegState() int64 {
-	globals.Log.INFO.Printf("Binding call: GetRegState()")
-	return int64(cl.client.GetSession().GetRegState())
-}
-
-// Registers user with all nodes it has not been registered with.
-// Returns error if registration fails
-func (cl *Client) StorageIsEmpty() bool {
-	globals.Log.INFO.Printf("Binding call: StorageIsEmpty()")
-	return cl.client.GetSession().StorageIsEmpty()
+	return &Contact{c: &c}, nil
 }
 
-// Sends a message structured via the message interface
-// Automatically serializes the message type before the rest of the payload
-// Returns an error if either sender or recipient are too short
-// the encrypt bool tell the client if it should send and e2e encrypted message
-// or not.  If true, and there is no keying relationship with the user specified
-// in the message object, then it will return an error.  If using precanned
-// users encryption must be set to false.
-func (cl *Client) Send(m Message, encrypt bool) (int64, error) {
-	globals.Log.INFO.Printf("Binding call: Send()\n"+
-		"Sender: %v\n"+
-		"Payload: %v\n"+
-		"Recipient: %v\n"+
-		"MessageTye: %v", m.GetSender(), m.GetPayload(),
-		m.GetRecipient(), m.GetMessageType())
-
-	sender, err := id.Unmarshal(m.GetSender())
-	if err != nil {
-		return 0, err
+//Unmarshals a marshaled send report object, returns an error if it fails
+func UnmarshalSendReport(b []byte) (*SendReport, error) {
+	sr := &SendReport{}
+	if err := json.Unmarshal(b, sr); err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to Unmarshal "+
+			"Send Report: %+v", err))
 	}
-	recipient, err := id.Unmarshal(m.GetRecipient())
+	return sr, nil
+}
+
+// StartNetworkFollower kicks off the tracking of the network. It starts
+// long running network client threads and returns an object for checking
+// state and stopping those threads.
+// Call this when returning from sleep and close when going back to
+// sleep.
+// These threads may become a significant drain on battery when offline, ensure
+// they are stopped if there is no internet access
+// Threads Started:
+//   - Network Follower (/network/follow.go)
+//   	tracks the network events and hands them off to workers for handling
+//   - Historical Round Retrieval (/network/rounds/historical.go)
+//		Retrieves data about rounds which are too old to be stored by the client
+//	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
+//		Requests all messages in a given round from the gateway of the last node
+//	 - Message Handling Worker Group (/network/message/handle.go)
+//		Decrypts and partitions messages when signals via the Switchboard
+//	 - Health Tracker (/network/health)
+//		Via the network instance tracks the state of the network
+//	 - Garbled Messages (/network/message/garbled.go)
+//		Can be signaled to check all recent messages which could be be decoded
+//		Uses a message store on disk for persistence
+//	 - Critical Messages (/network/message/critical.go)
+//		Ensures all protocol layer mandatory messages are sent
+//		Uses a message store on disk for persistence
+//	 - KeyExchange Trigger (/keyExchange/trigger.go)
+//		Responds to sent rekeys and executes them
+//   - KeyExchange Confirm (/keyExchange/confirm.go)
+//		Responds to confirmations of successful rekey operations
+func (c *Client) StartNetworkFollower(clientError ClientError) error {
+	errChan, err := c.api.StartNetworkFollower()
 	if err != nil {
-		return 0, err
+		return errors.New(fmt.Sprintf("Failed to start the "+
+			"network follower: %+v", err))
 	}
 
-	var cryptoType parse.CryptoType
-	if encrypt {
-		cryptoType = parse.E2E
-	} else {
-		cryptoType = parse.Unencrypted
-	}
-
-	return time.Now().UnixNano(), cl.client.Send(&parse.Message{
-		TypedBody: parse.TypedBody{
-			MessageType: m.GetMessageType(),
-			Body:        m.GetPayload(),
-		},
-		InferredType: cryptoType,
-		Sender:       sender,
-		Receiver:     recipient,
-	})
-}
-
-//a version of the send function which does not return a timestamp for use
-//on iOS
-func (cl *Client) SendNoTimestamp(m Message, encrypt bool) error {
-	_, err := cl.Send(m, encrypt)
-	return err
-}
-
-// Logs the user out, saving the state for the system and clearing all data
-// from RAM
-func (cl *Client) Logout() error {
-	globals.Log.INFO.Printf("Binding call: Logout()\n")
-	return cl.client.Logout(500 * time.Millisecond)
+	go func() {
+		for report := range errChan {
+			go clientError.Report(report.Source, report.Message, report.Trace)
+		}
+	}()
+	return nil
 }
 
-// Get the version string from the locally built client repository
-func GetLocalVersion() string {
-	globals.Log.INFO.Printf("Binding call: GetLocalVersion()\n")
-	return api.GetLocalVersion()
+// StopNetworkFollower stops the network follower if it is running.
+// It returns errors if the Follower is in the wrong status to stop or if it
+// fails to stop it.
+// if the network follower is running and this fails, the client object will
+// most likely be in an unrecoverable state and need to be trashed.
+func (c *Client) StopNetworkFollower(timeoutMS int) error {
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	if err := c.api.StopNetworkFollower(timeout); err != nil {
+		return errors.New(fmt.Sprintf("Failed to stop the "+
+			"network follower: %+v", err))
+	}
+	return nil
 }
 
-// Get the version string from the registration server
-// You need to connect to gateways for this to be populated.
-// For the client to function, the local version must be compatible with this
-// version. If that's not the case, check out the git tag corresponding to the
-// client release version returned here.
-func (cl *Client) GetRemoteVersion() string {
-	globals.Log.INFO.Printf("Binding call: GetRemoteVersion()\n")
-	return cl.GetRemoteVersion()
+// Gets the state of the network follower. Returns:
+// Stopped 	- 0
+// Starting - 1000
+// Running	- 2000
+// Stopping	- 3000
+func (c *Client) NetworkFollowerStatus() int {
+	return int(c.api.NetworkFollowerStatus())
 }
 
-// Turns off blocking transmission so multiple messages can be sent
-// simultaneously
-func (cl *Client) DisableBlockingTransmission() {
-	globals.Log.INFO.Printf("Binding call: DisableBlockingTransmission()\n")
-	cl.client.DisableBlockingTransmission()
+// returns true if the network is read to be in a healthy state where
+// messages can be sent
+func (c *Client) IsNetworkHealthy() bool {
+	return c.api.GetHealth().IsHealthy()
 }
 
-// Sets the minimum amount of time, in ms, between message transmissions
-// Just for testing, probably to be removed in production
-func (cl *Client) SetRateLimiting(limit int) {
-	globals.Log.INFO.Printf("Binding call: SetRateLimiting()\n"+
-		"   limit: %v", limit)
-	cl.client.SetRateLimiting(uint32(limit))
+// registers the network health callback to be called any time the network
+// health changes
+func (c *Client) RegisterNetworkHealthCB(nhc NetworkHealthCallback) {
+	c.api.GetHealth().AddFunc(nhc.Callback)
 }
 
-// SearchForUser searches for the user with the passed username.
-// returns state on the search callback.  A timeout in ms is required.
-// A recommended timeout is 2 minutes or 120000
-func (cl *Client) SearchForUser(username string,
-	cb SearchCallback, timeoutMS int) {
+// RegisterListener records and installs a listener for messages
+// matching specific uid, msgType, and/or username
+// Returns a ListenerUnregister interface which can be
+//
+// to register for any userID, pass in an id with length 0 or an id with
+// all zeroes
+//
+// to register for any message type, pass in a message type of 0
+//
+// Message Types can be found in client/interfaces/message/type.go
+// Make sure to not conflict with ANY default message types
+func (c *Client) RegisterListener(uid []byte, msgType int,
+	listener Listener) (*Unregister, error) {
+	jww.INFO.Printf("RegisterListener(%v, %d)", uid,
+		msgType)
+
+	name := listener.Name()
+
+	var u *id.ID
+	if len(uid) == 0 {
+		u = &id.ID{}
+	} else {
+		var err error
+		u, err = id.Unmarshal(uid)
+		if err != nil {
+			return nil, errors.New(fmt.Sprintf("Failed to "+
+				"ResgisterListener: %+v", err))
+		}
+	}
 
-	globals.Log.INFO.Printf("Binding call: SearchForUser()\n"+
-		"   username: %v\n"+
-		"   timeout: %v\n", username, timeoutMS)
+	mt := message.Type(msgType)
 
-	proxy := &searchCallbackProxy{cb}
-	cl.client.SearchForUser(username, proxy, time.Duration(timeoutMS)*time.Millisecond)
-}
+	f := func(item message.Receive) {
+		listener.Hear(&Message{r: item})
+	}
 
-// DeleteContact deletes the contact at the given userID.  returns the emails
-// of that contact if possible
-func (cl *Client) DeleteContact(uid []byte) (string, error) {
-	globals.Log.INFO.Printf("Binding call: DeleteContact()\n"+
-		"   uid: %v\n", uid)
-	u, err := id.Unmarshal(uid)
-	if err != nil {
-		return "", err
+	lid := c.api.GetSwitchboard().RegisterFunc(name, u, mt, f)
+
+	return newListenerUnregister(lid, c.api.GetSwitchboard()), nil
+}
+
+// RegisterRoundEventsHandler registers a callback interface for round
+// events.
+// The rid is the round the event attaches to
+// The timeoutMS is the number of milliseconds until the event fails, and the
+// validStates are a list of states (one per byte) on which the event gets
+// triggered
+// States:
+//  0x00 - PENDING (Never seen by client)
+//  0x01 - PRECOMPUTING
+//  0x02 - STANDBY
+//  0x03 - QUEUED
+//  0x04 - REALTIME
+//  0x05 - COMPLETED
+//  0x06 - FAILED
+// These states are defined in elixxir/primitives/states/state.go
+func (c *Client) RegisterRoundEventsHandler(rid int, cb RoundEventCallback,
+	timeoutMS int, il *IntList) *Unregister {
+
+	rcb := func(ri *mixmessages.RoundInfo, timedOut bool) {
+		cb.EventCallback(int(ri.ID), int(ri.State), timedOut)
 	}
 
-	return cl.client.DeleteUser(u)
-}
+	timeout := time.Duration(timeoutMS) * time.Millisecond
 
-// Nickname lookup API
-// Non-blocking, once the API call completes, the callback function
-// passed as argument is called
-func (cl *Client) LookupNick(user []byte,
-	cb NickLookupCallback) error {
-	proxy := &nickCallbackProxy{cb}
-	userID, err := id.Unmarshal(user)
-	if err != nil {
-		return err
+	vStates := make([]states.Round, len(il.lst))
+	for i, s := range il.lst {
+		vStates[i] = states.Round(s)
 	}
-	cl.client.LookupNick(userID, proxy)
-	return nil
-}
 
-// Parses a passed message.  Allows a message to be parsed using the internal parser
-// across the Bindings
-func ParseMessage(message []byte) (Message, error) {
-	return api.ParseMessage(message)
-}
+	roundID := id.Round(rid)
 
-func (s *storageProxy) SetLocation(locationA, locationB string) error {
-	return s.boundStorage.SetLocation(locationA, locationB)
+	ec := c.api.GetRoundEvents().AddRoundEvent(roundID, rcb, timeout)
+
+	return newRoundUnregister(roundID, ec, c.api.GetRoundEvents())
 }
 
-func (s *storageProxy) GetLocation() (string, string) {
-	locsStr := s.boundStorage.GetLocation()
-	locs := strings.Split(locsStr, ",")
+// RegisterMessageDeliveryCB allows the caller to get notified if the rounds a
+// message was sent in successfully completed. Under the hood, this uses the same
+// interface as RegisterRoundEventsHandler, but provides a convenient way to use
+// the interface in its most common form, looking up the result of message
+// retrieval
+//
+// The callbacks will return at timeoutMS if no state update occurs
+//
+// This function takes the marshaled send report to ensure a memory leak does
+// not occur as a result of both sides of the bindings holding a reference to
+// the same pointer.
+func (c *Client) WaitForRoundCompletion(marshaledSendReport []byte,
+	mdc MessageDeliveryCallback, timeoutMS int) error {
 
-	if len(locs) == 2 {
-		return locs[0], locs[1]
-	} else {
-		return locsStr, locsStr + "-2"
+	sr, err := UnmarshalSendReport(marshaledSendReport)
+	if err != nil {
+		return errors.New(fmt.Sprintf("Failed to "+
+			"WaitForRoundCompletion callback due to bad Send Report: %+v", err))
 	}
-}
 
-func (s *storageProxy) SaveA(data []byte) error {
-	return s.boundStorage.SaveA(data)
-}
+	f := func(allRoundsSucceeded, timedOut bool, rounds map[id.Round]api.RoundResult) {
+		results := make([]byte, len(sr.rl.list))
 
-func (s *storageProxy) LoadA() []byte {
-	return s.boundStorage.LoadA()
-}
+		for i, r := range sr.rl.list {
+			if result, exists := rounds[r]; exists {
+				results[i] = byte(result)
+			}
+		}
 
-func (s *storageProxy) SaveB(data []byte) error {
-	return s.boundStorage.SaveB(data)
-}
+		mdc.EventCallback(sr.mid.Marshal(), allRoundsSucceeded, timedOut, results)
+	}
 
-func (s *storageProxy) LoadB() []byte {
-	return s.boundStorage.LoadB()
-}
+	timeout := time.Duration(timeoutMS) * time.Millisecond
 
-func (s *storageProxy) IsEmpty() bool {
-	return s.boundStorage.IsEmpty()
+	return c.api.GetRoundResults(sr.rl.list, timeout, f)
 }
 
-type Writer interface{ io.Writer }
-
-func SetLogOutput(w Writer) {
-	api.SetLogOutput(w)
+// Returns a user object from which all information about the current user
+// can be gleaned
+func (c *Client) GetUser() *User {
+	u := c.api.GetUser()
+	return &User{u: &u}
 }
 
-// Call this to get the session data without getting Save called from the Go side
-func (cl *Client) GetSessionData() ([]byte, error) {
-	return cl.client.GetSessionData()
-}
+// GetNodeRegistrationStatus returns a struct with the number of nodes the
+// client is registered with and the number of in progress registrations.
+func (c *Client) GetNodeRegistrationStatus() (*NodeRegistrationsStatus, error) {
+	registered, inProgress, err := c.api.GetNodeRegistrationStatus()
 
-//LoadEncryptedSession: Spits out the encrypted session file in text
-func (cl *Client) LoadEncryptedSession() (string, error) {
-	globals.Log.INFO.Printf("Binding call: LoadEncryptedSession()")
-	return cl.client.LoadEncryptedSession()
+	return &NodeRegistrationsStatus{registered, inProgress}, err
 }
 
-//WriteToSession: Writes to file the replacement string
-func (cl *Client) WriteToSession(replacement string, storage globals.Storage) error {
-	globals.Log.INFO.Printf("Binding call: WriteToSession")
-	return cl.client.WriteToSessionFile(replacement, storage)
+/*
+// SearchWithHandler is a non-blocking search that also registers
+// a callback interface for user disovery events.
+func (c *Client) SearchWithHandler(data, separator string,
+	searchTypes []byte, hdlr UserDiscoveryHandler) {
 }
 
-func (cl *Client) InitListeners() error {
-	globals.Log.INFO.Printf("Binding call: InitListeners")
-	return cl.client.InitListeners()
-}
 
-// RegisterForNotifications sends a message to notification bot indicating it
-// is registering for notifications
-func (cl *Client) RegisterForNotifications(notificationToken []byte) error {
-	return cl.client.RegisterForNotifications(notificationToken)
+// RegisterAuthEventsHandler registers a callback interface for channel
+// authentication events.
+func (b *BindingsClient) RegisterAuthEventsHandler(hdlr AuthEventHandler) {
 }
 
-// UnregisterForNotifications sends a message to notification bot indicating it
-// no longer wants to be registered for notifications
-func (cl *Client) UnregisterForNotifications() error {
-	return cl.client.UnregisterForNotifications()
-}
+// Search accepts a "separator" separated list of search elements with
+// an associated list of searchTypes. It returns a ContactList which
+// allows you to iterate over the found contact objects.
+func (b *BindingsClient) Search(data, separator string,
+	searchTypes []byte) ContactList {
+	return nil
+}*/
diff --git a/bindings/client_test.go b/bindings/client_test.go
deleted file mode 100644
index 016c4f99ac0371e1c7aa88f3e948017a01aa1f9e..0000000000000000000000000000000000000000
--- a/bindings/client_test.go
+++ /dev/null
@@ -1,814 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package bindings
-
-import (
-	"bytes"
-	"crypto"
-	crand "crypto/rand"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/comms/gateway"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/comms/registration"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/xx_network/comms/connect"
-	"math/rand"
-	"os"
-	"reflect"
-	"sync"
-	"testing"
-	"time"
-)
-
-const NumNodes = 3
-const NumGWs = NumNodes
-const GWsStartPort = 7950
-const RegPort = 5100
-const ValidRegCode = "WTROXJ33"
-
-var RegHandler = MockRegistration{}
-var RegComms *registration.Comms
-
-var GWComms [NumGWs]*gateway.Comms
-
-var def *ndf.NetworkDefinition
-
-type MockRegistration struct {
-}
-
-func (i *MockRegistration) RegisterUser(registrationCode, test string) (hash []byte, err error) {
-	return nil, nil
-}
-
-func (i *MockRegistration) RegisterNode(salt []byte, ServerAddr, ServerTlsCert,
-	GatewayAddr, GatewayTlsCert, RegistrationCode string) error {
-	return nil
-}
-
-func (i *MockRegistration) GetCurrentClientVersion() (string, error) {
-	return globals.SEMVER, nil
-}
-
-func (i *MockRegistration) PollNdf(clientNdfHash []byte,
-	auth *connect.Auth) ([]byte,
-	error) {
-	ndfJson, _ := json.Marshal(def)
-	return ndfJson, nil
-}
-
-func (i *MockRegistration) Poll(*pb.PermissioningPoll, *connect.Auth, string) (*pb.PermissionPollResponse, error) {
-	return nil, nil
-}
-
-func (i *MockRegistration) CheckRegistration(msg *pb.RegisteredNodeCheck) (*pb.RegisteredNodeConfirmation, error) {
-	return nil, nil
-}
-
-// Setups general testing params and calls test wrapper
-func TestMain(m *testing.M) {
-	os.Exit(testMainWrapper(m))
-}
-
-// Make sure NewClient returns an error when called incorrectly.
-func TestNewClientNil(t *testing.T) {
-
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	_, err := NewClient(nil, "", "", ndfStr, pubKey)
-	if err == nil {
-		t.Errorf("NewClient returned nil on invalid (nil, nil) input!")
-	}
-
-	_, err = NewClient(nil, "", "", "", "hello")
-	if err == nil {
-		t.Errorf("NewClient returned nil on invalid (nil, 'hello') input!")
-	}
-}
-
-//Happy path: tests creation of valid client
-func TestNewClient(t *testing.T) {
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	client, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("NewClient returned error: %v", err)
-	} else if client == nil {
-		t.Errorf("NewClient returned nil Client object")
-	}
-	for _, gw := range GWComms {
-		gw.DisconnectAll()
-	}
-}
-
-//Happy Path: Register with permissioning
-func TestRegister(t *testing.T) {
-
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = client.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = client.GenerateKeys("")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, err := client.RegisterWithPermissioning(true, ValidRegCode)
-	if err != nil {
-		t.Errorf("Registration failed: %s", err.Error())
-	}
-	if len(regRes) == 0 {
-		t.Errorf("Invalid registration number received: %v", regRes)
-	}
-	for _, gw := range GWComms {
-		gw.DisconnectAll()
-	}
-}
-
-type DummyReceptionCallback struct{}
-
-func (*DummyReceptionCallback) Callback(error) {
-	return
-}
-
-//Error path: Changing username should panic before registration has happened
-func TestClient_ChangeUsername_ErrorPath(t *testing.T) {
-	defer func() {
-		if r := recover(); r != nil {
-			return
-		}
-	}()
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-
-	testClient, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = testClient.ChangeUsername("josh420")
-	if err == nil {
-		t.Error("Expected error path, should not be able to change username before" +
-			"regState PermissioningComplete")
-	}
-}
-
-//Happy path: should have no errors when changing username
-func TestClient_ChangeUsername(t *testing.T) {
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-
-	testClient, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = testClient.client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, err := testClient.RegisterWithPermissioning(false, ValidRegCode)
-	if len(regRes) == 0 {
-		t.Errorf("Invalid registration number received: %v", regRes)
-	}
-
-	err = testClient.ChangeUsername("josh420")
-	if err != nil {
-		t.Errorf("Unexpected error, should have changed username: %v", err)
-	}
-
-}
-
-func TestClient_StorageIsEmpty(t *testing.T) {
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-
-	testClient, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = testClient.client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, err := testClient.RegisterWithPermissioning(false, ValidRegCode)
-	if len(regRes) == 0 {
-		t.Errorf("Invalid registration number received: %v", regRes)
-	}
-
-	if testClient.StorageIsEmpty() {
-		t.Errorf("Unexpected empty storage!")
-	}
-}
-
-//Error path: Have added no contacts, so deleting a contact should fail
-func TestDeleteUsername_EmptyContactList(t *testing.T) {
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-
-	testClient, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = testClient.client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, err := testClient.RegisterWithPermissioning(false, ValidRegCode)
-	if len(regRes) == 0 {
-		t.Errorf("Invalid registration number received: %v", regRes)
-	}
-	//Attempt to delete a contact from an empty contact list
-	_, err = testClient.DeleteContact([]byte("typo"))
-	if err != nil {
-		return
-	}
-	t.Errorf("Expected error path, but did not get error on deleting a contact." +
-		"Contact list should be empty")
-}
-
-//Happy path: Tests regState gets properly updated along the registration codepath
-func TestClient_GetRegState(t *testing.T) {
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	testClient, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = testClient.client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register with a valid registration code
-	_, err = testClient.RegisterWithPermissioning(true, ValidRegCode)
-
-	if err != nil {
-		t.Errorf("Register with permissioning failed: %s", err.Error())
-	}
-
-	if testClient.GetRegState() != int64(user.PermissioningComplete) {
-		t.Errorf("Unexpected reg state: Expected PermissioningComplete (%d), recieved: %d",
-			user.PermissioningComplete, testClient.GetRegState())
-	}
-
-	err = testClient.RegisterWithNodes()
-	if err != nil {
-		t.Errorf("Register with nodes failed: %v", err.Error())
-	}
-}
-
-//Happy path: send unencrypted message
-func TestClient_Send(t *testing.T) {
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	testClient, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-
-	if err != nil {
-		t.Errorf("Failed to marshal group JSON: %s", err)
-	}
-
-	err = testClient.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = testClient.client.GenerateKeys(nil, "password")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	// Register with a valid registration code
-	userID, err := testClient.RegisterWithPermissioning(true, ValidRegCode)
-
-	if err != nil {
-		t.Errorf("Register with permissioning failed: %s", err.Error())
-	}
-
-	err = testClient.RegisterWithNodes()
-	if err != nil {
-		t.Errorf("Register with nodes failed: %v", err.Error())
-	}
-
-	// Login to gateway
-	_, err = testClient.Login(userID, "password")
-
-	if err != nil {
-		t.Errorf("Login failed: %s", err.Error())
-	}
-
-	err = testClient.StartMessageReceiver(&DummyReceptionCallback{})
-
-	if err != nil {
-		t.Errorf("Could not start message reception: %+v", err)
-	}
-
-	receiverID := id.NewIdFromBytes(userID, t)
-	receiverID.SetType(id.User)
-	// Test send with invalid sender ID
-	_, err = testClient.Send(
-		mockMesssage{
-			Sender:    id.NewIdFromUInt(12, id.User, t),
-			TypedBody: parse.TypedBody{Body: []byte("test")},
-			Receiver:  receiverID,
-		}, false)
-
-	if err != nil {
-		// TODO: would be nice to catch the sender but we
-		//  don't have the interface/mocking for that.
-		t.Errorf("error on first message send: %+v", err)
-	}
-
-	// Test send with valid inputs
-	_, err = testClient.Send(
-		mockMesssage{
-			Sender:    id.NewIdFromBytes(userID, t),
-			TypedBody: parse.TypedBody{Body: []byte("test")},
-			Receiver:  testClient.client.GetCurrentUser(),
-		}, false)
-
-	if err != nil {
-		t.Errorf("Error sending message: %v", err)
-	}
-
-	err = testClient.Logout()
-
-	if err != nil {
-		t.Errorf("Logout failed: %v", err)
-	}
-	disconnectServers()
-}
-
-func TestLoginLogout(t *testing.T) {
-
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	if err != nil {
-		t.Errorf("Error starting client: %+v", err)
-	}
-	// InitNetwork to gateway
-	err = client.InitNetwork()
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = client.client.GenerateKeys(nil, "")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, err := client.RegisterWithPermissioning(true, ValidRegCode)
-	loginRes, err2 := client.Login(regRes, "")
-	if err2 != nil {
-		t.Errorf("Login failed: %s", err2.Error())
-	}
-	if len(loginRes) == 0 {
-		t.Errorf("Invalid login received: %v", loginRes)
-	}
-
-	err = client.StartMessageReceiver(&DummyReceptionCallback{})
-	if err != nil {
-		t.Errorf("Could not start message reciever: %+v", err)
-	}
-	time.Sleep(200 * time.Millisecond)
-	err3 := client.Logout()
-	if err3 != nil {
-		t.Errorf("Logoutfailed: %s", err3.Error())
-	}
-	for _, gw := range GWComms {
-		gw.DisconnectAll()
-	}
-}
-
-type MockListener bool
-
-func (m *MockListener) Hear(msg Message, isHeardElsewhere bool) {
-	*m = true
-}
-
-// Proves that a message can be received by a listener added with the bindings
-func TestListen(t *testing.T) {
-
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	// InitNetwork to gateway
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = client.client.GenerateKeys(nil, "1234")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, _ := client.RegisterWithPermissioning(true, ValidRegCode)
-	_, err = client.Login(regRes, "1234")
-
-	if err != nil {
-		t.Errorf("Could not log in: %+v", err)
-	}
-
-	listener := MockListener(false)
-	client.Listen(id.ZeroUser[:], int32(cmixproto.Type_NO_TYPE), &listener)
-	client.client.GetSwitchboard().Speak(&parse.Message{
-		TypedBody: parse.TypedBody{
-			MessageType: 0,
-			Body:        []byte("stuff"),
-		},
-		Sender:   &id.ZeroUser,
-		Receiver: client.client.GetCurrentUser(),
-	})
-	time.Sleep(time.Second)
-	if !listener {
-		t.Error("Message not received")
-	}
-	for _, gw := range GWComms {
-		gw.DisconnectAll()
-	}
-}
-
-func TestStopListening(t *testing.T) {
-
-	ndfStr, pubKey := getNDFJSONStr(def, t)
-
-	d := DummyStorage{LocationA: "Blah", StoreA: []byte{'a', 'b', 'c'}}
-	client, err := NewClient(&d, "hello", "", ndfStr, pubKey)
-	// InitNetwork to gateway
-	err = client.InitNetwork()
-
-	if err != nil {
-		t.Errorf("Could not connect: %+v", err)
-	}
-
-	err = client.client.GenerateKeys(nil, "1234")
-	if err != nil {
-		t.Errorf("Could not generate Keys: %+v", err)
-	}
-
-	regRes, _ := client.RegisterWithPermissioning(true, ValidRegCode)
-
-	_, err = client.Login(regRes, "1234")
-
-	if err != nil {
-		t.Errorf("Could not log in: %+v", err)
-	}
-
-	listener := MockListener(false)
-	handle, err := client.Listen(id.ZeroUser[:], int32(cmixproto.Type_NO_TYPE), &listener)
-	if err != nil {
-		t.Fatal(err)
-	}
-	client.StopListening(handle)
-	client.client.GetSwitchboard().Speak(&parse.Message{
-		TypedBody: parse.TypedBody{
-			MessageType: 0,
-			Body:        []byte("stuff"),
-		},
-		Sender:   &id.ZeroUser,
-		Receiver: &id.ZeroUser,
-	})
-	if listener {
-		t.Error("Message was received after we stopped listening for it")
-	}
-}
-
-type MockWriter struct {
-	lastMessage []byte
-}
-
-func (mw *MockWriter) Write(msg []byte) (int, error) {
-	mw.lastMessage = msg
-	return len(msg), nil
-}
-
-func TestSetLogOutput(t *testing.T) {
-	mw := &MockWriter{}
-	SetLogOutput(mw)
-	msg := "Test logging message"
-	globals.Log.CRITICAL.Print(msg)
-	if !bytes.Contains(mw.lastMessage, []byte(msg)) {
-		t.Errorf("Mock writer didn't get the logging message")
-	}
-}
-
-func TestParse(t *testing.T) {
-	ms := parse.Message{}
-	ms.Body = []byte{0, 1, 2}
-	ms.MessageType = int32(cmixproto.Type_NO_TYPE)
-	ms.Receiver = &id.ZeroUser
-	ms.Sender = &id.ZeroUser
-
-	messagePacked := ms.Pack()
-
-	msOut, err := ParseMessage(messagePacked)
-
-	if err != nil {
-		t.Errorf("Message failed to parse: %s", err.Error())
-	}
-
-	if msOut.GetMessageType() != int32(ms.MessageType) {
-		t.Errorf("Types do not match after message parse: %v vs %v", msOut.GetMessageType(), ms.MessageType)
-	}
-
-	if !reflect.DeepEqual(ms.Body, msOut.GetPayload()) {
-		t.Errorf("Bodies do not match after message parse: %v vs %v", msOut.GetPayload(), ms.Body)
-	}
-
-}
-
-func getNDFJSONStr(def *ndf.NetworkDefinition, t *testing.T) (string, string) {
-	ndfBytes, err := json.Marshal(def)
-
-	if err != nil {
-		t.Errorf("Could not JSON the NDF: %+v", err)
-	}
-
-	// Load tls private key
-	privKey, err := rsa.LoadPrivateKeyFromPem([]byte("-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC7Dkb6VXFn4cdp\nU0xh6ji0nTDQUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZr\ntzujFPBRFp9O14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfI\nTVCv8CLE0t1ibiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGes\nkWEFa2VttHqF910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq\n6/OAXCU1JLi3kW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzf\nrarmsGM0LZh6JY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYI\nCqldpt79gaET9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8V\nMKbrCaOkzD5zgnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4S\no9AppDQB41SH3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenP\nel2ApMXp+LVRdDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/u\nSALsU2v9UHBzprdrLSZk2YpozJb+CQIDAQABAoICAARjDFUYpeU6zVNyCauOM7BA\ns4FfQdHReg+zApTfWHosDQ04NIc9CGbM6e5E9IFlb3byORzyevkllf5WuMZVWmF8\nd1YBBeTftKYBn2Gwa42Ql9dl3eD0wQ1gUWBBeEoOVZQ0qskr9ynpr0o6TfciWZ5m\nF50UWmUmvc4ppDKhoNwogNU/pKEwwF3xOv2CW2hB8jyLQnk3gBZlELViX3UiFKni\n/rCfoYYvDFXt+ABCvx/qFNAsQUmerurQ3Ob9igjXRaC34D7F9xQ3CMEesYJEJvc9\nGjvr5DbnKnjx152HS56TKhK8gp6vGHJz17xtWECXD3dIUS/1iG8bqXuhdg2c+2aW\nm3MFpa5jgpAawUWc7c32UnqbKKf+HI7/x8J1yqJyNeU5SySyYSB5qtwTShYzlBW/\nyCYD41edeJcmIp693nUcXzU+UAdtpt0hkXS59WSWlTrB/huWXy6kYXLNocNk9L7g\niyx0cOmkuxREMHAvK0fovXdVyflQtJYC7OjJxkzj2rWO+QtHaOySXUyinkuTb5ev\nxNhs+ROWI/HAIE9buMqXQIpHx6MSgdKOL6P6AEbBan4RAktkYA6y5EtH/7x+9V5E\nQTIz4LrtI6abaKb4GUlZkEsc8pxrkNwCqOAE/aqEMNh91Na1TOj3f0/a6ckGYxYH\npyrvwfP2Ouu6e5FhDcCBAoIBAQDcN8mK99jtrH3q3Q8vZAWFXHsOrVvnJXyHLz9V\n1Rx/7TnMUxvDX1PIVxhuJ/tmHtxrNIXOlps80FCZXGgxfET/YFrbf4H/BaMNJZNP\nag1wBV5VQSnTPdTR+Ijice+/ak37S2NKHt8+ut6yoZjD7sf28qiO8bzNua/OYHkk\nV+RkRkk68Uk2tFMluQOSyEjdsrDNGbESvT+R1Eotupr0Vy/9JRY/TFMc4MwJwOoy\ns7wYr9SUCq/cYn7FIOBTI+PRaTx1WtpfkaErDc5O+nLLEp1yOrfktl4LhU/r61i7\nfdtafUACTKrXG2qxTd3w++mHwTwVl2MwhiMZfxvKDkx0L2gxAoIBAQDZcxKwyZOy\ns6Aw7igw1ftLny/dpjPaG0p6myaNpeJISjTOU7HKwLXmlTGLKAbeRFJpOHTTs63y\ngcmcuE+vGCpdBHQkaCev8cve1urpJRcxurura6+bYaENO6ua5VzF9BQlDYve0YwY\nlbJiRKmEWEAyULjbIebZW41Z4UqVG3MQI750PRWPW4WJ2kDhksFXN1gwSnaM46KR\nPmVA0SL+RCPcAp/VkImCv0eqv9exsglY0K/QiJfLy3zZ8QvAn0wYgZ3AvH3lr9rJ\nT7pg9WDb+OkfeEQ7INubqSthhaqCLd4zwbMRlpyvg1cMSq0zRvrFpwVlSY85lW4F\ng/tgjJ99W9VZAoIBAH3OYRVDAmrFYCoMn+AzA/RsIOEBqL8kaz/Pfh9K4D01CQ/x\naqryiqqpFwvXS4fLmaClIMwkvgq/90ulvuCGXeSG52D+NwW58qxQCxgTPhoA9yM9\nVueXKz3I/mpfLNftox8sskxl1qO/nfnu15cXkqVBe4ouD+53ZjhAZPSeQZwHi05h\nCbJ20gl66M+yG+6LZvXE96P8+ZQV80qskFmGdaPozAzdTZ3xzp7D1wegJpTz3j20\n3ULKAiIb5guZNU0tEZz5ikeOqsQt3u6/pVTeDZR0dxnyFUf/oOjmSorSG75WT3sA\n0ZiR0SH5mhFR2Nf1TJ4JHmFaQDMQqo+EG6lEbAECggEAA7kGnuQ0lSCiI3RQV9Wy\nAa9uAFtyE8/XzJWPaWlnoFk04jtoldIKyzHOsVU0GOYOiyKeTWmMFtTGANre8l51\nizYiTuVBmK+JD/2Z8/fgl8dcoyiqzvwy56kX3QUEO5dcKO48cMohneIiNbB7PnrM\nTpA3OfkwnJQGrX0/66GWrLYP8qmBDv1AIgYMilAa40VdSyZbNTpIdDgfP6bU9Ily\nG7gnyF47HHPt5Cx4ouArbMvV1rof7ytCrfCEhP21Lc46Ryxy81W5ZyzoQfSxfdKb\nGyDR+jkryVRyG69QJf5nCXfNewWbFR4ohVtZ78DNVkjvvLYvr4qxYYLK8PI3YMwL\nsQKCAQB9lo7JadzKVio+C18EfNikOzoriQOaIYowNaaGDw3/9KwIhRsKgoTs+K5O\ngt/gUoPRGd3M2z4hn5j4wgeuFi7HC1MdMWwvgat93h7R1YxiyaOoCTxH1klbB/3K\n4fskdQRxuM8McUebebrp0qT5E0xs2l+ABmt30Dtd3iRrQ5BBjnRc4V//sQiwS1aC\nYi5eNYCQ96BSAEo1dxJh5RI/QxF2HEPUuoPM8iXrIJhyg9TEEpbrEJcxeagWk02y\nOMEoUbWbX07OzFVvu+aJaN/GlgiogMQhb6IiNTyMlryFUleF+9OBA8xGHqGWA6nR\nOaRA5ZbdE7g7vxKRV36jT3wvD7W+\n-----END PRIVATE KEY-----\n"))
-	if err != nil || privKey == nil {
-		t.Error("Failed to load privKey\n")
-	}
-
-	// Sign the NDF
-	rsaHash := crypto.SHA256.New()
-	rsaHash.Write(ndfBytes)
-	signature, _ := rsa.Sign(
-		crand.Reader, privKey, crypto.SHA256, rsaHash.Sum(nil), nil)
-
-	// Compose network definition string
-	ndfStr := string(ndfBytes) + "\n" + base64.StdEncoding.EncodeToString(signature) + "\n"
-
-	return ndfStr, "-----BEGIN CERTIFICATE-----\nMIIGHTCCBAWgAwIBAgIUOcAn9cpH+hyRH8/UfqtbFDoSxYswDQYJKoZIhvcNAQEL\nBQAwgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJQ2xhcmVt\nb250MRAwDgYDVQQKDAdFbGl4eGlyMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEZMBcG\nA1UEAwwQZ2F0ZXdheS5jbWl4LnJpcDEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZWxp\neHhpci5pbzAeFw0xOTA4MTYwMDQ4MTNaFw0yMDA4MTUwMDQ4MTNaMIGSMQswCQYD\nVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUNsYXJlbW9udDEQMA4GA1UE\nCgwHRWxpeHhpcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGTAXBgNVBAMMEGdhdGV3\nYXkuY21peC5yaXAxHzAdBgkqhkiG9w0BCQEWEGFkbWluQGVsaXh4aXIuaW8wggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7Dkb6VXFn4cdpU0xh6ji0nTDQ\nUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZrtzujFPBRFp9O\n14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfITVCv8CLE0t1i\nbiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGeskWEFa2VttHqF\n910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq6/OAXCU1JLi3\nkW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzfrarmsGM0LZh6\nJY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYICqldpt79gaET\n9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8VMKbrCaOkzD5z\ngnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4So9AppDQB41SH\n3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenPel2ApMXp+LVR\ndDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/uSALsU2v9UHBz\nprdrLSZk2YpozJb+CQIDAQABo2kwZzAdBgNVHQ4EFgQUDaTvG7SwgRQ3wcYx4l+W\nMcZjX7owHwYDVR0jBBgwFoAUDaTvG7SwgRQ3wcYx4l+WMcZjX7owDwYDVR0TAQH/\nBAUwAwEB/zAUBgNVHREEDTALgglmb28uY28udWswDQYJKoZIhvcNAQELBQADggIB\nADKz0ST0uS57oC4rT9zWhFqVZkEGh1x1XJ28bYtNUhozS8GmnttV9SnJpq0EBCm/\nr6Ub6+Wmf60b85vCN5WDYdoZqGJEBjGGsFzl4jkYEE1eeMfF17xlNUSdt1qLCE8h\nU0glr32uX4a6nsEkvw1vo1Liuyt+y0cOU/w4lgWwCqyweu3VuwjZqDoD+3DShVzX\n8f1p7nfnXKitrVJt9/uE+AtAk2kDnjBFbRxCfO49EX4Cc5rADUVXMXm0itquGBYp\nMbzSgFmsMp40jREfLYRRzijSZj8tw14c2U9z0svvK9vrLCrx9+CZQt7cONGHpr/C\n/GIrP/qvlg0DoLAtjea73WxjSCbdL3Nc0uNX/ymXVHdQ5husMCZbczc9LYdoT2VP\nD+GhkAuZV9g09COtRX4VP09zRdXiiBvweiq3K78ML7fISsY7kmc8KgVH22vcXvMX\nCgGwbrxi6QbQ80rWjGOzW5OxNFvjhvJ3vlbOT6r9cKZGIPY8IdN/zIyQxHiim0Jz\noavr9CPDdQefu9onizsmjsXFridjG/ctsJxcUEqK7R12zvaTxu/CVYZbYEUFjsCe\nq6ZAACiEJGvGeKbb/mSPvGs2P1kS70/cGp+P5kBCKqrm586FB7BcafHmGFrWhT3E\nLOUYkOV/gADT2hVDCrkPosg7Wb6ND9/mhCVVhf4hLGRh\n-----END CERTIFICATE-----\n"
-}
-
-// Handles initialization of mock registration server,
-// gateways used for registration and gateway used for session
-func testMainWrapper(m *testing.M) int {
-
-	def = getNDF()
-
-	// Initialize permissioning server
-	// TODO(nan) We shouldn't need to start registration servers twice, right?
-	pAddr := def.Registration.Address
-	regId := new(id.ID)
-	copy(regId[:], "testRegServer")
-	regId.SetType(id.Generic)
-	RegComms = registration.StartRegistrationServer(regId, pAddr,
-		&RegHandler, nil, nil)
-
-	// Start mock gateways used by registration and defer their shutdown (may not be needed)
-	//the ports used are colliding between tests in GoLand when running full suite, this is a dumb fix
-	bump := rand.Intn(10) * 10
-	for i := 0; i < NumGWs; i++ {
-		gwId := new(id.ID)
-		copy(gwId[:], "testGateway")
-		gwId.SetType(id.Gateway)
-
-		gw := ndf.Gateway{
-			ID:      gwId.Marshal(),
-			Address: fmtAddress(GWsStartPort + i + bump),
-		}
-
-		def.Gateways = append(def.Gateways, gw)
-		GWComms[i] = gateway.StartGateway(gwId, gw.Address,
-			gateway.NewImplementation(), nil, nil)
-	}
-
-	// Start mock registration server and defer its shutdown
-	def.Registration = ndf.Registration{
-		Address: fmtAddress(RegPort),
-	}
-	RegComms = registration.StartRegistrationServer(regId, def.Registration.Address,
-		&RegHandler, nil, nil)
-
-	for i := 0; i < NumNodes; i++ {
-		nId := new(id.ID)
-		nId[0] = byte(i)
-		nId.SetType(id.Node)
-		n := ndf.Node{
-			ID: nId[:],
-		}
-		def.Nodes = append(def.Nodes, n)
-	}
-
-	defer testWrapperShutdown()
-	return m.Run()
-}
-
-func testWrapperShutdown() {
-	for _, gw := range GWComms {
-		gw.Shutdown()
-	}
-	RegComms.Shutdown()
-
-}
-
-func fmtAddress(port int) string { return fmt.Sprintf("localhost:%d", port) }
-
-func getNDF() *ndf.NetworkDefinition {
-	return &ndf.NetworkDefinition{
-		E2E: ndf.Group{
-			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
-				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
-				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
-				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
-				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
-				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
-				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
-				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
-				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
-				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
-				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
-				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
-				"847AEF49F66E43873",
-			Generator: "2",
-		},
-		CMIX: ndf.Group{
-			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
-				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
-				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
-				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
-				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
-				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
-				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
-				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
-			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
-				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
-				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
-				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
-				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
-				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
-				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
-				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
-		},
-		Registration: ndf.Registration{
-			Address:        fmt.Sprintf("0.0.0.0:%d", 5000+rand.Intn(1000)),
-			TlsCertificate: "",
-		},
-	}
-}
-
-// Mock dummy storage interface for testing.
-type DummyStorage struct {
-	LocationA string
-	LocationB string
-	StoreA    []byte
-	StoreB    []byte
-	mutex     sync.Mutex
-}
-
-func (d *DummyStorage) IsEmpty() bool {
-	return d.StoreA == nil && d.StoreB == nil
-}
-
-func (d *DummyStorage) SetLocation(lA, lB string) error {
-	d.LocationA = lA
-	d.LocationB = lB
-	return nil
-}
-
-func (d *DummyStorage) GetLocation() string {
-	return fmt.Sprintf("%s,%s", d.LocationA, d.LocationB)
-}
-
-func (d *DummyStorage) SaveA(b []byte) error {
-	d.StoreA = make([]byte, len(b))
-	copy(d.StoreA, b)
-	return nil
-}
-
-func (d *DummyStorage) SaveB(b []byte) error {
-	d.StoreB = make([]byte, len(b))
-	copy(d.StoreB, b)
-	return nil
-}
-
-func (d *DummyStorage) Lock() {
-	d.mutex.Lock()
-}
-
-func (d *DummyStorage) Unlock() {
-	d.mutex.Unlock()
-}
-
-func (d *DummyStorage) LoadA() []byte {
-	return d.StoreA
-}
-
-func (d *DummyStorage) LoadB() []byte {
-	return d.StoreB
-}
-
-type mockMesssage struct {
-	parse.TypedBody
-	// The crypto type is inferred from the message's contents
-	InferredType parse.CryptoType
-	Sender       *id.ID
-	Receiver     *id.ID
-	Nonce        []byte
-	Timestamp    time.Time
-}
-
-// Returns the message's sender ID
-func (m mockMesssage) GetSender() []byte {
-	return m.Sender.Bytes()
-}
-
-// Returns the message payload
-// Parse this with protobuf/whatever according to the type of the message
-func (m mockMesssage) GetPayload() []byte {
-	return m.TypedBody.Body
-}
-
-// Returns the message's recipient ID
-func (m mockMesssage) GetRecipient() []byte {
-	return m.Receiver.Bytes()
-}
-
-// Returns the message's type
-func (m mockMesssage) GetMessageType() int32 {
-	return m.TypedBody.MessageType
-}
-
-// Returns the message's timestamp in seconds since unix epoc
-func (m mockMesssage) GetTimestamp() int64 {
-	return m.Timestamp.Unix()
-}
-
-// Returns the message's timestamp in ns since unix epoc
-func (m mockMesssage) GetTimestampNano() int64 {
-	return m.Timestamp.UnixNano()
-}
-
-func disconnectServers() {
-	for _, gw := range GWComms {
-		gw.DisconnectAll()
-
-	}
-	RegComms.DisconnectAll()
-}
diff --git a/bindings/contact.go b/bindings/contact.go
new file mode 100644
index 0000000000000000000000000000000000000000..daa45aa357095fa243c1a8b699198265e027a7f8
--- /dev/null
+++ b/bindings/contact.go
@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/primitives/fact"
+)
+
+/* fact object*/
+//creates a new fact. The factType must be either:
+//  0 - Username
+//  1 - Email
+//  2 - Phone Number
+// The fact must be well formed for the type and must not include commas or
+// semicolons. If it is not well formed, it will be rejected.  Phone numbers
+// must have the two letter country codes appended.  For the complete set of
+// validation, see /elixxir/primitives/fact/fact.go
+func NewFact(factType int, factStr string) (*Fact, error) {
+	f, err := fact.NewFact(fact.FactType(factType), factStr)
+	if err != nil {
+		return nil, err
+	}
+	return &Fact{f: &f}, nil
+}
+
+type Fact struct {
+	f *fact.Fact
+}
+
+func (f *Fact) Get() string {
+	return f.f.Fact
+}
+
+func (f *Fact) Type() int {
+	return int(f.f.T)
+}
+
+func (f *Fact) Stringify() string {
+	return f.f.Stringify()
+}
+
+/* contact object*/
+type Contact struct {
+	c *contact.Contact
+}
+
+// GetID returns the user ID for this user.
+func (c *Contact) GetID() []byte {
+	return c.c.ID.Bytes()
+}
+
+// GetDHPublicKey returns the public key associated with the Contact.
+func (c *Contact) GetDHPublicKey() []byte {
+	return c.c.DhPubKey.Bytes()
+}
+
+// GetDHPublicKey returns hash of a DH proof of key ownership.
+func (c *Contact) GetOwnershipProof() []byte {
+	return c.c.OwnershipProof
+}
+
+// Returns a fact list for adding and getting facts to and from the contact
+func (c *Contact) GetFactList() *FactList {
+	return &FactList{c: c.c}
+}
+
+func (c *Contact) Marshal() ([]byte, error) {
+	return c.c.Marshal(), nil
+}
diff --git a/bindings/interfaces.go b/bindings/interfaces.go
deleted file mode 100644
index a41a1c6df3c79f0a1b5b145ab0b422e3de929bf3..0000000000000000000000000000000000000000
--- a/bindings/interfaces.go
+++ /dev/null
@@ -1,115 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package bindings
-
-import (
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/primitives/switchboard"
-)
-
-// Message used for binding
-type Message interface {
-	// Returns the message's sender ID
-	GetSender() []byte
-	// Returns the message payload
-	// Parse this with protobuf/whatever according to the type of the message
-	GetPayload() []byte
-	// Returns the message's recipient ID
-	GetRecipient() []byte
-	// Returns the message's type
-	GetMessageType() int32
-	// Returns the message's timestamp in seconds since unix epoc
-	GetTimestamp() int64
-	// Returns the message's timestamp in ns since unix epoc
-	GetTimestampNano() int64
-}
-
-// Copy of the storage interface.
-// It is identical to the interface used in Globals,
-// and a results the types can be passed freely between the two
-type Storage interface {
-	// Give a Location for storage.  Does not need to be implemented if unused.
-	SetLocation(string, string) error
-	// Returns the Location for storage.
-	// Does not need to be implemented if unused.
-	GetLocation() string
-	// Stores the passed byte slice to location A
-	SaveA([]byte) error
-	// Returns the stored byte slice stored in location A
-	LoadA() []byte
-	// Stores the passed byte slice to location B
-	SaveB([]byte) error
-	// Returns the stored byte slice stored in location B
-	LoadB() []byte
-	// Returns whether the storage has even been written to.
-	// if something exists in A or B
-	IsEmpty() bool
-}
-
-// Translate a bindings storage to a client storage
-type storageProxy struct {
-	boundStorage Storage
-}
-
-// Translate a bindings message to a parse message
-// An object implementing this interface can be called back when the client
-// gets a message of the type that the registerer specified at registration
-// time.
-type Listener interface {
-	// This does not include the generic interfaces seen in the go implementation
-	// Those are used internally on the backend and cause errors if we try to port them
-	Hear(msg Message, isHeardElsewhere bool)
-}
-
-// Translate a bindings listener to a switchboard listener
-// Note to users of this package from other languages: Symbols that start with
-// lowercase are unexported from the package and meant for internal use only.
-type listenerProxy struct {
-	proxy Listener
-}
-
-func (lp *listenerProxy) Hear(msg switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	msgInterface := &parse.BindingsMessageProxy{Proxy: msg.(*parse.Message)}
-	lp.proxy.Hear(msgInterface, isHeardElsewhere)
-}
-
-// Interface used to receive a callback on searching for a user
-type SearchCallback interface {
-	Callback(userID, pubKey []byte, err error)
-}
-
-type searchCallbackProxy struct {
-	proxy SearchCallback
-}
-
-func (scp *searchCallbackProxy) Callback(userID, pubKey []byte, err error) {
-	scp.proxy.Callback(userID, pubKey, err)
-}
-
-// Interface used to receive a callback on searching for a user's nickname
-type NickLookupCallback interface {
-	Callback(nick string, err error)
-}
-
-type nickCallbackProxy struct {
-	proxy NickLookupCallback
-}
-
-// interface used to receive the result of a nickname request
-func (ncp *nickCallbackProxy) Callback(nick string, err error) {
-	ncp.proxy.Callback(nick, err)
-}
-
-// interface used to receive a ui friendly description of the current status of
-// registration
-type ConnectionStatusCallback interface {
-	Callback(status int, TimeoutSeconds int)
-}
-
-type OperationProgressCallback interface {
-	Callback(int)
-}
diff --git a/bindings/list.go b/bindings/list.go
new file mode 100644
index 0000000000000000000000000000000000000000..3fc31382636507aa32ec8423c1ac04d7a4c13092
--- /dev/null
+++ b/bindings/list.go
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"errors"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+/*IntList*/
+
+type IntList struct {
+	lst []int
+}
+
+func MakeIntList() *IntList {
+	return &IntList{lst: make([]int, 0)}
+}
+
+func (il *IntList) Add(i int) {
+	il.lst = append(il.lst, i)
+}
+
+func (il *IntList) Len() int {
+	return len(il.lst)
+}
+
+func (il *IntList) Get(i int) (int, error) {
+	if i < 0 || i >= len(il.lst) {
+		return 0, errors.New("invalid index")
+	}
+	return il.lst[i], nil
+}
+
+/*RoundList*/
+
+type RoundList struct {
+	list []id.Round
+}
+
+// Gets the number of round IDs stored
+func (rl *RoundList) Len() int {
+	return len(rl.list)
+}
+
+// Gets a stored round ID at the given index
+func (rl *RoundList) Get(i int) (int, error) {
+	if i < 0 || i > len(rl.list) {
+		return -1, errors.New("round ID cannot be under 0 or over" +
+			" list len")
+	}
+
+	return int(rl.list[i]), nil
+}
+
+/*ContactList*/
+
+type ContactList struct {
+	list []contact.Contact
+}
+
+// Gets the number of round IDs stored
+func (cl *ContactList) Len() int {
+	return len(cl.list)
+}
+
+// Gets a stored round ID at the given index
+func (cl *ContactList) Get(i int) (*Contact, error) {
+	if i < 0 || i > len(cl.list) {
+		return nil, errors.New("contact cannot be under 0 or over" +
+			" list len")
+	}
+
+	return &Contact{c: &cl.list[i]}, nil
+}
+
+/*FactList*/
+func NewFactList() *FactList {
+	return &FactList{c: &contact.Contact{
+		ID:             nil,
+		DhPubKey:       nil,
+		OwnershipProof: nil,
+		Facts:          make([]fact.Fact, 0),
+	}}
+}
+
+type FactList struct {
+	c *contact.Contact
+}
+
+func (fl *FactList) Num() int {
+	return len(fl.c.Facts)
+}
+
+func (fl *FactList) Get(i int) Fact {
+	return Fact{f: &(fl.c.Facts)[i]}
+}
+
+func (fl *FactList) Add(factData string, factType int) error {
+	f, err := fact.NewFact(fact.FactType(factType), factData)
+	if err != nil {
+		return err
+	}
+	fl.c.Facts = append(fl.c.Facts, f)
+	return nil
+}
+
+func (fl *FactList) Stringify() (string, error) {
+	return fl.c.Facts.Stringify(), nil
+}
diff --git a/bindings/message.go b/bindings/message.go
new file mode 100644
index 0000000000000000000000000000000000000000..978a62743dbd823062f1c4a961e20d74f386c496
--- /dev/null
+++ b/bindings/message.go
@@ -0,0 +1,45 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import "gitlab.com/elixxir/client/interfaces/message"
+
+// Message is a message received from the cMix network in the clear
+// or that has been decrypted using established E2E keys.
+type Message struct {
+	r message.Receive
+}
+
+//Returns the id of the message
+func (m *Message) GetID() []byte {
+	return m.r.ID[:]
+}
+
+// Returns the message's sender ID, if available
+func (m *Message) GetSender() []byte {
+	return m.r.Sender.Bytes()
+}
+
+// Returns the message's payload/contents
+func (m *Message) GetPayload() []byte {
+	return m.r.Payload
+}
+
+// Returns the message's type
+func (m *Message) GetMessageType() int {
+	return int(m.r.MessageType)
+}
+
+// Returns the message's timestamp in ms
+func (m *Message) GetTimestampMS() int {
+	return int(m.r.Timestamp.Unix())
+}
+
+func (m *Message) GetTimestampNano() int {
+	return int(m.r.Timestamp.UnixNano())
+}
diff --git a/bindings/params.go b/bindings/params.go
new file mode 100644
index 0000000000000000000000000000000000000000..35afbb8901cfd5e8309f8be7a30a14c907496c23
--- /dev/null
+++ b/bindings/params.go
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2021 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Contains params-related bindings
+
+package bindings
+
+import (
+	"gitlab.com/elixxir/client/interfaces/params"
+)
+
+func (c *Client) GetCMIXParams() (string, error) {
+	p, err := params.GetDefaultCMIX().Marshal()
+	return string(p), err
+}
+
+func (c *Client) GetE2EParams() (string, error) {
+	p, err := params.GetDefaultE2E().Marshal()
+	return string(p), err
+}
+
+func (c *Client) GetNetworkParams() (string, error) {
+	p, err := params.GetDefaultNetwork().Marshal()
+	return string(p), err
+}
+
+func (c *Client) GetUnsafeParams() (string, error) {
+	p, err := params.GetDefaultUnsafe().Marshal()
+	return string(p), err
+}
diff --git a/bindings/registrationStatus.go b/bindings/registrationStatus.go
new file mode 100644
index 0000000000000000000000000000000000000000..22446376436a0a8145c97e20e868073e1984d0a0
--- /dev/null
+++ b/bindings/registrationStatus.go
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+// NodeRegistrationsStatus structure for returning node registration statuses
+// for bindings.
+type NodeRegistrationsStatus struct {
+	registered int
+	inProgress int
+}
+
+// GetRegistered returns the number of nodes registered with the client.
+func (nrs *NodeRegistrationsStatus) GetRegistered() int {
+	return nrs.registered
+}
+
+// GetInProgress return the number of nodes currently registering.
+func (nrs *NodeRegistrationsStatus) GetInProgress() int {
+	return nrs.inProgress
+}
diff --git a/bindings/send.go b/bindings/send.go
new file mode 100644
index 0000000000000000000000000000000000000000..a7e35873447758c22cb9ddfc8eddeaf86fe1bb73
--- /dev/null
+++ b/bindings/send.go
@@ -0,0 +1,148 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// SendCMIX sends a "raw" CMIX message payload to the provided
+// recipient. Note that both SendE2E and SendUnsafe call SendCMIX.
+// Returns the round ID of the round the payload was sent or an error
+// if it fails.
+
+// This will return an error if:
+//  - the recipient ID is invalid
+//  - the contents are too long for the message structure
+//  - the message cannot be sent
+
+// This will return the round the message was sent on if it is successfully sent
+// This can be used to register a round event to learn about message delivery.
+// on failure a round id of -1 is returned
+func (c *Client) SendCmix(recipient, contents []byte, parameters string) (int, error) {
+	p, err := params.GetCMIXParameters(parameters)
+	if err != nil {
+		return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
+			err))
+	}
+
+	u, err := id.Unmarshal(recipient)
+	if err != nil {
+		return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
+			err))
+	}
+
+	msg, err := c.api.NewCMIXMessage(contents)
+	if err != nil {
+		return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
+			err))
+	}
+
+	rid, _, err := c.api.SendCMIX(msg, u, p)
+	if err != nil {
+		return -1, errors.New(fmt.Sprintf("Failed to sendCmix: %+v",
+			err))
+	}
+	return int(rid), nil
+}
+
+// SendUnsafe sends an unencrypted payload to the provided recipient
+// with the provided msgType. Returns the list of rounds in which parts
+// of the message were sent or an error if it fails.
+// NOTE: Do not use this function unless you know what you are doing.
+// This function always produces an error message in client logging.
+//
+// Message Types can be found in client/interfaces/message/type.go
+// Make sure to not conflict with ANY default message types with custom types
+func (c *Client) SendUnsafe(recipient, payload []byte,
+	messageType int, parameters string) (*RoundList, error) {
+	p, err := params.GetUnsafeParameters(parameters)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to sendUnsafe: %+v",
+			err))
+	}
+	u, err := id.Unmarshal(recipient)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to sendUnsafe: %+v",
+			err))
+	}
+
+	m := message.Send{
+		Recipient:   u,
+		Payload:     payload,
+		MessageType: message.Type(messageType),
+	}
+
+	rids, err := c.api.SendUnsafe(m, p)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed to sendUnsafe: %+v",
+			err))
+	}
+
+	return &RoundList{list: rids}, nil
+}
+
+// SendE2E sends an end-to-end payload to the provided recipient with
+// the provided msgType. Returns the list of rounds in which parts of
+// the message were sent or an error if it fails.
+//
+// Message Types can be found in client/interfaces/message/type.go
+// Make sure to not conflict with ANY default message types
+func (c *Client) SendE2E(recipient, payload []byte, messageType int, parameters string) (*SendReport, error) {
+	p, err := params.GetE2EParameters(parameters)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed SendE2E: %+v", err))
+	}
+
+	u, err := id.Unmarshal(recipient)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed SendE2E: %+v", err))
+	}
+
+	m := message.Send{
+		Recipient:   u,
+		Payload:     payload,
+		MessageType: message.Type(messageType),
+	}
+
+	rids, mid, err := c.api.SendE2E(m, p)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Failed SendE2E: %+v", err))
+	}
+
+	sr := SendReport{
+		rl:  &RoundList{list: rids},
+		mid: mid,
+	}
+
+	return &sr, nil
+}
+
+// the send report is the mechanisim by which sendE2E returns a single
+type SendReport struct {
+	rl  *RoundList
+	mid e2e.MessageID
+}
+
+func (sr *SendReport) GetRoundList() *RoundList {
+	return sr.rl
+}
+
+func (sr *SendReport) GetMessageID() []byte {
+	return sr.mid[:]
+}
+
+func (sr *SendReport) Marshal() ([]byte, error) {
+	return json.Marshal(sr)
+}
diff --git a/bindings/ud.go b/bindings/ud.go
new file mode 100644
index 0000000000000000000000000000000000000000..a4c3cb5babca0236b546e5cb10fc74b10a98f0f3
--- /dev/null
+++ b/bindings/ud.go
@@ -0,0 +1,185 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/ud"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+//This package wraps the user discovery system
+
+// User Discovery object
+type UserDiscovery struct {
+	ud *ud.Manager
+}
+
+// Returns a new user discovery object. Only call this once. It must be called
+// after StartNetworkFollower is called and will fail if the network has never
+// been contacted.
+// This function technically has a memory leak because it causes both sides of
+// the bindings to think the other is in charge of the client object.
+// In general this is not an issue because the client object should exist
+// for the life of the program.
+func NewUserDiscovery(client *Client) (*UserDiscovery, error) {
+	m, err := ud.NewManager(&client.api, &single.Manager{})
+
+	if err != nil {
+		return nil, err
+	} else {
+		return &UserDiscovery{ud: m}, nil
+	}
+}
+
+// Register registers a user with user discovery. Will return an error if the
+// network signatures are malformed or if the username is taken. Usernames
+// cannot be changed after registration at this time. Will fail if the user is
+// already registered.
+// Identity does not go over cmix, it occurs over normal communications
+func (ud *UserDiscovery) Register(username string) error {
+	return ud.ud.Register(username)
+}
+
+// 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.
+// Will fail if the fact string is not well formed.
+// 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
+// called along with the code to finalize the fact.
+func (ud *UserDiscovery) AddFact(fStr string) (string, error) {
+	f, err := fact.UnstringifyFact(fStr)
+	if err != nil {
+		return "", errors.WithMessage(err, "Failed to add due to "+
+			"malformed fact")
+	}
+
+	return ud.ud.SendRegisterFact(f)
+}
+
+// Confirms a fact first registered via AddFact. The confirmation ID comes from
+// AddFact while the code will come over the associated communications system
+func (ud *UserDiscovery) ConfirmFact(confirmationID, code string) error {
+	return ud.ud.SendConfirmFact(confirmationID, code)
+}
+
+// Removes a previously confirmed fact.  Will fail if the passed fact string is
+// not well formed or if the fact is not associated with this client.
+func (ud *UserDiscovery) RemoveFact(fStr string) error {
+	f, err := fact.UnstringifyFact(fStr)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to remove due to "+
+			"malformed fact")
+	}
+	return ud.ud.RemoveFact(f)
+}
+
+// SearchCallback returns the result of a search
+type SearchCallback interface {
+	Callback(contacts *ContactList, error string)
+}
+
+// Searches for the passed Facts.  The factList is the stringification of a
+// fact list object, look at /bindings/list.go for more on that object.
+// This will reject if that object is malformed. The SearchCallback will return
+// a list of contacts, each having the facts it hit against.
+// This is NOT intended to be used to search for multiple users at once, that
+// can have a privacy reduction. Instead, it is intended to be used to search
+// for a user where multiple pieces of information is known.
+func (ud UserDiscovery) Search(fl string, callback SearchCallback,
+	timeoutMS int) error {
+	factList, _, err := fact.UnstringifyFactList(fl)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to search due to "+
+			"malformed fact list")
+	}
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	cb := func(cl []contact.Contact, err error) {
+		var contactList *ContactList
+		var errStr string
+		if err == nil {
+			contactList = &ContactList{list: cl}
+		} else {
+			errStr = err.Error()
+		}
+		callback.Callback(contactList, errStr)
+	}
+	return ud.ud.Search(factList, cb, timeout)
+}
+
+// SingleSearchCallback returns the result of a single search
+type SingleSearchCallback interface {
+	Callback(contact *Contact, error string)
+}
+
+// Searches for the passed Facts.  The fact is the stringification of a
+// fact object, look at /bindings/contact.go for more on that object.
+// This will reject if that object is malformed. The SearchCallback will return
+// a list of contacts, each having the facts it hit against.
+// This only searches for a single fact at a time. It is intended to make some
+// simple use cases of the API easier.
+func (ud UserDiscovery) SearchSingle(f string, callback SingleSearchCallback,
+	timeoutMS int) error {
+	fObj, err := fact.UnstringifyFact(f)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to single search due "+
+			"to malformed fact")
+	}
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	cb := func(cl []contact.Contact, err error) {
+		var c *Contact
+		var errStr string
+		if err == nil {
+			c = &Contact{c: &cl[0]}
+		} else {
+			errStr = err.Error()
+		}
+		callback.Callback(c, errStr)
+	}
+	return ud.ud.Search([]fact.Fact{fObj}, cb, timeout)
+}
+
+// SingleSearchCallback returns the result of a single search
+type LookupCallback interface {
+	Callback(contact *Contact, error string)
+}
+
+// Looks for the contact object associated with the given userID.  The
+// id is the byte representation of an id.
+// This will reject if that id is malformed. The LookupCallback will return
+// the associated contact if it exists.
+func (ud UserDiscovery) Lookup(idBytes []byte, callback LookupCallback,
+	timeoutMS int) error {
+
+	uid, err := id.Unmarshal(idBytes)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to lookup due to "+
+			"malformed id")
+	}
+
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	cb := func(cl contact.Contact, err error) {
+		var c *Contact
+		var errStr string
+		if err == nil {
+			c = &Contact{c: &cl}
+		} else {
+			errStr = err.Error()
+		}
+		callback.Callback(c, errStr)
+	}
+
+	return ud.ud.Lookup(uid, cb, timeout)
+
+}
diff --git a/bindings/user.go b/bindings/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..722fe6af8dd48a3c80ef102750a3110ec48ff54d
--- /dev/null
+++ b/bindings/user.go
@@ -0,0 +1,74 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package bindings
+
+import (
+	"gitlab.com/elixxir/client/interfaces/user"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+)
+
+type User struct {
+	u *user.User
+}
+
+func (u *User) GetTransmissionID() []byte {
+	return u.u.TransmissionID.Marshal()
+}
+
+func (u *User) GetReceptionID() []byte {
+	return u.u.ReceptionID.Marshal()
+}
+
+func (u *User) GetTransmissionSalt() []byte {
+	return u.u.TransmissionSalt
+}
+
+func (u *User) GetReceptionSalt() []byte {
+	return u.u.ReceptionSalt
+}
+
+func (u *User) GetTransmissionRSAPrivateKeyPem() []byte {
+	return rsa.CreatePrivateKeyPem(u.u.TransmissionRSA)
+}
+
+func (u *User) GetTransmissionRSAPublicKeyPem() []byte {
+	return rsa.CreatePublicKeyPem(u.u.TransmissionRSA.GetPublic())
+}
+
+func (u *User) GetReceptionRSAPrivateKeyPem() []byte {
+	return rsa.CreatePrivateKeyPem(u.u.ReceptionRSA)
+}
+
+func (u *User) GetReceptionRSAPublicKeyPem() []byte {
+	return rsa.CreatePublicKeyPem(u.u.ReceptionRSA.GetPublic())
+}
+
+func (u *User) IsPrecanned() bool {
+	return u.u.Precanned
+}
+
+func (u *User) GetCmixDhPrivateKey() []byte {
+	return u.u.CmixDhPrivateKey.Bytes()
+}
+
+func (u *User) GetCmixDhPublicKey() []byte {
+	return u.u.CmixDhPublicKey.Bytes()
+}
+
+func (u *User) GetE2EDhPrivateKey() []byte {
+	return u.u.E2eDhPrivateKey.Bytes()
+}
+
+func (u *User) GetE2EDhPublicKey() []byte {
+	return u.u.E2eDhPublicKey.Bytes()
+}
+
+func (u *User) GetContact() *Contact {
+	c := u.u.GetContact()
+	return &Contact{c: &c}
+}
diff --git a/bots/README.md b/bots/README.md
deleted file mode 100644
index f759d6a5567a145801bf468d5049812b8222aa79..0000000000000000000000000000000000000000
--- a/bots/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Use this module to write code for working with different cMix bots
diff --git a/bots/bots.go b/bots/bots.go
deleted file mode 100644
index 6cf0bdc3cee88b91aa0bc11a279cf0789a3a39ce..0000000000000000000000000000000000000000
--- a/bots/bots.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package bots
-
-import (
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/io"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"gitlab.com/xx_network/comms/connect"
-)
-
-var session user.Session
-var topology *connect.Circuit
-var comms io.Communications
-var transmissionHost *connect.Host
-
-type channelResponseListener chan string
-
-func (l *channelResponseListener) Hear(msg switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	m := msg.(*parse.Message)
-	*l <- string(m.Body)
-}
-
-var pushKeyResponseListener channelResponseListener
-var getKeyResponseListener channelResponseListener
-var registerResponseListener channelResponseListener
-var searchResponseListener channelResponseListener
-var nicknameResponseListener channelResponseListener
-
-// Nickname request listener
-type nickReqListener struct{}
-
-// Nickname listener simply replies with message containing user's nick
-func (l *nickReqListener) Hear(msg switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	m := msg.(*parse.Message)
-	nick := session.GetCurrentUser().Username
-	resp := parse.Pack(&parse.TypedBody{
-		MessageType: int32(cmixproto.Type_NICKNAME_RESPONSE),
-		Body:        []byte(nick),
-	})
-	globals.Log.DEBUG.Printf("Sending nickname response to user %v", *m.Sender)
-	sendCommand(m.Sender, resp)
-}
-
-var nicknameRequestListener nickReqListener
-
-// InitBots is called internally by the Login API
-func InitBots(s user.Session, m io.Communications, top *connect.Circuit, host *connect.Host) {
-	// FIXME: these all need to be used in non-blocking threads if we are
-	// going to do it this way...
-	msgBufSize := 100
-	pushKeyResponseListener = make(channelResponseListener, msgBufSize)
-	getKeyResponseListener = make(channelResponseListener, msgBufSize)
-	registerResponseListener = make(channelResponseListener, msgBufSize)
-	searchResponseListener = make(channelResponseListener, msgBufSize)
-	nicknameRequestListener = nickReqListener{}
-	nicknameResponseListener = make(channelResponseListener, msgBufSize)
-
-	session = s
-	topology = top
-	comms = m
-	transmissionHost = host
-
-	l := session.GetSwitchboard()
-
-	l.Register(&id.UDB, int32(cmixproto.Type_UDB_PUSH_KEY_RESPONSE),
-		&pushKeyResponseListener)
-	l.Register(&id.UDB, int32(cmixproto.Type_UDB_GET_KEY_RESPONSE),
-		&getKeyResponseListener)
-	l.Register(&id.UDB, int32(cmixproto.Type_UDB_REGISTER_RESPONSE),
-		&registerResponseListener)
-	l.Register(&id.UDB, int32(cmixproto.Type_UDB_SEARCH_RESPONSE),
-		&searchResponseListener)
-	l.Register(&id.ZeroUser,
-		int32(cmixproto.Type_NICKNAME_REQUEST), &nicknameRequestListener)
-	l.Register(&id.ZeroUser,
-		int32(cmixproto.Type_NICKNAME_RESPONSE), &nicknameResponseListener)
-}
-
-// sendCommand sends a command to the udb. This doesn't block.
-// Callers that need to wait on a response should implement waiting with a
-// listener.
-func sendCommand(botID *id.ID, command []byte) error {
-	return comms.SendMessage(session, topology, botID,
-		parse.Unencrypted, command, transmissionHost)
-}
-
-// Nickname Lookup function
-func LookupNick(user *id.ID) (string, error) {
-	globals.Log.DEBUG.Printf("Sending nickname request to user %v", *user)
-	msg := parse.Pack(&parse.TypedBody{
-		MessageType: int32(cmixproto.Type_NICKNAME_REQUEST),
-		Body:        []byte{},
-	})
-
-	err := sendCommand(user, msg)
-	if err != nil {
-		return "", err
-	}
-
-	nickResponse := <-nicknameResponseListener
-	return nickResponse, nil
-}
diff --git a/bots/bots_test.go b/bots/bots_test.go
deleted file mode 100644
index 6f0c7edba4e04504b780cf8039028940a7e25986..0000000000000000000000000000000000000000
--- a/bots/bots_test.go
+++ /dev/null
@@ -1,268 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// Package bot functions for working with the user discovery bot (UDB)
-package bots
-
-import (
-	"encoding/base64"
-	"encoding/binary"
-	"fmt"
-	"github.com/pkg/errors"
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"os"
-	"strings"
-	"testing"
-	"time"
-)
-
-var ListenCh chan *format.Message
-
-type dummyMessaging struct {
-	listener chan *format.Message
-}
-
-// SendMessage to the server
-func (d *dummyMessaging) SendMessage(sess user.Session,
-	topology *connect.Circuit,
-	recipientID *id.ID,
-	cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	jww.INFO.Printf("Sending: %s", string(message))
-	return nil
-}
-
-// SendMessage without partitions to the server
-func (d *dummyMessaging) SendMessageNoPartition(sess user.Session,
-	topology *connect.Circuit,
-	recipientID *id.ID,
-	cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	jww.INFO.Printf("Sending: %s", string(message))
-	return nil
-}
-
-// MessageReceiver thread to get new messages
-func (d *dummyMessaging) MessageReceiver(session user.Session,
-	delay time.Duration, transmissionHost *connect.Host, callback func(error)) {
-}
-
-var pubKeyBits string
-var keyFingerprint string
-var pubKey []byte
-
-func TestMain(m *testing.M) {
-	u := &user.User{
-		User:     new(id.ID),
-		Username: "Bernie",
-	}
-	binary.BigEndian.PutUint64(u.User[:], 18)
-	u.User.SetType(id.User)
-
-	cmixGrp, e2eGrp := getGroups()
-
-	fakeSession := user.NewSession(&globals.RamStorage{},
-		u, nil, nil, nil,
-		nil, nil, nil, nil,
-		cmixGrp, e2eGrp, "password")
-	fakeComm := &dummyMessaging{
-		listener: ListenCh,
-	}
-	h := connect.Host{}
-	nodeID := new(id.ID)
-	nodeID.SetType(id.Node)
-	topology := connect.NewCircuit([]*id.ID{nodeID})
-
-	InitBots(fakeSession, fakeComm, topology, &h)
-
-	// Make the reception channels buffered for this test
-	// which overwrites the channels registered in InitBots
-	pushKeyResponseListener = make(channelResponseListener, 100)
-	getKeyResponseListener = make(channelResponseListener, 100)
-	registerResponseListener = make(channelResponseListener, 100)
-	searchResponseListener = make(channelResponseListener, 100)
-
-	pubKeyBits = "S8KXBczy0jins9uS4LgBPt0bkFl8t00MnZmExQ6GcOcu8O7DKgAsNzLU7a+gMTbIsS995IL/kuFF8wcBaQJBY23095PMSQ/nMuetzhk9HdXxrGIiKBo3C/n4SClpq4H+PoF9XziEVKua8JxGM2o83KiCK3tNUpaZbAAElkjueY7wuD96h4oaA+WV5Nh87cnIZ+fAG0uLve2LSHZ0FBZb3glOpNAOv7PFWkvN2BO37ztOQCXTJe72Y5ReoYn7nWVNxGUh0ilal+BRuJt1GZ7whOGDRE0IXfURIoK2yjyAnyZJWWMhfGsL5S6iL4aXUs03mc8BHKRq3HRjvTE10l3YFA=="
-	pubKey, _ = base64.StdEncoding.DecodeString(pubKeyBits)
-
-	keyFingerprint = fingerprint(pubKey)
-
-	os.Exit(m.Run())
-}
-
-// TestRegister smoke tests the registration functionality.
-func TestRegister(t *testing.T) {
-	// Send response messages from fake UDB in advance
-	pushKeyResponseListener <- fmt.Sprintf("PUSHKEY COMPLETE %s", keyFingerprint)
-	registerResponseListener <- "REGISTRATION COMPLETE"
-
-	dummyRegState := func(int) {
-		return
-	}
-	err := Register("EMAIL", "rick@elixxir.io", pubKey, dummyRegState, 30*time.Second)
-	if err != nil {
-		t.Errorf("Registration failure: %s", err.Error())
-	}
-	// Send response messages from fake UDB in advance
-	pushKeyResponseListener <- fmt.Sprintf("PUSHKEY Failed: Could not push key %s becasue key already exists", keyFingerprint)
-	err = Register("EMAIL", "rick@elixxir.io", pubKey, dummyRegState, 30*time.Second)
-	if err == nil {
-		t.Errorf("Registration duplicate did not fail")
-	}
-}
-
-// TestSearch smoke tests the search function
-func TestSearch(t *testing.T) {
-	publicKeyString := base64.StdEncoding.EncodeToString(pubKey)
-	//uid := id.NewIdFromUInt(26, id.User, t)
-	//serRetUid := base64.StdEncoding.EncodeToString(uid[:])
-	//result, _ := base64.StdEncoding.DecodeString(serRetUid)
-	//t.Fatal(serRetUid)
-	//t.Fatal(len(result))
-
-	// Send response messages from fake UDB in advance
-	searchResponseListener <- "blah@elixxir.io FOUND UR69db14ZyicpZVqJ1HFC5rk9UZ8817aV6+VHmrJpGc= AAAAAAAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD 8oKh7TYG4KxQcBAymoXPBHSD/uga9pX3Mn/jKhvcD8M="
-	getKeyResponseListener <- fmt.Sprintf("GETKEY %s %s", keyFingerprint,
-		publicKeyString)
-
-	dummySearchState := func(int) {
-		return
-	}
-
-	searchedUser, _, err := Search("EMAIL", "blah@elixxir.io",
-		dummySearchState, 30*time.Second)
-	if err != nil {
-		t.Errorf("Error on Search: %s", err.Error())
-	}
-	if !searchedUser.Cmp(id.NewIdFromUInt(26, id.User, t)) {
-		t.Errorf("Search did not return user ID 26! returned %s", searchedUser)
-	}
-	//Test the timeout capabilities
-	searchedUser, _, err = Search("EMAIL", "blah@elixxir.io", dummySearchState, 1*time.Millisecond)
-	if strings.Compare(err.Error(), "UDB search timeout exceeded on user lookup") != 0 {
-		t.Errorf("error: %v", err)
-	}
-}
-
-// messages using switchboard
-// Test LookupNick function
-func TestNicknameFunctions(t *testing.T) {
-	// Test receiving a nickname request
-	msg := &parse.Message{
-		Sender: session.GetCurrentUser().User,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_NICKNAME_REQUEST),
-			Body:        []byte{},
-		},
-		InferredType: parse.Unencrypted,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-
-	// Test nickname lookup
-
-	// send response to switchboard
-	msg = &parse.Message{
-		Sender: session.GetCurrentUser().User,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_NICKNAME_RESPONSE),
-			Body:        []byte(session.GetCurrentUser().Username),
-		},
-		InferredType: parse.Unencrypted,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-	// AFter sending the message, perform the lookup to read it
-	nick, err := LookupNick(session.GetCurrentUser().User)
-	if err != nil {
-		t.Errorf("Error on LookupNick: %s", err.Error())
-	}
-	if nick != session.GetCurrentUser().Username {
-		t.Errorf("LookupNick returned wrong value. Expected %s,"+
-			" Got %s", session.GetCurrentUser().Username, nick)
-	}
-}
-
-type errorMessaging struct{}
-
-// SendMessage that just errors out
-func (e *errorMessaging) SendMessage(sess user.Session,
-	topology *connect.Circuit,
-	recipientID *id.ID,
-	cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	return errors.New("This is an error")
-}
-
-// SendMessage no partition that just errors out
-func (e *errorMessaging) SendMessageNoPartition(sess user.Session,
-	topology *connect.Circuit,
-	recipientID *id.ID,
-	cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	return errors.New("This is an error")
-}
-
-// MessageReceiver thread to get new messages
-func (e *errorMessaging) MessageReceiver(session user.Session,
-	delay time.Duration, transmissionHost *connect.Host, callback func(error)) {
-}
-
-// Test LookupNick returns error on sending problem
-func TestLookupNick_error(t *testing.T) {
-	// Replace comms with errorMessaging
-	comms = &errorMessaging{}
-	_, err := LookupNick(session.GetCurrentUser().User)
-	if err == nil {
-		t.Errorf("LookupNick should have returned an error")
-	}
-}
-
-func getGroups() (cmixGrp *cyclic.Group, e2eGrp *cyclic.Group) {
-
-	cmixPrime := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
-		"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
-		"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
-		"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
-		"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
-		"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
-		"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
-		"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
-		"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
-		"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
-		"15728E5A8AACAA68FFFFFFFFFFFFFFFF"
-
-	e2ePrime := "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
-		"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
-		"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
-		"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
-		"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
-		"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
-		"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
-		"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
-		"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
-		"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
-		"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
-		"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
-		"847AEF49F66E43873"
-
-	cmixGrp = cyclic.NewGroup(large.NewIntFromString(cmixPrime, 16),
-		large.NewIntFromUInt(2))
-
-	e2eGrp = cyclic.NewGroup(large.NewIntFromString(e2ePrime, 16),
-		large.NewIntFromUInt(2))
-
-	return
-}
diff --git a/bots/userDiscovery.go b/bots/userDiscovery.go
deleted file mode 100644
index d84976c955d24e3814939419a7d3c8541dae07fc..0000000000000000000000000000000000000000
--- a/bots/userDiscovery.go
+++ /dev/null
@@ -1,279 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// Package bot functions for working with the user discovery bot (UDB)
-package bots
-
-import (
-	"bytes"
-	"crypto/sha256"
-	"encoding/base64"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/primitives/id"
-	"strings"
-	"time"
-)
-
-var pushkeyExpected = "PUSHKEY COMPLETE"
-
-// Register sends a registration message to the UDB. It does this by sending 2
-// PUSHKEY messages to the UDB, then calling UDB's REGISTER command.
-// If any of the commands fail, it returns an error.
-// valueType: Currently only "EMAIL"
-func Register(valueType, value string, publicKey []byte, regStatus func(int), timeout time.Duration) error {
-	globals.Log.DEBUG.Printf("Running register for %v, %v, %q", valueType,
-		value, publicKey)
-
-	registerTimeout := time.NewTimer(timeout)
-
-	var err error
-	if valueType == "EMAIL" {
-		value, err = hashAndEncode(strings.ToLower(value))
-		if err != nil {
-			return fmt.Errorf("Could not hash and encode email %s: %+v", value, err)
-		}
-	}
-
-	keyFP := fingerprint(publicKey)
-
-	regStatus(globals.UDB_REG_PUSHKEY)
-	// push key and error if it already exists
-	err = pushKey(keyFP, publicKey)
-
-	if err != nil {
-		return errors.Wrap(err, "Could not PUSHKEY")
-	}
-
-	var response string
-
-	// wait for the response to submitting the key against the timeout.
-	// discard responses from other searches
-	submitted := false
-
-	for !submitted {
-		select {
-		case response = <-pushKeyResponseListener:
-			if strings.Contains(response, keyFP) {
-				if strings.Contains(response, pushkeyExpected) {
-					submitted = true
-				} else {
-					err := errors.New(response)
-					return errors.Wrap(err, "PushKey failed")
-				}
-			}
-		case <-registerTimeout.C:
-			return errors.New("UDB register timeout exceeded on key submission")
-		}
-	}
-
-	//send the user information to udb
-	msgBody := parse.Pack(&parse.TypedBody{
-		MessageType: int32(cmixproto.Type_UDB_REGISTER),
-		Body:        []byte(fmt.Sprintf("%s %s %s", valueType, value, keyFP)),
-	})
-
-	regStatus(globals.UDB_REG_PUSHUSER)
-
-	// Send register command
-	// Send register command
-	err = sendCommand(&id.UDB, msgBody)
-	if err != nil {
-		return errors.Wrap(err, "Could not Push User")
-	}
-
-	// wait for the response to submitting the key against the timeout.
-	// discard responses from other searches
-	complete := false
-
-	for !complete {
-		select {
-		case response = <-registerResponseListener:
-			expected := "REGISTRATION COMPLETE"
-			unavalibleReg := "Can not register with existing email"
-			if strings.Contains(response, expected) {
-				complete = true
-			} else if strings.Contains(response, value) && strings.Contains(response, unavalibleReg) {
-				return errors.New("Cannot register with existing username")
-			}
-		case <-registerTimeout.C:
-			return errors.New("UDB register timeout exceeded on user submission")
-		}
-	}
-
-	return nil
-}
-
-// Search returns a userID and public key based on the search criteria
-// it accepts a valueType of EMAIL and value of an e-mail address, and
-// returns a map of userid -> public key
-func Search(valueType, value string, searchStatus func(int), timeout time.Duration) (*id.ID, []byte, error) {
-	globals.Log.DEBUG.Printf("Running search for %v, %v", valueType, value)
-
-	searchTimeout := time.NewTimer(timeout)
-
-	var err error
-	if valueType == "EMAIL" {
-		value, err = hashAndEncode(strings.ToLower(value))
-		if err != nil {
-			return nil, nil, fmt.Errorf("Could not hash and encode email %s: %+v", value, err)
-		}
-	}
-
-	searchStatus(globals.UDB_SEARCH_LOOK)
-
-	msgBody := parse.Pack(&parse.TypedBody{
-		MessageType: int32(cmixproto.Type_UDB_SEARCH),
-		Body:        []byte(fmt.Sprintf("%s %s", valueType, value)),
-	})
-	err = sendCommand(&id.UDB, msgBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var response string
-
-	// wait for the response to searching for the value against the timeout.
-	// discard responses from other searches
-	found := false
-
-	for !found {
-		select {
-		case response = <-searchResponseListener:
-			empty := fmt.Sprintf("SEARCH %s NOTFOUND", value)
-			if response == empty {
-				return nil, nil, nil
-			}
-			if strings.Contains(response, value) {
-				found = true
-			}
-		case <-searchTimeout.C:
-			return nil, nil, errors.New("UDB search timeout exceeded on user lookup")
-		}
-	}
-
-	// While search returns more than 1 result, we only process the first
-	cMixUID, keyFP, err := parseSearch(response)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	searchStatus(globals.UDB_SEARCH_GETKEY)
-
-	// Get the full key and decode it
-	msgBody = parse.Pack(&parse.TypedBody{
-		MessageType: int32(cmixproto.Type_UDB_GET_KEY),
-		Body:        []byte(keyFP),
-	})
-	err = sendCommand(&id.UDB, msgBody)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// wait for the response to searching for the key against the timeout.
-	// discard responses from other searches
-	found = false
-	for !found {
-		select {
-		case response = <-getKeyResponseListener:
-			if strings.Contains(response, keyFP) {
-				found = true
-			}
-		case <-searchTimeout.C:
-			return nil, nil, errors.New("UDB search timeout exceeded on key lookup")
-		}
-	}
-
-	publicKey := parseGetKey(response)
-
-	return cMixUID, publicKey, nil
-}
-
-func hashAndEncode(s string) (string, error) {
-	buf := new(bytes.Buffer)
-	encoder := base64.NewEncoder(base64.StdEncoding, buf)
-
-	sha := sha256.New()
-	sha.Write([]byte(s))
-	hashed := sha.Sum(nil)
-
-	_, err := encoder.Write(hashed)
-	if err != nil {
-		err = errors.New(fmt.Sprintf("Error base64 encoding string %s: %+v", s, err))
-		return "", err
-	}
-
-	err = encoder.Close()
-	if err != nil {
-		err = errors.New(fmt.Sprintf("Error closing encoder: %+v", err))
-		return "", err
-	}
-
-	return buf.String(), nil
-}
-
-// parseSearch parses the responses from SEARCH. It returns the user's id and
-// the user's public key fingerprint
-func parseSearch(msg string) (*id.ID, string, error) {
-	globals.Log.DEBUG.Printf("Parsing search response: %v", msg)
-	resParts := strings.Split(msg, " ")
-	if len(resParts) != 5 {
-		return &id.ZeroUser, "", errors.WithMessagef(errors.New("Invalid response from search"), ": %s", msg)
-	}
-
-	cMixUIDBytes, err := base64.StdEncoding.DecodeString(resParts[3])
-	if err != nil {
-		return &id.ZeroUser, "", errors.WithMessagef(errors.New("Couldn't parse search cMix UID"), ": %s", msg)
-	}
-	cMixUID, err := id.Unmarshal(cMixUIDBytes)
-	if err != nil {
-		return &id.ZeroUser, "", err
-	}
-
-	return cMixUID, resParts[4], nil
-}
-
-// parseGetKey parses the responses from GETKEY. It returns the
-// corresponding public key.
-func parseGetKey(msg string) []byte {
-	resParts := strings.Split(msg, " ")
-	if len(resParts) != 3 {
-		globals.Log.WARN.Printf("Invalid response from GETKEY: %s", msg)
-		return nil
-	}
-	keymat, err := base64.StdEncoding.DecodeString(resParts[2])
-	if err != nil || len(keymat) == 0 {
-		globals.Log.WARN.Printf("Couldn't decode GETKEY keymat: %s", msg)
-		return nil
-	}
-	return keymat
-}
-
-// pushKey uploads the users' public key
-func pushKey(keyFP string, publicKey []byte) error {
-	publicKeyString := base64.StdEncoding.EncodeToString(publicKey)
-	globals.Log.DEBUG.Printf("Running pushkey for %v, %v, %v", id.UDB, keyFP,
-		publicKeyString)
-
-	pushKeyMsg := fmt.Sprintf("%s %s", keyFP, publicKeyString)
-
-	return sendCommand(&id.UDB, parse.Pack(&parse.TypedBody{
-		MessageType: int32(cmixproto.Type_UDB_PUSH_KEY),
-		Body:        []byte(pushKeyMsg),
-	}))
-}
-
-// fingerprint generates the same fingerprint that the udb should generate
-// TODO: Maybe move this helper to crypto module?
-func fingerprint(publicKey []byte) string {
-	h, _ := hash.NewCMixHash() // why does this return an err and not panic?
-	h.Write(publicKey)
-	return base64.StdEncoding.EncodeToString(h.Sum(nil))
-}
diff --git a/cmd/getndf.go b/cmd/getndf.go
new file mode 100644
index 0000000000000000000000000000000000000000..e794fb4d38b6f3d7e0ca82b2b3a2576afef0d6d6
--- /dev/null
+++ b/cmd/getndf.go
@@ -0,0 +1,119 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Package cmd initializes the CLI and config parsers as well as the logger.
+package cmd
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/viper"
+	// "gitlab.com/elixxir/client/interfaces/contact"
+	// "gitlab.com/elixxir/client/interfaces/message"
+	// "gitlab.com/elixxir/client/switchboard"
+	// "gitlab.com/elixxir/client/ud"
+	// "gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/xx_network/comms/connect"
+	//"time"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
+)
+
+const opensslCertDL = "openssl s_client -showcerts -connect ip:port < " +
+	"/dev/null 2>&1 | openssl x509 -outform PEM > certfile.pem"
+
+// getNDFCmd user discovery subcommand, allowing user lookup and registration for
+// allowing others to search.
+// This basically runs a client for these functions with the UD module enabled.
+// Normally, clients don't need it so it is not loaded for the rest of the
+// commands.
+var getNDFCmd = &cobra.Command{
+	Use: "getndf",
+	Short: "Download the network definition file from the network " +
+		"and print it.",
+	Args: cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		gwHost := viper.GetString("gwhost")
+		permHost := viper.GetString("permhost")
+		certPath := viper.GetString("cert")
+
+		// Load the certificate
+		var cert []byte
+		if certPath != "" {
+			cert, _ = utils.ReadFile(certPath)
+		}
+		if len(cert) == 0 {
+			jww.FATAL.Panicf("Could not load a certificate, "+
+				"provide a certificate file with --cert.\n\n"+
+				"You can download a cert using openssl:\n\n%s",
+				opensslCertDL)
+		}
+
+		params := connect.GetDefaultHostParams()
+		params.AuthEnabled = false
+		comms, _ := client.NewClientComms(nil, nil, nil, nil)
+		// Gateway lookup
+		if gwHost != "" {
+			host, _ := connect.NewHost(&id.TempGateway, gwHost,
+				cert, params)
+			pollMsg := &pb.GatewayPoll{
+				Partial: &pb.NDFHash{
+					Hash: nil,
+				},
+				LastUpdate:  uint64(0),
+				ReceptionID: id.DummyUser.Marshal(),
+			}
+			resp, err := comms.SendPoll(host, pollMsg)
+			if err != nil {
+				jww.FATAL.Panicf("Unable to poll %s for NDF:"+
+					" %+v",
+					gwHost, err)
+			}
+			fmt.Printf("%s", resp.PartialNDF.Ndf)
+			return
+		}
+
+		if permHost != "" {
+			host, _ := connect.NewHost(&id.Permissioning, permHost,
+				cert, params)
+			pollMsg := &pb.NDFHash{
+				Hash: []byte("DummyUserRequest"),
+			}
+			resp, err := comms.RequestNdf(host, pollMsg)
+			if err != nil {
+				jww.FATAL.Panicf("Unable to ask %s for NDF:"+
+					" %+v",
+					permHost, err)
+			}
+			fmt.Printf("%s", resp.Ndf)
+			return
+		}
+
+		fmt.Println("Enter --gwhost or --permhost and --cert please")
+	},
+}
+
+func init() {
+	getNDFCmd.Flags().StringP("gwhost", "", "",
+		"Poll this gateway host:port for the NDF")
+	viper.BindPFlag("gwhost",
+		getNDFCmd.Flags().Lookup("gwhost"))
+	getNDFCmd.Flags().StringP("permhost", "", "",
+		"Poll this permissioning host:port for the NDF")
+	viper.BindPFlag("permhost",
+		getNDFCmd.Flags().Lookup("permhost"))
+
+	getNDFCmd.Flags().StringP("cert", "", "",
+		"Check with the TLS certificate at this path")
+	viper.BindPFlag("cert",
+		getNDFCmd.Flags().Lookup("cert"))
+
+	rootCmd.AddCommand(getNDFCmd)
+}
diff --git a/cmd/init.go b/cmd/init.go
new file mode 100644
index 0000000000000000000000000000000000000000..cf19521f066d1e9e878524a7ee75fd3471cf6b7c
--- /dev/null
+++ b/cmd/init.go
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Package cmd initializes the CLI and config parsers as well as the logger.
+package cmd
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+// initCmd creates a new user object with the given NDF
+var initCmd = &cobra.Command{
+	Use:   "init",
+	Short: ("Initialize a user ID but do not connect to the network"),
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		client := createClient()
+		user := client.GetUser()
+		jww.INFO.Printf("User: %s", user.ReceptionID)
+		writeContact(user.GetContact())
+		fmt.Printf("%s\n", user.ReceptionID)
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(initCmd)
+}
diff --git a/cmd/root.go b/cmd/root.go
index c61e825a24dcb4eae57cae5fd6c61eca64955142..fb1efae75e8475ef671c9fbddd5b94bba61f0d9f 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,8 +1,9 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
 
 // Package cmd initializes the CLI and config parsers as well as the logger.
 package cmd
@@ -10,54 +11,24 @@ package cmd
 import (
 	"encoding/base64"
 	"encoding/binary"
+	"encoding/hex"
 	"fmt"
-	"github.com/golang/protobuf/proto"
 	"github.com/spf13/cobra"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
 	"gitlab.com/elixxir/client/api"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"gitlab.com/elixxir/primitives/utils"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/xx_network/primitives/id"
 	"io/ioutil"
 	"os"
 	"strconv"
 	"strings"
-	"sync/atomic"
 	"time"
 )
 
-var verbose bool
-var userId uint64
-var privateKeyPath string
-var destinationUserId uint64
-var destinationUserIDBase64 string
-var message string
-var sessionFile string
-var noBlockingTransmission bool
-var rateLimiting uint32
-var registrationCode string
-var username string
-var end2end bool
-var keyParams []string
-var ndfPath string
-var skipNDFVerification bool
-var ndfPubKey string
-var sessFilePassword string
-var noTLS bool
-var searchForUser string
-var waitForMessages uint
-var messageTimeout uint
-var messageCnt uint
-var precanned = false
-var logPath string = ""
-var notificationToken string
-
 // Execute adds all child commands to the root command and sets flags
 // appropriately.  This is called by main.main(). It only needs to
 // happen once to the rootCmd.
@@ -68,521 +39,554 @@ func Execute() {
 	}
 }
 
-func sessionInitialization() (*id.ID, string, *api.Client) {
-	var err error
-	register := false
-
-	var client *api.Client
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "client",
+	Short: "Runs a client for cMix anonymous communication platform",
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
 
-	// Read in the network definition file and save as string
-	ndfBytes, err := utils.ReadFile(ndfPath)
-	if err != nil {
-		globals.Log.FATAL.Panicf("Could not read network definition file: %v", err)
-	}
+		client := initClient()
+
+		user := client.GetUser()
+		jww.INFO.Printf("User: %s", user.ReceptionID)
+		writeContact(user.GetContact())
+
+		// Set up reception handler
+		swboard := client.GetSwitchboard()
+		recvCh := make(chan message.Receive, 10000)
+		listenerID := swboard.RegisterChannel("DefaultCLIReceiver",
+			switchboard.AnyUser(), message.Text, recvCh)
+		jww.INFO.Printf("Message ListenerID: %v", listenerID)
+
+		// Set up auth request handler, which simply prints the
+		// user id of the requestor.
+		authMgr := client.GetAuthRegistrar()
+		authMgr.AddGeneralRequestCallback(printChanRequest)
+
+		// If unsafe channels, add auto-acceptor
+		num_channels_confirmed := 0
+		authMgr.AddGeneralConfirmCallback(func(
+			partner contact.Contact) {
+			jww.INFO.Printf("Channel Confirmed: %s",
+				partner.ID)
+			num_channels_confirmed++
+		})
+		if viper.GetBool("unsafe-channel-creation") {
+			authMgr.AddGeneralRequestCallback(func(
+				requestor contact.Contact, message string) {
+				jww.INFO.Printf("Channel Request: %s",
+					requestor.ID)
+				err := client.ConfirmAuthenticatedChannel(
+					requestor)
+				if err != nil {
+					jww.FATAL.Panicf("%+v", err)
+				}
+				num_channels_confirmed++
+			})
+		}
 
-	// Check if the NDF verify flag is set
-	if skipNDFVerification {
-		ndfPubKey = ""
-		globals.Log.WARN.Println("Skipping NDF verification")
-	} else {
-		pkFile, err := os.Open(ndfPubKey)
+		_, err := client.StartNetworkFollower()
 		if err != nil {
-			globals.Log.FATAL.Panicf("Could not open cert file: %v",
-				err)
+			jww.FATAL.Panicf("%+v", err)
 		}
 
-		pkBytes, err := ioutil.ReadAll(pkFile)
-		if err != nil {
-			globals.Log.FATAL.Panicf("Could not read cert file: %v",
-				err)
+		// Wait until connected or crash on timeout
+		connected := make(chan bool, 10)
+		client.GetHealth().AddChannel(connected)
+		waitUntilConnected(connected)
+
+		// After connection, make sure we have registered with at least
+		// 85% of the nodes
+		numReg := 1
+		numNotReg := 100
+		for numReg < 3*numNotReg {
+			time.Sleep(1 * time.Second)
+			numReg, numNotReg, err = client.GetNodeRegistrationStatus()
+			if err != nil {
+				jww.FATAL.Panicf("%+v", err)
+			}
+			jww.INFO.Printf("Registering with nodes (%d/%d)...",
+				numReg, (numReg + numNotReg))
 		}
-		ndfPubKey = string(pkBytes)
-	}
 
-	// Verify the signature
-	globals.Log.DEBUG.Println("Verifying NDF...")
-	ndfJSON := api.VerifyNDF(string(ndfBytes), ndfPubKey)
-	globals.Log.DEBUG.Printf("   NDF Verified")
+		// Send Messages
+		msgBody := viper.GetString("message")
 
-	//If no session file is passed initialize with RAM Storage
-	if sessionFile == "" {
-		client, err = api.NewClient(&globals.RamStorage{}, "", "", ndfJSON)
-		if err != nil {
-			globals.Log.ERROR.Printf("Could Not Initialize Ram Storage: %s\n",
-				err.Error())
-			return &id.ZeroUser, "", nil
+		isPrecanPartner := false
+		recipientContact := readContact()
+		recipientID := recipientContact.ID
+
+		// Try to get recipientID from destid
+		if recipientID == nil {
+			recipientID, isPrecanPartner = parseRecipient(
+				viper.GetString("destid"))
 		}
-		globals.Log.INFO.Println("Initialized Ram Storage")
-		register = true
-	} else {
 
-		var sessionA, sessionB string
+		// Set it to myself
+		if recipientID == nil {
+			jww.INFO.Printf("sending message to self")
+			recipientID = user.ReceptionID
+			recipientContact = user.GetContact()
+		}
 
-		locs := strings.Split(sessionFile, ",")
+		time.Sleep(10 * time.Second)
 
-		if len(locs) == 2 {
-			sessionA = locs[0]
-			sessionB = locs[1]
-		} else {
-			sessionA = sessionFile
-			sessionB = sessionFile + "-2"
+		// Accept auth request for this recipient
+		if viper.GetBool("accept-channel") {
+			acceptChannel(client, recipientID)
 		}
 
-		//If a session file is passed, check if it's valid
-		_, err1 := os.Stat(sessionA)
-		_, err2 := os.Stat(sessionB)
+		// Send unsafe messages or not?
+		unsafe := viper.GetBool("unsafe")
+		assumeAuth := viper.GetBool("assume-auth-channel")
+		if !unsafe && !assumeAuth {
+			addAuthenticatedChannel(client, recipientID,
+				recipientContact, isPrecanPartner)
+			// Do not wait for channel confirmations if we
+			// tried to add a channel
+			num_channels_confirmed++
+		}
 
-		if err1 != nil && err2 != nil {
-			//If the file does not exist, register a new user
-			if os.IsNotExist(err1) && os.IsNotExist(err2) {
-				register = true
+		msg := message.Send{
+			Recipient:   recipientID,
+			Payload:     []byte(msgBody),
+			MessageType: message.Text,
+		}
+		paramsE2E := params.GetDefaultE2E()
+		paramsUnsafe := params.GetDefaultUnsafe()
+
+		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 {
-				//Fail if any other error is received
-				globals.Log.ERROR.Printf("Error with file paths: %s %s",
-					err1, err2)
-				return &id.ZeroUser, "", nil
+				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)
+			}
+
+			jww.INFO.Printf("RoundIDs: %+v\n", roundIDs)
+			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)
+		timeoutTimer := time.NewTimer(waitTimeout * time.Second)
+		done := false
+		for !done && expectedCnt != 0 {
+			select {
+			case <-timeoutTimer.C:
+				fmt.Println("Timed out!")
+				done = true
+				break
+			case m := <-recvCh:
+				fmt.Printf("Message received: %s\n", string(
+					m.Payload))
+				//fmt.Printf("%s", m.Timestamp)
+				receiveCnt++
+				if receiveCnt == expectedCnt {
+					done = true
+				}
+				break
+			}
+		}
+		fmt.Printf("Received %d\n", receiveCnt)
+		if receiveCnt == 0 && sendCnt == 0 {
+			scnt := uint(0)
+			for num_channels_confirmed == 0 && scnt < waitSecs {
+				time.Sleep(1 * time.Second)
+				scnt++
 			}
 		}
-		//Initialize client with OS Storage
-		client, err = api.NewClient(nil, sessionA, sessionB, ndfJSON)
+		err = client.StopNetworkFollower(5 * time.Second)
 		if err != nil {
-			globals.Log.ERROR.Printf("Could Not Initialize OS Storage: %s\n", err.Error())
-			return &id.ZeroUser, "", nil
+			jww.WARN.Printf(
+				"Failed to cleanly close threads: %+v\n",
+				err)
 		}
-		globals.Log.INFO.Println("Initialized OS Storage")
-
-	}
+	},
+}
 
-	if noBlockingTransmission {
-		globals.Log.INFO.Println("Disabling Blocking Transmissions")
-		client.DisableBlockingTransmission()
+// Helper function which prints the round resuls
+func printRoundResults(allRoundsSucceeded, timedOut bool,
+	rounds map[id.Round]api.RoundResult, roundIDs []id.Round, msg message.Send) {
+
+	// Done as string slices for easy and human readable printing
+	successfulRounds := make([]string, 0)
+	failedRounds := make([]string, 0)
+	timedOutRounds := make([]string, 0)
+
+	for _, r := range roundIDs {
+		// Group all round reports into a category based on their
+		// result (successful, failed, or timed out)
+		if result, exists := rounds[r]; exists {
+			if result == api.Succeeded {
+				successfulRounds = append(successfulRounds, strconv.Itoa(int(r)))
+			} else if result == api.Failed {
+				failedRounds = append(failedRounds, strconv.Itoa(int(r)))
+			} else {
+				timedOutRounds = append(timedOutRounds, strconv.Itoa(int(r)))
+			}
+		}
 	}
 
-	// Handle parsing gateway addresses from the config file
-
-	//REVIEWER NOTE: Possibly need to remove/rearrange this,
-	// now that client may not know gw's upon client creation
-	/*gateways := client.GetNDF().Gateways
-	// If gwAddr was not passed via command line, check config file
-	if len(gateways) < 1 {
-		// No gateways in config file or passed via command line
-		globals.Log.ERROR.Printf("Error: No gateway specified! Add to" +
-			" configuration file or pass via command line using -g!\n")
-		return &id.ZeroUser, "", nil
-	}*/
+	jww.INFO.Printf("Result of sending message \"%s\" to \"%v\":",
+		msg.Payload, msg.Recipient)
 
-	if noTLS {
-		client.DisableTls()
+	// Print out all rounds results, if they are populated
+	if len(successfulRounds) > 0 {
+		jww.INFO.Printf("\tRound(s) %v successful", strings.Join(successfulRounds, ","))
 	}
-
-	// InitNetwork to gateways, notificationBot and reg server
-	err = client.InitNetwork()
-	if err != nil {
-		globals.Log.FATAL.Panicf("Could not call connect on client: %+v", err)
+	if len(failedRounds) > 0 {
+		jww.ERROR.Printf("\tRound(s) %v failed", strings.Join(failedRounds, ","))
+	}
+	if len(timedOutRounds) > 0 {
+		jww.ERROR.Printf("\tRound(s) %v timed "+
+			"\n\tout (no network resolution could be found)", strings.Join(timedOutRounds, ","))
 	}
 
-	client.SetRateLimiting(rateLimiting)
-
-	// Holds the User ID
-	var uid *id.ID
-
-	// Register a new user if requested
-	if register {
-		globals.Log.INFO.Println("Registering...")
-
-		regCode := registrationCode
-		// If precanned user, use generated code instead
-		if userId != 0 {
-			precanned = true
-			uid := new(id.ID)
-			binary.BigEndian.PutUint64(uid[:], userId)
-			uid.SetType(id.User)
-			regCode = user.RegistrationCode(uid)
-		}
-
-		globals.Log.INFO.Printf("Building keys...")
-
-		var privKey *rsa.PrivateKey
+}
 
-		if privateKeyPath != "" {
-			privateKeyBytes, err := utils.ReadFile(privateKeyPath)
-			if err != nil {
-				globals.Log.FATAL.Panicf("Could not load user private key PEM from "+
-					"path %s: %+v", privateKeyPath, err)
-			}
+func createClient() *api.Client {
+	initLog(viper.GetUint("logLevel"), viper.GetString("log"))
+	jww.INFO.Printf(Version())
 
-			privKey, err = rsa.LoadPrivateKeyFromPem(privateKeyBytes)
-			if err != nil {
-				globals.Log.FATAL.Panicf("Could not load private key from PEM bytes: %+v", err)
-			}
-		}
+	pass := viper.GetString("password")
+	storeDir := viper.GetString("session")
+	regCode := viper.GetString("regcode")
+	precannedID := viper.GetUint("sendid")
 
-		//Generate keys for registration
-		err := client.GenerateKeys(privKey, sessFilePassword)
+	//create a new client if none exist
+	if _, err := os.Stat(storeDir); os.IsNotExist(err) {
+		// Load NDF
+		ndfPath := viper.GetString("ndf")
+		ndfJSON, err := ioutil.ReadFile(ndfPath)
 		if err != nil {
-			globals.Log.FATAL.Panicf("%+v", err)
+			jww.FATAL.Panicf(err.Error())
 		}
 
-		globals.Log.INFO.Printf("Attempting to register with code %s...", regCode)
-
-		errRegister := fmt.Errorf("")
-
-		//Attempt to register user with same keys until a success occurs
-		for errRegister != nil {
-			_, errRegister = client.RegisterWithPermissioning(precanned, regCode)
-			if errRegister != nil {
-				globals.Log.FATAL.Panicf("Could Not Register User: %s",
-					errRegister.Error())
-			}
+		if precannedID != 0 {
+			err = api.NewPrecannedClient(precannedID,
+				string(ndfJSON), storeDir, []byte(pass))
+		} else {
+			err = api.NewClient(string(ndfJSON), storeDir,
+				[]byte(pass), regCode)
 		}
 
-		err = client.RegisterWithNodes()
 		if err != nil {
-			globals.Log.FATAL.Panicf("Could Not Register User with nodes: %s",
-				err.Error())
+			jww.FATAL.Panicf("%+v", err)
 		}
-
-		uid = client.GetCurrentUser()
-
-		userbase64 := base64.StdEncoding.EncodeToString(uid[:])
-		globals.Log.INFO.Printf("Registered as user (uid, the var) %v", uid)
-		globals.Log.INFO.Printf("Registered as user (userID, the global) %v", userId)
-		globals.Log.INFO.Printf("Successfully registered user %s!", userbase64)
-
-	} else {
-		// hack for session persisting with cmd line
-		// doesn't support non pre canned users
-		uid := new(id.ID)
-		binary.BigEndian.PutUint64(uid[:], userId)
-		uid.SetType(id.User)
-		globals.Log.INFO.Printf("Skipped Registration, user: %v", uid)
 	}
 
-	if !precanned {
-		// If we are sending to a non precanned user we retrieve the uid from the session returned by client.login
-		uid, err = client.Login(sessFilePassword)
-	} else {
-		_, err = client.Login(sessFilePassword)
-	}
+	netParams := params.GetDefaultNetwork()
+	netParams.E2EParams.MinKeys = uint16(viper.GetUint("e2eMinKeys"))
+	netParams.E2EParams.MaxKeys = uint16(viper.GetUint("e2eMaxKeys"))
+	netParams.E2EParams.NumRekeys = uint16(
+		viper.GetUint("e2eNumReKeys"))
+	netParams.ForceHistoricalRounds = viper.GetBool("forceHistoricalRounds")
 
+	client, err := api.OpenClient(storeDir, []byte(pass), netParams)
 	if err != nil {
-		globals.Log.FATAL.Panicf("Could not login: %v", err)
+		jww.FATAL.Panicf("%+v", err)
 	}
-	return uid, client.GetSession().GetCurrentUser().Username, client
+	return client
 }
 
-func setKeyParams(client *api.Client) {
-	globals.Log.DEBUG.Printf("Trying to parse key parameters...")
-	minKeys, err := strconv.Atoi(keyParams[0])
-	if err != nil {
-		return
-	}
+func initClient() *api.Client {
+	createClient()
 
-	maxKeys, err := strconv.Atoi(keyParams[1])
-	if err != nil {
-		return
-	}
+	pass := viper.GetString("password")
+	storeDir := viper.GetString("session")
 
-	numRekeys, err := strconv.Atoi(keyParams[2])
+	netParams := params.GetDefaultNetwork()
+	netParams.E2EParams.MinKeys = uint16(viper.GetUint("e2eMinKeys"))
+	netParams.E2EParams.MaxKeys = uint16(viper.GetUint("e2eMaxKeys"))
+	netParams.E2EParams.NumRekeys = uint16(
+		viper.GetUint("e2eNumReKeys"))
+	netParams.ForceHistoricalRounds = viper.GetBool("forceHistoricalRounds")
+
+	//load the client
+	client, err := api.Login(storeDir, []byte(pass), netParams)
 	if err != nil {
-		return
+		jww.FATAL.Panicf("%+v", err)
 	}
 
-	ttlScalar, err := strconv.ParseFloat(keyParams[3], 64)
-	if err != nil {
+	return client
+}
+
+func writeContact(c contact.Contact) {
+	outfilePath := viper.GetString("writeContact")
+	if outfilePath == "" {
 		return
 	}
-
-	minNumKeys, err := strconv.Atoi(keyParams[4])
+	err := ioutil.WriteFile(outfilePath, c.Marshal(), 0644)
 	if err != nil {
-		return
+		jww.FATAL.Panicf("%+v", err)
 	}
-
-	globals.Log.DEBUG.Printf("Setting key generation parameters: %d, %d, %d, %f, %d",
-		minKeys, maxKeys, numRekeys, ttlScalar, minNumKeys)
-
-	params := client.GetKeyParams()
-	params.MinKeys = uint16(minKeys)
-	params.MaxKeys = uint16(maxKeys)
-	params.NumRekeys = uint16(numRekeys)
-	params.TTLScalar = ttlScalar
-	params.MinNumKeys = uint16(minNumKeys)
-}
-
-type FallbackListener struct {
-	MessagesReceived int64
 }
 
-func (l *FallbackListener) Hear(item switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	if !isHeardElsewhere {
-		message := item.(*parse.Message)
-		sender, ok := user.Users.GetUser(message.Sender)
-		var senderNick string
-		if !ok {
-			globals.Log.ERROR.Printf("Couldn't get sender %v", message.Sender)
-		} else {
-			senderNick = sender.Username
-		}
-		atomic.AddInt64(&l.MessagesReceived, 1)
-		globals.Log.INFO.Printf("Message of type %v from %q, %v received with fallback: %s\n",
-			message.MessageType, printIDNice(message.Sender), senderNick,
-			string(message.Body))
+func readContact() contact.Contact {
+	inputFilePath := viper.GetString("destfile")
+	if inputFilePath == "" {
+		return contact.Contact{}
 	}
+	data, err := ioutil.ReadFile(inputFilePath)
+	jww.INFO.Printf("Contact file size read in: %d", len(data))
+	if err != nil {
+		jww.FATAL.Panicf("Failed to read contact file: %+v", err)
+	}
+	c, err := contact.Unmarshal(data)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err)
+	}
+	return c
 }
 
-type TextListener struct {
-	MessagesReceived int64
-}
-
-func (l *TextListener) Hear(item switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	message := item.(*parse.Message)
-	globals.Log.INFO.Println("Hearing a text message")
-	result := cmixproto.TextMessage{}
-	err := proto.Unmarshal(message.Body, &result)
+func acceptChannel(client *api.Client, recipientID *id.ID) {
+	recipientContact, err := client.GetAuthenticatedChannelRequest(
+		recipientID)
 	if err != nil {
-		globals.Log.ERROR.Printf("Error unmarshaling text message: %v\n",
-			err.Error())
+		jww.FATAL.Panicf("%+v", err)
 	}
-
-	sender, ok := user.Users.GetUser(message.Sender)
-	var senderNick string
-	if !ok {
-		globals.Log.INFO.Printf("First message from sender %v", printIDNice(message.Sender))
-		u := user.Users.NewUser(message.Sender, base64.StdEncoding.EncodeToString(message.Sender[:]))
-		user.Users.UpsertUser(u)
-		senderNick = u.Username
-	} else {
-		senderNick = sender.Username
+	err = client.ConfirmAuthenticatedChannel(
+		recipientContact)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
 	}
-	logMsg := fmt.Sprintf("Message from %v, %v Received: %s\n",
-		printIDNice(message.Sender),
-		senderNick, result.Message)
-	globals.Log.INFO.Printf("%s -- Timestamp: %s\n", logMsg,
-		message.Timestamp.String())
-	fmt.Printf(logMsg)
-
-	atomic.AddInt64(&l.MessagesReceived, 1)
 }
 
-type userSearcher struct {
-	foundUserChan chan []byte
+func printChanRequest(requestor contact.Contact, message string) {
+	msg := fmt.Sprintf("Authentication channel request from: %s\n",
+		requestor.ID)
+	jww.INFO.Printf(msg)
+	fmt.Printf(msg)
+	msg = fmt.Sprintf("Authentication channel request message: %s\n", message)
+	jww.INFO.Printf(msg)
+	//fmt.Printf(msg)
 }
 
-func newUserSearcher() api.SearchCallback {
-	us := userSearcher{}
-	us.foundUserChan = make(chan []byte)
-	return &us
-}
+func addAuthenticatedChannel(client *api.Client, recipientID *id.ID,
+	recipient contact.Contact, isPrecanPartner bool) {
+	if client.HasAuthenticatedChannel(recipientID) {
+		jww.INFO.Printf("Authenticated channel already in place for %s",
+			recipientID)
+		return
+	}
 
-func (us *userSearcher) Callback(userID, pubKey []byte, err error) {
-	if err != nil {
-		globals.Log.ERROR.Printf("Could not find searched user: %+v", err)
+	var allowed bool
+	if viper.GetBool("unsafe-channel-creation") {
+		msg := "unsafe channel creation enabled\n"
+		jww.WARN.Printf(msg)
+		fmt.Printf("WARNING: %s", msg)
+		allowed = true
 	} else {
-		us.foundUserChan <- userID
+		allowed = askToCreateChannel(recipientID)
+	}
+	if !allowed {
+		jww.FATAL.Panicf("User did not allow channel creation!")
 	}
-}
-
-// rootCmd represents the base command when called without any subcommands
-var rootCmd = &cobra.Command{
-	Use:   "client",
-	Short: "Runs a client for cMix anonymous communication platform",
-	Args:  cobra.NoArgs,
-	Run: func(cmd *cobra.Command, args []string) {
-		if !verbose && viper.Get("verbose") != nil {
-			verbose = viper.GetBool("verbose")
-		}
-		if logPath == "" && viper.Get("logPath") != nil {
-			logPath = viper.GetString("logPath")
-		}
-		globals.Log = globals.InitLog(verbose, logPath)
-		// Disable stdout output
-		jww.SetStdoutOutput(ioutil.Discard)
-		// Main client run function
-		userID, _, client := sessionInitialization()
-		err := client.RegisterWithNodes()
-		if err != nil {
-			globals.Log.ERROR.Println(err)
-		}
-		// Set Key parameters if defined
-		if len(keyParams) == 5 {
-			setKeyParams(client)
-		}
 
-		// Set up the listeners for both of the types the client needs for
-		// the integration test
-		// Normal text messages
-		text := TextListener{}
-		client.Listen(&id.ZeroUser, int32(cmixproto.Type_TEXT_MESSAGE),
-			&text)
-		// All other messages
-		fallback := FallbackListener{}
-		client.Listen(&id.ZeroUser, int32(cmixproto.Type_NO_TYPE),
-			&fallback)
-
-		// Log the user in, for now using the first gateway specified
-		// This will also register the user email with UDB
-		globals.Log.INFO.Println("Logging in...")
-		cb := func(err error) {
-			globals.Log.ERROR.Print(err)
-		}
+	msg := fmt.Sprintf("Adding authenticated channel for: %s\n",
+		recipientID)
+	jww.INFO.Printf(msg)
+	fmt.Printf(msg)
 
-		err = client.InitListeners()
-		if err != nil {
-			globals.Log.FATAL.Panicf("Could not initialize receivers: %+v\n", err)
-		}
-
-		err = client.StartMessageReceiver(cb)
+	recipientContact := recipient
 
+	if isPrecanPartner {
+		jww.WARN.Printf("Precanned user id detected: %s",
+			recipientID)
+		preUsr, err := client.MakePrecannedAuthenticatedChannel(
+			getPrecanID(recipientID))
 		if err != nil {
-			globals.Log.FATAL.Panicf("Could Not start message reciever: %s\n", err)
+			jww.FATAL.Panicf("%+v", err)
 		}
-		globals.Log.INFO.Println("Logged In!")
-		globals.Log.INFO.Printf("session prior to udb reg: %v", client.GetSession())
-
-		// todo: since this is in the root cmd, would checking the regstate directly really be bad?
-		//  It's correct that it should be an error state for RegisterWithUDB, however for this, it's start up code
-		if username != "" && client.GetSession().GetRegState() == user.PermissioningComplete {
-			err := client.RegisterWithUDB(username, 2*time.Minute)
-			if err != nil {
-				globals.Log.ERROR.Printf("%+v", err)
+		// Sanity check, make sure user id's haven't changed
+		preBytes := preUsr.ID.Bytes()
+		idBytes := recipientID.Bytes()
+		for i := 0; i < len(preBytes); i++ {
+			if idBytes[i] != preBytes[i] {
+				jww.FATAL.Panicf("no id match: %v %v",
+					preBytes, idBytes)
 			}
 		}
-
-		cryptoType := parse.Unencrypted
-		if end2end {
-			cryptoType = parse.E2E
-		}
-
-		var recipientId *id.ID
-
-		if destinationUserId != 0 && destinationUserIDBase64 != "" {
-			globals.Log.FATAL.Panicf("Two destiantions set for the message, can only have one")
+	} else if recipientContact.ID != nil && recipientContact.DhPubKey != nil {
+		me := client.GetUser().GetContact()
+		jww.INFO.Printf("Requesting auth channel from: %s",
+			recipientID)
+		err := client.RequestAuthenticatedChannel(recipientContact,
+			me, msg)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
 		}
+	} else {
+		jww.ERROR.Printf("Could not add auth channel for %s",
+			recipientID)
+	}
+}
 
-		if destinationUserId == 0 && destinationUserIDBase64 == "" {
-			recipientId = userID
-		} else if destinationUserIDBase64 != "" {
-			recipientIdBytes, err := base64.StdEncoding.DecodeString(destinationUserIDBase64)
-			if err != nil {
-				globals.Log.FATAL.Panic("Could not decode the destination user ID")
-			}
-			recipientId, err = id.Unmarshal(recipientIdBytes)
-			if err != nil {
-				// Destination user ID must be 33 bytes and include the id type
-				globals.Log.FATAL.Panicf("Could not unmarshal destination user ID: %v", err)
-			}
-		} else {
-			recipientId = new(id.ID)
-			binary.BigEndian.PutUint64(recipientId[:], destinationUserId)
-			recipientId.SetType(id.User)
+func waitUntilConnected(connected chan bool) {
+	waitTimeout := time.Duration(viper.GetUint("waitTimeout"))
+	timeoutTimer := time.NewTimer(waitTimeout * time.Second)
+	isConnected := false
+	//Wait until we connect or panic if we can't by a timeout
+	for !isConnected {
+		select {
+		case isConnected = <-connected:
+			jww.INFO.Printf("Network Status: %v\n",
+				isConnected)
+			break
+		case <-timeoutTimer.C:
+			jww.FATAL.Panic("timeout on connection")
 		}
+	}
 
-		if message != "" {
-			// Get the recipient's nick
-			recipientNick := ""
-			u, ok := user.Users.GetUser(recipientId)
-			if ok {
-				recipientNick = u.Username
-			}
-
-			// Handle sending to UDB
-			if recipientId.Cmp(&id.UDB) {
-				parseUdbMessage(message, client)
-			} else {
-				// Handle sending to any other destination
-				wireOut := api.FormatTextMessage(message)
-
-				for i := uint(0); i < messageCnt; i++ {
-					logMsg := fmt.Sprintf(
-						"Sending Message to "+
-							"%s, %v: %s\n", printIDNice(recipientId),
-						recipientNick, message)
-					globals.Log.INFO.Printf(logMsg)
-					fmt.Printf(logMsg)
-					if i != 0 {
-						time.Sleep(1 * time.Second)
-					}
-					// Send the message
-					err := client.Send(&parse.Message{
-						Sender: userID,
-						TypedBody: parse.TypedBody{
-							MessageType: int32(cmixproto.Type_TEXT_MESSAGE),
-							Body:        wireOut,
-						},
-						InferredType: cryptoType,
-						Receiver:     recipientId,
-					})
-					if err != nil {
-						globals.Log.ERROR.Printf("Error sending message: %+v", err)
-					}
+	// Now start a thread to empty this channel and update us
+	// on connection changes for debugging purposes.
+	go func() {
+		prev := true
+		for {
+			select {
+			case isConnected = <-connected:
+				if isConnected != prev {
+					prev = isConnected
+					jww.INFO.Printf(
+						"Network Status Changed: %v\n",
+						isConnected)
 				}
+				break
 			}
 		}
+	}()
+}
 
-		var udbLister api.SearchCallback
-
-		if searchForUser != "" {
-			udbLister = newUserSearcher()
-			client.SearchForUser(searchForUser, udbLister, 2*time.Minute)
-		}
-
-		if message != "" {
-			// Wait up to 45s to receive a message
-			lastCnt := int64(0)
-			ticker := time.Tick(1 * time.Second)
-			for end, timeout := false, time.After(50*time.Second); !end; {
-				numMsgReceived := atomic.LoadInt64(&text.MessagesReceived)
-
-				select {
-				case <-ticker:
-					globals.Log.INFO.Printf("Messages recieved: %v\n\tMessages needed: %v", numMsgReceived, waitForMessages)
-				}
+func getPrecanID(recipientID *id.ID) uint {
+	return uint(recipientID.Bytes()[7])
+}
 
-				if numMsgReceived >= int64(waitForMessages) {
-					end = true
-				}
-				if numMsgReceived != lastCnt {
-					lastCnt = numMsgReceived
-					timeout = time.After(45 * time.Second)
-				}
+func parseRecipient(idStr string) (*id.ID, bool) {
+	if idStr == "0" {
+		return nil, false
+	}
 
-				select {
-				case <-timeout:
-					fmt.Printf("Timing out client, %v/%v "+
-						"message(s) been received\n",
-						numMsgReceived, waitForMessages)
-					end = true
-				default:
-				}
-			}
+	var recipientID *id.ID
+	if strings.HasPrefix(idStr, "0x") {
+		recipientID = getUIDFromHexString(idStr[2:])
+	} else if strings.HasPrefix(idStr, "b64:") {
+		recipientID = getUIDFromb64String(idStr[4:])
+	} else {
+		recipientID = getUIDFromString(idStr)
+	}
+	// check if precanned
+	rBytes := recipientID.Bytes()
+	for i := 0; i < 32; i++ {
+		if i != 7 && rBytes[i] != 0 {
+			return recipientID, false
 		}
+	}
+	if rBytes[7] != byte(0) && rBytes[7] <= byte(40) {
+		return recipientID, true
+	}
+	jww.FATAL.Panicf("error recipient id parse failure: %+v", recipientID)
+	return recipientID, false
+}
 
-		if searchForUser != "" {
-			foundUser := <-udbLister.(*userSearcher).foundUserChan
-			if isValid, uid := isValidUser(foundUser); isValid {
-				globals.Log.INFO.Printf("Found User %s at ID: %s",
-					searchForUser, printIDNice(uid))
-			} else {
-				globals.Log.INFO.Printf("Found User %s is invalid", searchForUser)
-			}
-		}
+func getUIDFromHexString(idStr string) *id.ID {
+	idBytes, err := hex.DecodeString(fmt.Sprintf("%0*d%s",
+		66-len(idStr), 0, idStr))
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	ID, err := id.Unmarshal(idBytes)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	return ID
+}
 
-		if notificationToken != "" {
-			err = client.RegisterForNotifications([]byte(notificationToken))
-			if err != nil {
-				globals.Log.FATAL.Printf("failed to register for notifications: %+v", err)
-			}
-		}
+func getUIDFromb64String(idStr string) *id.ID {
+	idBytes, err := base64.StdEncoding.DecodeString(idStr)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	ID, err := id.Unmarshal(idBytes)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	return ID
+}
 
-		//Logout
-		err = client.Logout(500 * time.Millisecond)
+func getUIDFromString(idStr string) *id.ID {
+	idInt, err := strconv.Atoi(idStr)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	if idInt > 255 {
+		jww.FATAL.Panicf("cannot convert integers above 255. Use 0x " +
+			"or b64: representation")
+	}
+	idBytes := make([]byte, 33)
+	binary.BigEndian.PutUint64(idBytes, uint64(idInt))
+	idBytes[32] = byte(id.User)
+	ID, err := id.Unmarshal(idBytes)
+	if err != nil {
+		jww.FATAL.Panicf("%+v", err)
+	}
+	return ID
+}
 
+func initLog(threshold uint, logPath string) {
+	if logPath != "-" && logPath != "" {
+		// Disable stdout output
+		jww.SetStdoutOutput(ioutil.Discard)
+		// Use log file
+		logOutput, err := os.OpenFile(logPath,
+			os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 		if err != nil {
-			globals.Log.ERROR.Printf("Could not logout: %s\n", err.Error())
-			return
+			panic(err.Error())
 		}
+		jww.SetLogOutput(logOutput)
+	}
 
-	},
+	if threshold > 1 {
+		jww.INFO.Printf("log level set to: TRACE")
+		jww.SetStdoutThreshold(jww.LevelTrace)
+		jww.SetLogThreshold(jww.LevelTrace)
+	} else if threshold == 1 {
+		jww.INFO.Printf("log level set to: DEBUG")
+		jww.SetStdoutThreshold(jww.LevelDebug)
+		jww.SetLogThreshold(jww.LevelDebug)
+	} else {
+		jww.INFO.Printf("log level set to: TRACE")
+		jww.SetStdoutThreshold(jww.LevelInfo)
+		jww.SetLogThreshold(jww.LevelInfo)
+	}
 }
 
 func isValidUser(usr []byte) (bool, *id.ID) {
@@ -593,7 +597,7 @@ func isValidUser(usr []byte) (bool, *id.ID) {
 		if b != 0 {
 			uid, err := id.Unmarshal(usr)
 			if err != nil {
-				globals.Log.WARN.Printf("Could not unmarshal user: %s", err)
+				jww.WARN.Printf("Could not unmarshal user: %s", err)
 				return false, nil
 			}
 			return true, uid
@@ -602,6 +606,22 @@ func isValidUser(usr []byte) (bool, *id.ID) {
 	return false, nil
 }
 
+func askToCreateChannel(recipientID *id.ID) bool {
+	for {
+		fmt.Printf("This is the first time you have messaged %v, "+
+			"are you sure? (yes/no) ", recipientID)
+		var input string
+		fmt.Scanln(&input)
+		if input == "yes" {
+			return true
+		}
+		if input == "no" {
+			return false
+		}
+		fmt.Printf("Please answer 'yes' or 'no'\n")
+	}
+}
+
 // init is the initialization function for Cobra which defines commands
 // and flags.
 func init() {
@@ -614,103 +634,109 @@ func init() {
 	// Here you will define your flags and configuration settings.
 	// Cobra supports persistent flags, which, if defined here,
 	// will be global for your application.
-	rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false,
+	rootCmd.PersistentFlags().UintP("logLevel", "v", 0,
 		"Verbose mode for debugging")
+	viper.BindPFlag("logLevel", rootCmd.PersistentFlags().Lookup("logLevel"))
 
-	rootCmd.PersistentFlags().BoolVarP(&noBlockingTransmission, "noBlockingTransmission",
-		"", false, "Sets if transmitting messages blocks or not.  "+
-			"Defaults to true if unset.")
-	rootCmd.PersistentFlags().Uint32VarP(&rateLimiting, "rateLimiting", "",
-		1000, "Sets the amount of time, in ms, "+
-			"that the client waits between sending messages.  "+
-			"set to zero to disable.  "+
-			"Automatically disabled if 'blockingTransmission' is false")
-
-	rootCmd.PersistentFlags().Uint64VarP(&userId, "userid", "i", 0,
-		"ID to sign in as. Does not register, must be an available precanned user")
-
-	rootCmd.PersistentFlags().StringVarP(&registrationCode,
-		"regcode", "r",
-		"",
-		"Registration Code with the registration server")
-
-	rootCmd.PersistentFlags().StringVarP(&username,
-		"username", "E",
-		"",
-		"Username to register for User Discovery")
-
-	rootCmd.PersistentFlags().StringVarP(&sessionFile, "sessionfile", "f",
-		"", "Passes a file path for loading a session.  "+
-			"If the file doesn't exist the code will register the user and"+
-			" store it there.  If not passed the session will be stored"+
-			" to ram and lost when the cli finishes")
-
-	rootCmd.PersistentFlags().StringVarP(&ndfPubKey,
-		"ndfPubKeyCertPath",
-		"p",
-		"",
-		"Path to the certificated containing the public key for the "+
-			" network definition JSON file")
-
-	rootCmd.PersistentFlags().StringVarP(&ndfPath,
-		"ndf",
-		"n",
-		"ndf.json",
-		"Path to the network definition JSON file")
+	rootCmd.PersistentFlags().StringP("session", "s",
+		"", "Sets the initial storage directory for "+
+			"client session data")
+	viper.BindPFlag("session", rootCmd.PersistentFlags().Lookup("session"))
 
-	rootCmd.PersistentFlags().BoolVar(&skipNDFVerification,
-		"skipNDFVerification",
-		false,
-		"Specifies if the NDF should be loaded without the signature")
+	rootCmd.PersistentFlags().StringP("writeContact", "w",
+		"-", "Write contact information, if any, to this file, "+
+			" defaults to stdout")
+	viper.BindPFlag("writeContact", rootCmd.PersistentFlags().Lookup(
+		"writeContact"))
 
-	rootCmd.PersistentFlags().StringVarP(&sessFilePassword,
-		"password",
-		"P",
-		"",
+	rootCmd.PersistentFlags().StringP("password", "p", "",
 		"Password to the session file")
+	viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup(
+		"password"))
 
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().StringVarP(&message, "message", "m", "", "Message to send")
-	rootCmd.PersistentFlags().Uint64VarP(&destinationUserId, "destid", "d", 0,
-		"ID to send message to")
-
-	rootCmd.Flags().StringVarP(&notificationToken, "nbRegistration", "x", "",
-		"Token to register user with notification bot")
-
-	rootCmd.PersistentFlags().BoolVarP(&end2end, "end2end", "", false,
-		"Send messages with E2E encryption to destination user. Must have found each other via UDB first")
-
-	rootCmd.PersistentFlags().StringSliceVarP(&keyParams, "keyParams", "",
-		make([]string, 0), "Define key generation parameters. Pass values in comma separated list"+
-			" in the following order: MinKeys,MaxKeys,NumRekeys,TTLScalar,MinNumKeys")
-
-	rootCmd.Flags().BoolVarP(&noTLS, "noTLS", "", false,
-		"Set to ignore tls. Connections will fail if the network requires tls. For debugging")
-
-	rootCmd.Flags().StringVar(&privateKeyPath, "privateKey", "",
-		"The path for a PEM encoded private key which will be used "+
-			"to create the user")
-
-	rootCmd.Flags().StringVar(&destinationUserIDBase64, "dest64", "",
-		"Sets the destination user id encoded in base 64")
-
-	rootCmd.Flags().UintVarP(&waitForMessages, "waitForMessages",
-		"w", 1, "Denotes the number of messages the "+
-			"client should receive before closing")
-
-	rootCmd.Flags().StringVarP(&searchForUser, "SearchForUser", "s", "",
-		"Sets the email to search for to find a user with user discovery")
-
-	rootCmd.Flags().StringVarP(&logPath, "log", "l", "",
-		"Print logs to specified log file, not stdout")
-
-	rootCmd.Flags().UintVarP(&messageTimeout, "messageTimeout",
-		"t", 45, "The number of seconds to wait for "+
-			"'waitForMessages' messages to arrive")
-
-	rootCmd.Flags().UintVarP(&messageCnt, "messageCount",
-		"c", 1, "The number of times to send the message")
+	rootCmd.PersistentFlags().StringP("ndf", "n", "ndf.json",
+		"Path to the network definition JSON file")
+	viper.BindPFlag("ndf", rootCmd.PersistentFlags().Lookup("ndf"))
+
+	rootCmd.PersistentFlags().StringP("log", "l", "-",
+		"Path to the log output path (- is stdout)")
+	viper.BindPFlag("log", rootCmd.PersistentFlags().Lookup("log"))
+
+	rootCmd.Flags().StringP("regcode", "", "",
+		"Identity code (optional)")
+	viper.BindPFlag("regcode", rootCmd.Flags().Lookup("regcode"))
+
+	rootCmd.PersistentFlags().StringP("message", "m", "",
+		"Message to send")
+	viper.BindPFlag("message", rootCmd.PersistentFlags().Lookup("message"))
+
+	rootCmd.Flags().UintP("sendid", "", 0,
+		"Use precanned user id (must be between 1 and 40, inclusive)")
+	viper.BindPFlag("sendid", rootCmd.Flags().Lookup("sendid"))
+
+	rootCmd.Flags().StringP("destid", "d", "0",
+		"ID to send message to (if below 40, will be precanned. Use "+
+			"'0x' or 'b64:' for hex and base64 representations)")
+	viper.BindPFlag("destid", rootCmd.Flags().Lookup("destid"))
+
+	rootCmd.Flags().StringP("destfile", "",
+		"", "Read this contact file for the destination id")
+	viper.BindPFlag("destfile", rootCmd.Flags().Lookup("destfile"))
+
+	rootCmd.Flags().UintP("sendCount",
+		"", 1, "The number of times to send the message")
+	viper.BindPFlag("sendCount", rootCmd.Flags().Lookup("sendCount"))
+	rootCmd.Flags().UintP("sendDelay",
+		"", 500, "The delay between sending the messages in ms")
+	viper.BindPFlag("sendDelay", rootCmd.Flags().Lookup("sendDelay"))
+
+	rootCmd.Flags().UintP("receiveCount",
+		"", 1, "How many messages we should wait for before quitting")
+	viper.BindPFlag("receiveCount", rootCmd.Flags().Lookup("receiveCount"))
+	rootCmd.Flags().UintP("waitTimeout", "", 15,
+		"The number of seconds to wait for messages to arrive")
+	viper.BindPFlag("waitTimeout",
+		rootCmd.Flags().Lookup("waitTimeout"))
+
+	rootCmd.Flags().BoolP("unsafe", "", false,
+		"Send raw, unsafe messages without e2e encryption.")
+	viper.BindPFlag("unsafe", rootCmd.Flags().Lookup("unsafe"))
+
+	rootCmd.Flags().BoolP("unsafe-channel-creation", "", false,
+		"Turns off the user identity authenticated channel check, "+
+			"automatically approving authenticated channels")
+	viper.BindPFlag("unsafe-channel-creation",
+		rootCmd.Flags().Lookup("unsafe-channel-creation"))
+
+	rootCmd.Flags().BoolP("assume-auth-channel", "", false,
+		"Do not check for an authentication channel for this user")
+	viper.BindPFlag("assume-auth-channel",
+		rootCmd.Flags().Lookup("assume-auth-channel"))
+
+	rootCmd.Flags().BoolP("accept-channel", "", false,
+		"Accept the channel request for the corresponding recipient ID")
+	viper.BindPFlag("accept-channel",
+		rootCmd.Flags().Lookup("accept-channel"))
+
+	rootCmd.Flags().BoolP("forceHistoricalRounds", "", false,
+		"Force all rounds to be sent to historical round retrieval")
+	viper.BindPFlag("forceHistoricalRounds",
+		rootCmd.Flags().Lookup("forceHistoricalRounds"))
+
+	// E2E Params
+	defaultE2EParams := params.GetDefaultE2ESessionParams()
+	rootCmd.Flags().UintP("e2eMinKeys",
+		"", uint(defaultE2EParams.MinKeys),
+		"Minimum number of keys used before requesting rekey")
+	viper.BindPFlag("e2eMinKeys", rootCmd.Flags().Lookup("e2eMinKeys"))
+	rootCmd.Flags().UintP("e2eMaxKeys",
+		"", uint(defaultE2EParams.MaxKeys),
+		"Max keys used before blocking until a rekey completes")
+	viper.BindPFlag("e2eMaxKeys", rootCmd.Flags().Lookup("e2eMaxKeys"))
+	rootCmd.Flags().UintP("e2eNumReKeys",
+		"", uint(defaultE2EParams.NumRekeys),
+		"Number of rekeys reserved for rekey operations")
+	viper.BindPFlag("e2eNumReKeys", rootCmd.Flags().Lookup("e2eNumReKeys"))
 }
 
 // initConfig reads in config file and ENV variables if set.
@@ -745,4 +771,3 @@ func buildPrecannedIDList() []*id.ID {
 
 	return idList
 }
-
diff --git a/cmd/single.go b/cmd/single.go
new file mode 100644
index 0000000000000000000000000000000000000000..cf2b07f78cdc6cbcd64181d84f4bc63dd53a4f67
--- /dev/null
+++ b/cmd/single.go
@@ -0,0 +1,263 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Package cmd initializes the CLI and config parsers as well as the logger.
+package cmd
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/spf13/cobra"
+	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/viper"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/xx_network/primitives/utils"
+	"time"
+)
+
+// singleCmd is the single-use subcommand that allows for sending and responding
+// to single-use messages.
+var singleCmd = &cobra.Command{
+	Use:   "single",
+	Short: "Send and respond to single-use messages.",
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+
+		client := initClient()
+
+		// Write user contact to file
+		user := client.GetUser()
+		jww.INFO.Printf("User: %s", user.ReceptionID)
+		jww.INFO.Printf("User Transmission: %s", user.TransmissionID)
+		writeContact(user.GetContact())
+
+		// Set up reception handler
+		swBoard := client.GetSwitchboard()
+		recvCh := make(chan message.Receive, 10000)
+		listenerID := swBoard.RegisterChannel("DefaultCLIReceiver",
+			switchboard.AnyUser(), message.Text, recvCh)
+		jww.INFO.Printf("Message ListenerID: %v", listenerID)
+
+		// Set up auth request handler, which simply prints the user ID of the
+		// requester
+		authMgr := client.GetAuthRegistrar()
+		authMgr.AddGeneralRequestCallback(printChanRequest)
+
+		// If unsafe channels, then add auto-acceptor
+		if viper.GetBool("unsafe-channel-creation") {
+			authMgr.AddGeneralRequestCallback(func(
+				requester contact.Contact, message string) {
+				jww.INFO.Printf("Got request: %s", requester.ID)
+				err := client.ConfirmAuthenticatedChannel(requester)
+				if err != nil {
+					jww.FATAL.Panicf("%+v", err)
+				}
+			})
+		}
+
+		_, err := client.StartNetworkFollower()
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+
+		// Wait until connected or crash on timeout
+		connected := make(chan bool, 10)
+		client.GetHealth().AddChannel(connected)
+		waitUntilConnected(connected)
+
+		// Make single-use manager and start receiving process
+		singleMng := single.NewManager(client)
+
+		// Register the callback
+		callbackChan := make(chan responseCallbackChan)
+		callback := func(payload []byte, c single.Contact) {
+			callbackChan <- responseCallbackChan{payload, c}
+		}
+		singleMng.RegisterCallback("tag", callback)
+		client.AddService(singleMng.StartProcesses)
+
+		timeout := viper.GetDuration("timeout")
+
+		// If the send flag is set, then send a message
+		if viper.GetBool("send") {
+			// Get message details
+			payload := []byte(viper.GetString("message"))
+			partner := readSingleUseContact("contact")
+			maxMessages := uint8(viper.GetUint("maxMessages"))
+
+			sendSingleUse(singleMng, partner, payload, maxMessages, timeout)
+		}
+
+		// If the reply flag is set, then start waiting for a message and reply
+		// when it is received
+		if viper.GetBool("reply") {
+			replySingleUse(singleMng, timeout, callbackChan)
+		}
+	},
+}
+
+func init() {
+	// Single-use subcommand options
+
+	singleCmd.Flags().Bool("send", false, "Sends a single-use message.")
+	_ = viper.BindPFlag("send", singleCmd.Flags().Lookup("send"))
+
+	singleCmd.Flags().Bool("reply", false,
+		"Listens for a single-use message and sends a reply.")
+	_ = viper.BindPFlag("reply", singleCmd.Flags().Lookup("reply"))
+
+	singleCmd.Flags().StringP("contact", "c", "",
+		"Path to contact file to send message to.")
+	_ = viper.BindPFlag("contact", singleCmd.Flags().Lookup("contact"))
+
+	singleCmd.Flags().Uint8("maxMessages", 1,
+		"The max number of single-use response messages.")
+	_ = viper.BindPFlag("maxMessages", singleCmd.Flags().Lookup("maxMessages"))
+
+	singleCmd.Flags().DurationP("timeout", "t", 30*time.Second,
+		"Duration before stopping to wait for single-use message.")
+	_ = viper.BindPFlag("timeout", singleCmd.Flags().Lookup("timeout"))
+
+	rootCmd.AddCommand(singleCmd)
+}
+
+// sendSingleUse sends a single use message.
+func sendSingleUse(m *single.Manager, partner contact.Contact, payload []byte,
+	maxMessages uint8, timeout time.Duration) {
+	// Construct callback
+	callbackChan := make(chan struct {
+		payload []byte
+		err     error
+	})
+	callback := func(payload []byte, err error) {
+		callbackChan <- struct {
+			payload []byte
+			err     error
+		}{payload: payload, err: err}
+	}
+
+	jww.INFO.Printf("Sending single-use message to contact: %+v", partner)
+	jww.INFO.Printf("Payload: \"%s\"", payload)
+	jww.INFO.Printf("Max number of replies: %d", maxMessages)
+	jww.INFO.Printf("Timeout: %s", timeout)
+
+	// Send single-use message
+	fmt.Printf("Sending single-use transmission message: %s\n", payload)
+	jww.DEBUG.Printf("Sending single-use transmission to %s: %s", partner.ID, payload)
+	err := m.TransmitSingleUse(partner, payload, "tag", maxMessages, callback, timeout)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to transmit single-use message: %+v", err)
+	}
+
+	// Wait for callback to be called
+	fmt.Println("Waiting for response.")
+	results := <-callbackChan
+	if results.payload != nil {
+		fmt.Printf("Message received: %s\n", results.payload)
+		jww.DEBUG.Printf("Received single-use reply payload: %s", results.payload)
+	} else {
+		jww.ERROR.Print("Failed to receive single-use reply payload.")
+	}
+
+	if results.err != nil {
+		jww.FATAL.Panicf("Received error when waiting for reply: %+v", results.err)
+	}
+}
+
+// replySingleUse responds to any single-use message it receives by replying\
+// with the same payload.
+func replySingleUse(m *single.Manager, timeout time.Duration, callbackChan chan responseCallbackChan) {
+
+	// Wait to receive a message or stop after timeout occurs
+	fmt.Println("Waiting for single-use message.")
+	timer := time.NewTimer(timeout)
+	select {
+	case results := <-callbackChan:
+		if results.payload != nil {
+			fmt.Printf("Single-use transmission received: %s\n", results.payload)
+			jww.DEBUG.Printf("Received single-use transmission from %s: %s",
+				results.c.GetPartner(), results.payload)
+		} else {
+			jww.ERROR.Print("Failed to receive single-use payload.")
+		}
+
+		// Create new payload from repeated received payloads so that each
+		// message part contains the same payload
+		payload := makeResponsePayload(m, results.payload, results.c.GetMaxParts())
+
+		fmt.Printf("Sending single-use response message: %s\n", payload)
+		jww.DEBUG.Printf("Sending single-use response to %s: %s", results.c.GetPartner(), payload)
+		err := m.RespondSingleUse(results.c, payload, timeout)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to send response: %+v", err)
+		}
+
+	case <-timer.C:
+		fmt.Println("Timed out!")
+		jww.FATAL.Panicf("Failed to receive transmission after %s.", timeout)
+	}
+}
+
+// responseCallbackChan structure used to collect information sent to the
+// response callback.
+type responseCallbackChan struct {
+	payload []byte
+	c       single.Contact
+}
+
+// makeResponsePayload generates a new payload that will span the max number of
+// message parts in the contact. Each resulting message payload will contain a
+// copy of the supplied payload with spaces taking up any remaining data.
+func makeResponsePayload(m *single.Manager, payload []byte, maxParts uint8) []byte {
+	payloads := make([][]byte, maxParts)
+	payloadPart := makeResponsePayloadPart(m, payload)
+	for i := range payloads {
+		payloads[i] = make([]byte, m.GetMaxResponsePayloadSize())
+		copy(payloads[i], payloadPart)
+	}
+	return bytes.Join(payloads, []byte{})
+}
+
+// makeResponsePayloadPart creates a single response payload by coping the given
+// payload and filling the rest with spaces.
+func makeResponsePayloadPart(m *single.Manager, payload []byte) []byte {
+	payloadPart := make([]byte, m.GetMaxResponsePayloadSize())
+	for i := range payloadPart {
+		payloadPart[i] = ' '
+	}
+	copy(payloadPart, payload)
+
+	return payloadPart
+}
+
+// readSingleUseContact opens the contact specified in the CLI flags. Panics if
+// no file provided or if an error occurs while reading or unmarshalling it.
+func readSingleUseContact(key string) contact.Contact {
+	// Get path
+	filePath := viper.GetString(key)
+	if filePath == "" {
+		jww.FATAL.Panicf("Failed to read contact file: no file path provided.")
+	}
+
+	// Read from file
+	data, err := utils.ReadFile(filePath)
+	jww.INFO.Printf("Contact file size read in: %d bytes", len(data))
+	if err != nil {
+		jww.FATAL.Panicf("Failed to read contact file: %+v", err)
+	}
+
+	// Unmarshal contact
+	c, err := contact.Unmarshal(data)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err)
+	}
+
+	return c
+}
diff --git a/cmd/ud.go b/cmd/ud.go
new file mode 100644
index 0000000000000000000000000000000000000000..fb9603dd52b18eb096bd3699c87b398dbcc8a7a5
--- /dev/null
+++ b/cmd/ud.go
@@ -0,0 +1,252 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Package cmd initializes the CLI and config parsers as well as the logger.
+package cmd
+
+import (
+	"fmt"
+	"github.com/spf13/cobra"
+	jww "github.com/spf13/jwalterweatherman"
+	"github.com/spf13/viper"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/client/ud"
+	"gitlab.com/elixxir/primitives/fact"
+	"time"
+)
+
+// udCmd is the user discovery subcommand, which allows for user lookup,
+// registration, and search. This basically runs a client for these functions
+// with the UD module enabled. Normally, clients do not need it so it is not
+// loaded for the rest of the commands.
+var udCmd = &cobra.Command{
+	Use:   "ud",
+	Short: "Register for and search users using the xx network user discovery service.",
+	Args:  cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		client := initClient()
+
+		// Get user and save contact to file
+		user := client.GetUser()
+		jww.INFO.Printf("User: %s", user.ReceptionID)
+		writeContact(user.GetContact())
+
+		// Set up reception handler
+		swBoard := client.GetSwitchboard()
+		recvCh := make(chan message.Receive, 10000)
+		listenerID := swBoard.RegisterChannel("DefaultCLIReceiver",
+			switchboard.AnyUser(), message.Text, recvCh)
+		jww.INFO.Printf("Message ListenerID: %v", listenerID)
+
+		// Set up auth request handler, which simply prints the user ID of the
+		// requester
+		authMgr := client.GetAuthRegistrar()
+		authMgr.AddGeneralRequestCallback(printChanRequest)
+
+		// If unsafe channels, add auto-acceptor
+		if viper.GetBool("unsafe-channel-creation") {
+			authMgr.AddGeneralRequestCallback(func(
+				requester contact.Contact, message string) {
+				jww.INFO.Printf("Got Request: %s", requester.ID)
+				err := client.ConfirmAuthenticatedChannel(requester)
+				if err != nil {
+					jww.FATAL.Panicf("%+v", err)
+				}
+			})
+		}
+
+		_, err := client.StartNetworkFollower()
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+
+		// Wait until connected or crash on timeout
+		connected := make(chan bool, 10)
+		client.GetHealth().AddChannel(connected)
+		waitUntilConnected(connected)
+
+		// Make single-use manager and start receiving process
+		singleMng := single.NewManager(client)
+		client.AddService(singleMng.StartProcesses)
+
+		// Make user discovery manager
+		userDiscoveryMgr, err := ud.NewManager(client, singleMng)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to create new UD manager: %+v", err)
+		}
+
+		userToRegister := viper.GetString("register")
+		if userToRegister != "" {
+			err = userDiscoveryMgr.Register(userToRegister)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to register user %s: %+v", userToRegister, err)
+			}
+		}
+
+		var newFacts fact.FactList
+		phone := viper.GetString("addphone")
+		if phone != "" {
+			f, err := fact.NewFact(fact.Phone, phone)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
+			}
+			newFacts = append(newFacts, f)
+		}
+
+		email := viper.GetString("addemail")
+		if email != "" {
+			f, err := fact.NewFact(fact.Email, email)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
+			}
+			newFacts = append(newFacts, f)
+		}
+
+		for i := 0; i < len(newFacts); i++ {
+			r, err := userDiscoveryMgr.SendRegisterFact(newFacts[i])
+			if err != nil {
+				jww.FATAL.Panicf("Failed to send register fact: %+v", err)
+			}
+			// TODO Store the code?
+			jww.INFO.Printf("Fact Add Response: %+v", r)
+		}
+
+		confirmID := viper.GetString("confirm")
+		if confirmID != "" {
+			// TODO: Lookup code
+			err = userDiscoveryMgr.SendConfirmFact(confirmID, confirmID)
+			if err != nil {
+				jww.FATAL.Panicf("%+v", err)
+			}
+		}
+
+		lookupIDStr := viper.GetString("lookup")
+		if lookupIDStr != "" {
+			lookupID, ok := parseRecipient(lookupIDStr)
+			if !ok {
+				jww.FATAL.Panicf("Could not parse recipient: %s", lookupIDStr)
+			}
+			err = userDiscoveryMgr.Lookup(lookupID,
+				func(newContact contact.Contact, err error) {
+					if err != nil {
+						jww.FATAL.Panicf("%+v", err)
+					}
+					printContact(newContact)
+				}, 90*time.Second)
+
+			if err != nil {
+				jww.WARN.Printf("Failed UD lookup: %+v", err)
+			}
+
+			time.Sleep(91 * time.Second)
+		}
+
+		usernameSearchStr := viper.GetString("searchusername")
+		emailSearchStr := viper.GetString("searchemail")
+		phoneSearchStr := viper.GetString("searchphone")
+
+		var facts fact.FactList
+		if usernameSearchStr != "" {
+			f, err := fact.NewFact(fact.Username, usernameSearchStr)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
+			}
+			facts = append(facts, f)
+		}
+		if emailSearchStr != "" {
+			f, err := fact.NewFact(fact.Email, emailSearchStr)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
+			}
+			facts = append(facts, f)
+		}
+		if phoneSearchStr != "" {
+			f, err := fact.NewFact(fact.Phone, phoneSearchStr)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
+			}
+			facts = append(facts, f)
+		}
+
+		if len(facts) == 0 {
+			err = client.StopNetworkFollower(10 * time.Second)
+			if err != nil {
+				jww.WARN.Print(err)
+			}
+			return
+		}
+
+		err = userDiscoveryMgr.Search(facts,
+			func(contacts []contact.Contact, err error) {
+				if err != nil {
+					jww.FATAL.Panicf("%+v", err)
+				}
+				for _, c := range contacts {
+					printContact(c)
+				}
+			}, 90*time.Second)
+		if err != nil {
+			jww.FATAL.Panicf("%+v", err)
+		}
+		time.Sleep(91 * time.Second)
+		err = client.StopNetworkFollower(90 * time.Second)
+		if err != nil {
+			jww.WARN.Print(err)
+		}
+	},
+}
+
+func init() {
+	// User Discovery subcommand Options
+	udCmd.Flags().StringP("register", "r", "",
+		"Register this user with user discovery.")
+	_ = viper.BindPFlag("register", udCmd.Flags().Lookup("register"))
+
+	udCmd.Flags().String("addphone", "",
+		"Add phone number to existing user registration.")
+	_ = viper.BindPFlag("addphone", udCmd.Flags().Lookup("addphone"))
+
+	udCmd.Flags().StringP("addemail", "e", "",
+		"Add email to existing user registration.")
+	_ = viper.BindPFlag("addemail", udCmd.Flags().Lookup("addemail"))
+
+	udCmd.Flags().String("confirm", "", "Confirm fact with confirmation ID.")
+	_ = viper.BindPFlag("confirm", udCmd.Flags().Lookup("confirm"))
+
+	udCmd.Flags().StringP("lookup", "u", "",
+		"Look up user ID. Use '0x' or 'b64:' for hex and base64 representations.")
+	_ = viper.BindPFlag("lookup", udCmd.Flags().Lookup("lookup"))
+
+	udCmd.Flags().String("searchusername", "",
+		"Search for users with this username.")
+	_ = viper.BindPFlag("searchusername", udCmd.Flags().Lookup("searchusername"))
+
+	udCmd.Flags().String("searchemail", "",
+		"Search for users with this email address.")
+	_ = viper.BindPFlag("searchemail", udCmd.Flags().Lookup("searchemail"))
+
+	udCmd.Flags().String("searchphone", "",
+		"Search for users with this email address.")
+	_ = viper.BindPFlag("searchphone", udCmd.Flags().Lookup("searchphone"))
+
+	rootCmd.AddCommand(udCmd)
+}
+
+func printContact(c contact.Contact) {
+	jww.DEBUG.Printf("Printing client: %+v", c)
+	cBytes := c.Marshal()
+	if len(cBytes) == 0 {
+		jww.ERROR.Print("Marshaled client has a size of 0.")
+	} else {
+		jww.DEBUG.Printf("Printing marshaled contact of size %d.", len(cBytes))
+	}
+
+	fmt.Print(string(cBytes))
+}
diff --git a/cmd/udb.go b/cmd/udb.go
deleted file mode 100644
index ed39e5d8be21880cc3b3540c64d214c8f6e87b12..0000000000000000000000000000000000000000
--- a/cmd/udb.go
+++ /dev/null
@@ -1,55 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package cmd
-
-import (
-	"gitlab.com/elixxir/client/api"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/primitives/id"
-	"strings"
-	"time"
-)
-
-type callbackSearch struct{}
-
-func (cs callbackSearch) Callback(userID, pubKey []byte, err error) {
-	if err != nil {
-		globals.Log.INFO.Printf("UDB search failed: %v\n", err.Error())
-	} else if len(pubKey) == 0 {
-		globals.Log.INFO.Printf("Public Key returned is empty\n")
-	} else {
-		userID, err := id.Unmarshal(userID)
-		if err != nil {
-			globals.Log.ERROR.Printf("Malformed user ID from successful UDB search: %v", err)
-		}
-		globals.Log.INFO.Printf("UDB search successful. Returned user %v\n",
-			userID)
-	}
-}
-
-var searchCallback = callbackSearch{}
-
-// Determines what UDB send function to call based on the text in the message
-func parseUdbMessage(msg string, client *api.Client) {
-	// Split the message on spaces
-	args := strings.Fields(msg)
-	if len(args) < 3 {
-		globals.Log.ERROR.Printf("UDB command must have at least three arguments!")
-	}
-	// The first arg is the command
-	// the second is the valueType
-	// the third is the value
-	keyword := args[0]
-	// Case-insensitive match the keyword to a command
-	if strings.EqualFold(keyword, "SEARCH") {
-		client.SearchForUser(args[2], searchCallback, 2*time.Minute)
-	} else if strings.EqualFold(keyword, "REGISTER") {
-		globals.Log.ERROR.Printf("UDB REGISTER not allowed, it is already done during user registration")
-	} else {
-		globals.Log.ERROR.Printf("UDB command not recognized!")
-	}
-}
diff --git a/cmd/version.go b/cmd/version.go
index 9cf6e3164e71315a0f180773f68d0f8b84f53860..99af5a4c68449110c4b9281285596caf305b2e78 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -1,8 +1,9 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
 
 // Handles command-line version functionality
 
@@ -10,17 +11,20 @@ package cmd
 
 import (
 	"fmt"
+
 	"github.com/spf13/cobra"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/primitives/utils"
+	"gitlab.com/elixxir/client/api"
+	"gitlab.com/xx_network/primitives/utils"
 )
 
 // Change this value to set the version for this build
-const currentVersion = "1.5.0"
+const currentVersion = "2.0.0"
 
-func printVersion() {
-	fmt.Printf("Elixxir Client v%s -- %s\n\n", globals.SEMVER, globals.GITVERSION)
-	fmt.Printf("Dependencies:\n\n%s\n", globals.DEPENDENCIES)
+func Version() string {
+	out := fmt.Sprintf("Elixxir Client v%s -- %s\n\n", api.SEMVER,
+		api.GITVERSION)
+	out += fmt.Sprintf("Dependencies:\n\n%s\n", api.DEPENDENCIES)
+	return out
 }
 
 func init() {
@@ -33,7 +37,7 @@ var versionCmd = &cobra.Command{
 	Short: "Print the version and dependency information for the Elixxir binary",
 	Long:  `Print the version and dependency information for the Elixxir binary`,
 	Run: func(cmd *cobra.Command, args []string) {
-		printVersion()
+		fmt.Printf(Version())
 	},
 }
 
diff --git a/cmix.go b/cmix.go
new file mode 100644
index 0000000000000000000000000000000000000000..69ee456715006686934cd8b27cbc3dccd8a976a1
--- /dev/null
+++ b/cmix.go
@@ -0,0 +1,8 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package main
diff --git a/cmixproto/generate.sh b/cmixproto/generate.sh
deleted file mode 100755
index 06e52ef847ad9a007c2ed67b9a3c9fce9cc68230..0000000000000000000000000000000000000000
--- a/cmixproto/generate.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-protoc --go_out=. types.proto
diff --git a/cmixproto/types.pb.go b/cmixproto/types.pb.go
deleted file mode 100644
index c0b870e2ae83e59fc0b2cffb7db46d80afedec20..0000000000000000000000000000000000000000
--- a/cmixproto/types.pb.go
+++ /dev/null
@@ -1,554 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// source: types.proto
-
-package cmixproto
-
-import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
-)
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
-
-type Type int32
-
-const (
-	// Used as a wildcard for listeners to listen to all existing types.
-	// Think of it as "No type in particular"
-	Type_NO_TYPE Type = 0
-	// See proto buf documentation below
-	Type_TEXT_MESSAGE Type = 1
-	// See proto buf
-	Type_CHANNEL_MESSAGE Type = 2
-	// Nickname request and response messages
-	Type_NICKNAME_REQUEST  Type = 6
-	Type_NICKNAME_RESPONSE Type = 7
-	// Second field is the key data itself. This should be 2048 bits long
-	// (according to the message length that our prime allows) and is
-	// base64-encoded.
-	Type_UDB_PUSH_KEY Type = 10
-	// The push key response message is a string. If the key push was a
-	// success, the UDB should respond with a message that starts with "PUSHKEY
-	// COMPLETE", followed by the fingerprint of the key that was pushed.
-	// If the response doesn't begin with "PUSHKEY COMPLETE", the message is
-	// an error message and should be shown to the user.
-	Type_UDB_PUSH_KEY_RESPONSE Type = 11
-	// The get key message includes a single string field with the key
-	// fingerprint of the key that needs gettin'. This is the same fingerprint
-	// you would have pushed.
-	Type_UDB_GET_KEY Type = 12
-	// The get key response message is a string. The first space-separated
-	// field should always be "GETKEY". The second field is the fingerprint of
-	// the key. The third field is "NOTFOUND" if the UDB didn't find the key,
-	// or the key itself, encoded in base64, otherwise.
-	Type_UDB_GET_KEY_RESPONSE Type = 13
-	// To wit: The first argument in the list of space-separated fields is
-	// the type of the registration. Currently the only allowed type is
-	// "EMAIL". The second argument is the value of the type you're registering
-	// with. In all currently acceptable registration types, this would be an
-	// email address. If you could register with your phone, it would be your
-	// phone number, and so on. Then, the key fingerprint of the user's key is
-	// the third argument. To register successfully, you must have already
-	// pushed the key with that fingerprint.
-	Type_UDB_REGISTER Type = 14
-	// The registration response is just a string. It will be either an error
-	// message to show to the user, or the message "REGISTRATION COMPLETE" if
-	// registration was successful.
-	Type_UDB_REGISTER_RESPONSE Type = 15
-	// The search message is just another space separated list. The first field
-	// will contain the type of registered user you're searching for, namely
-	// "EMAIL". The second field with contain the value of that type that
-	// you're searching for.
-	Type_UDB_SEARCH Type = 16
-	// The search response is a list of fields. The first is always "SEARCH".
-	// The second is always the value that the user searched for. The third is
-	// "FOUND" or "NOTFOUND" depending on whether the UDB found the user. If
-	// the user was FOUND, the last field will contain their key fingerprint,
-	// which you can use with GET_KEY to get the keys you need to talk with
-	// that user. Otherwise, this fourth field won't exist.
-	Type_UDB_SEARCH_RESPONSE Type = 17
-	// To get a message of this type, call the methods in the wallet.
-	// TODO expose these methods over the API
-	Type_PAYMENT_TRANSACTION Type = 20
-	// See proto buf
-	Type_PAYMENT_RESPONSE Type = 21
-	// See proto buf
-	Type_PAYMENT_INVOICE Type = 22
-	// This message type includes only the message hash of the original
-	// invoice message. Since there are no fields to delimit, it's not a
-	// proto buffer. When the payee gets a receipt back, they know that the
-	// other person probably paid them, and to check the next published
-	// blockchain for the images of their coins to make sure.
-	// The wallet sends this message after receiving a PAYMENT_RESPONSE
-	// indicating success.
-	Type_PAYMENT_RECEIPT Type = 23
-	// End to End Rekey message types
-	// Trigger a rekey, this message is used locally in client only
-	Type_REKEY_TRIGGER Type = 30
-	// Rekey confirmation message. Sent by partner to confirm completion of a rekey
-	Type_REKEY_CONFIRM Type = 31
-	// This message type is a single fixed-length hash of the invoice
-	// that the client just received. The client can look up this hash in the
-	// inbound transaction list to display the most recently received invoice
-	// in the UI.
-	Type_PAYMENT_INVOICE_UI Type = 9000
-	// This message type is a single fixed-length hash of the original invoice
-	// that this client sent to the paying client. The UI can look up the
-	// corresponding transaction in the list of completed transactions and
-	// display payment success on the UI. The wallet sends this message
-	// locally after receiving a PAYMENT_RECEIPT message.
-	Type_PAYMENT_RECEIPT_UI Type = 9001
-)
-
-var Type_name = map[int32]string{
-	0:    "NO_TYPE",
-	1:    "TEXT_MESSAGE",
-	2:    "CHANNEL_MESSAGE",
-	6:    "NICKNAME_REQUEST",
-	7:    "NICKNAME_RESPONSE",
-	10:   "UDB_PUSH_KEY",
-	11:   "UDB_PUSH_KEY_RESPONSE",
-	12:   "UDB_GET_KEY",
-	13:   "UDB_GET_KEY_RESPONSE",
-	14:   "UDB_REGISTER",
-	15:   "UDB_REGISTER_RESPONSE",
-	16:   "UDB_SEARCH",
-	17:   "UDB_SEARCH_RESPONSE",
-	20:   "PAYMENT_TRANSACTION",
-	21:   "PAYMENT_RESPONSE",
-	22:   "PAYMENT_INVOICE",
-	23:   "PAYMENT_RECEIPT",
-	30:   "REKEY_TRIGGER",
-	31:   "REKEY_CONFIRM",
-	9000: "PAYMENT_INVOICE_UI",
-	9001: "PAYMENT_RECEIPT_UI",
-}
-
-var Type_value = map[string]int32{
-	"NO_TYPE":               0,
-	"TEXT_MESSAGE":          1,
-	"CHANNEL_MESSAGE":       2,
-	"NICKNAME_REQUEST":      6,
-	"NICKNAME_RESPONSE":     7,
-	"UDB_PUSH_KEY":          10,
-	"UDB_PUSH_KEY_RESPONSE": 11,
-	"UDB_GET_KEY":           12,
-	"UDB_GET_KEY_RESPONSE":  13,
-	"UDB_REGISTER":          14,
-	"UDB_REGISTER_RESPONSE": 15,
-	"UDB_SEARCH":            16,
-	"UDB_SEARCH_RESPONSE":   17,
-	"PAYMENT_TRANSACTION":   20,
-	"PAYMENT_RESPONSE":      21,
-	"PAYMENT_INVOICE":       22,
-	"PAYMENT_RECEIPT":       23,
-	"REKEY_TRIGGER":         30,
-	"REKEY_CONFIRM":         31,
-	"PAYMENT_INVOICE_UI":    9000,
-	"PAYMENT_RECEIPT_UI":    9001,
-}
-
-func (x Type) String() string {
-	return proto.EnumName(Type_name, int32(x))
-}
-
-func (Type) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{0}
-}
-
-// Use this enumeration to get specific transactions from specific transaction
-// lists from the wallet
-type TransactionListTag int32
-
-const (
-	// Transactions in this list are invoices this user made to another user
-	// Most importantly, they include the preimage that this user wants fulfilled,
-	// but the image of this preimage is what the client will send in the invoice.
-	// Transactions enter this list when this user invoices another user.
-	TransactionListTag_OUTBOUND_REQUESTS TransactionListTag = 0
-	// Transactions in this list are invoices that this user received from
-	// other users. Most importantly, this includes the image that this user
-	// will fund.
-	TransactionListTag_INBOUND_REQUESTS TransactionListTag = 1
-	// Transactions in this list are currently processing on a payment bot.
-	// Transactions move from INBOUND_REQUESTS to PENDING_TRANSACTIONS after
-	// a Pay() call.
-	TransactionListTag_PENDING_TRANSACTIONS TransactionListTag = 2
-	// Transactions in this list are completed transactions that increased
-	// the value of this user's wallet. NOTE: They correspond to transactions
-	// originally in the OUTBOUND_REQUESTS list that went through.
-	TransactionListTag_COMPLETED_INBOUND_PAYMENTS TransactionListTag = 3
-	// Transactions in this list are completed transactions that decreased
-	// the value of this user's wallet. NOTE: They correspond to transactions
-	// originally in the INBOUND_REQUESTS list that went through.
-	TransactionListTag_COMPLETED_OUTBOUND_PAYMENTS TransactionListTag = 4
-)
-
-var TransactionListTag_name = map[int32]string{
-	0: "OUTBOUND_REQUESTS",
-	1: "INBOUND_REQUESTS",
-	2: "PENDING_TRANSACTIONS",
-	3: "COMPLETED_INBOUND_PAYMENTS",
-	4: "COMPLETED_OUTBOUND_PAYMENTS",
-}
-
-var TransactionListTag_value = map[string]int32{
-	"OUTBOUND_REQUESTS":           0,
-	"INBOUND_REQUESTS":            1,
-	"PENDING_TRANSACTIONS":        2,
-	"COMPLETED_INBOUND_PAYMENTS":  3,
-	"COMPLETED_OUTBOUND_PAYMENTS": 4,
-}
-
-func (x TransactionListTag) String() string {
-	return proto.EnumName(TransactionListTag_name, int32(x))
-}
-
-func (TransactionListTag) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{1}
-}
-
-// Use this enumeration to request different sort orders of transaction lists
-// when getting all IDs in a transaction list
-type TransactionListOrder int32
-
-const (
-	// Most recently initiated transaction first
-	TransactionListOrder_TIMESTAMP_DESCENDING TransactionListOrder = 0
-	// Stalest transaction first
-	TransactionListOrder_TIMESTAMP_ASCENDING TransactionListOrder = 1
-	// Biggest transaction first
-	TransactionListOrder_VALUE_DESCENDING TransactionListOrder = 2
-	// Smallest transaction first
-	TransactionListOrder_VALUE_ASCENDING TransactionListOrder = 3
-)
-
-var TransactionListOrder_name = map[int32]string{
-	0: "TIMESTAMP_DESCENDING",
-	1: "TIMESTAMP_ASCENDING",
-	2: "VALUE_DESCENDING",
-	3: "VALUE_ASCENDING",
-}
-
-var TransactionListOrder_value = map[string]int32{
-	"TIMESTAMP_DESCENDING": 0,
-	"TIMESTAMP_ASCENDING":  1,
-	"VALUE_DESCENDING":     2,
-	"VALUE_ASCENDING":      3,
-}
-
-func (x TransactionListOrder) String() string {
-	return proto.EnumName(TransactionListOrder_name, int32(x))
-}
-
-func (TransactionListOrder) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{2}
-}
-
-// Is Type.TEXT_MESSAGE
-// Used for conveying simple text messages between users
-type TextMessage struct {
-	// Terminal text foreground color. Used by the console UI
-	Color int32 `protobuf:"zigzag32,2,opt,name=color,proto3" json:"color,omitempty"`
-	// The message text itself. Varies in length
-	Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
-	// Timestamp (Unix time in seconds)
-	// You can use this to display the time when the other user sent the message
-	// TODO Remove this when all messages have timestamps
-	Time                 int64    `protobuf:"varint,4,opt,name=time,proto3" json:"time,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *TextMessage) Reset()         { *m = TextMessage{} }
-func (m *TextMessage) String() string { return proto.CompactTextString(m) }
-func (*TextMessage) ProtoMessage()    {}
-func (*TextMessage) Descriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{0}
-}
-
-func (m *TextMessage) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_TextMessage.Unmarshal(m, b)
-}
-func (m *TextMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_TextMessage.Marshal(b, m, deterministic)
-}
-func (m *TextMessage) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_TextMessage.Merge(m, src)
-}
-func (m *TextMessage) XXX_Size() int {
-	return xxx_messageInfo_TextMessage.Size(m)
-}
-func (m *TextMessage) XXX_DiscardUnknown() {
-	xxx_messageInfo_TextMessage.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TextMessage proto.InternalMessageInfo
-
-func (m *TextMessage) GetColor() int32 {
-	if m != nil {
-		return m.Color
-	}
-	return 0
-}
-
-func (m *TextMessage) GetMessage() string {
-	if m != nil {
-		return m.Message
-	}
-	return ""
-}
-
-func (m *TextMessage) GetTime() int64 {
-	if m != nil {
-		return m.Time
-	}
-	return 0
-}
-
-// Is Type.CHANNEL_MESSAGE
-// This is the type of all messages that come from the channelbot.
-type ChannelMessage struct {
-	// This is the original speaker of the channel message, who sent the
-	// message to the channel bot.
-	SpeakerID []byte `protobuf:"bytes,3,opt,name=speakerID,proto3" json:"speakerID,omitempty"`
-	// This is a serialized parse message under the hood. When writing a
-	// listener for a channel message on a client, you need to unpack the
-	// serialized parse message and rebroadcast it through the listeners.
-	Message              []byte   `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *ChannelMessage) Reset()         { *m = ChannelMessage{} }
-func (m *ChannelMessage) String() string { return proto.CompactTextString(m) }
-func (*ChannelMessage) ProtoMessage()    {}
-func (*ChannelMessage) Descriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{1}
-}
-
-func (m *ChannelMessage) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ChannelMessage.Unmarshal(m, b)
-}
-func (m *ChannelMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ChannelMessage.Marshal(b, m, deterministic)
-}
-func (m *ChannelMessage) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ChannelMessage.Merge(m, src)
-}
-func (m *ChannelMessage) XXX_Size() int {
-	return xxx_messageInfo_ChannelMessage.Size(m)
-}
-func (m *ChannelMessage) XXX_DiscardUnknown() {
-	xxx_messageInfo_ChannelMessage.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ChannelMessage proto.InternalMessageInfo
-
-func (m *ChannelMessage) GetSpeakerID() []byte {
-	if m != nil {
-		return m.SpeakerID
-	}
-	return nil
-}
-
-func (m *ChannelMessage) GetMessage() []byte {
-	if m != nil {
-		return m.Message
-	}
-	return nil
-}
-
-// Is Type.PAYMENT_RESPONSE
-type PaymentResponse struct {
-	// Indicates whether the payment succeeded or failed
-	Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
-	// Response message from the payment bot. You should display this to the
-	// user.
-	Response string `protobuf:"bytes,2,opt,name=response,proto3" json:"response,omitempty"`
-	// TODO Is it correct to use the whole hash?
-	// This is the hash of the payment message that the payment bot received.
-	// The client uses it to remove the correct pending transaction from the
-	// list of pending transactions.
-	ID                   []byte   `protobuf:"bytes,3,opt,name=ID,proto3" json:"ID,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *PaymentResponse) Reset()         { *m = PaymentResponse{} }
-func (m *PaymentResponse) String() string { return proto.CompactTextString(m) }
-func (*PaymentResponse) ProtoMessage()    {}
-func (*PaymentResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{2}
-}
-
-func (m *PaymentResponse) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_PaymentResponse.Unmarshal(m, b)
-}
-func (m *PaymentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_PaymentResponse.Marshal(b, m, deterministic)
-}
-func (m *PaymentResponse) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_PaymentResponse.Merge(m, src)
-}
-func (m *PaymentResponse) XXX_Size() int {
-	return xxx_messageInfo_PaymentResponse.Size(m)
-}
-func (m *PaymentResponse) XXX_DiscardUnknown() {
-	xxx_messageInfo_PaymentResponse.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_PaymentResponse proto.InternalMessageInfo
-
-func (m *PaymentResponse) GetSuccess() bool {
-	if m != nil {
-		return m.Success
-	}
-	return false
-}
-
-func (m *PaymentResponse) GetResponse() string {
-	if m != nil {
-		return m.Response
-	}
-	return ""
-}
-
-func (m *PaymentResponse) GetID() []byte {
-	if m != nil {
-		return m.ID
-	}
-	return nil
-}
-
-// Is Type.PAYMENT_INVOICE
-type PaymentInvoice struct {
-	// Timestamp (Unix time in seconds)
-	// Not currently used but could be useful for the user to verify the
-	// correctness of an invoice.
-	Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"`
-	// This is a single compound coin that the invoicer wants to be funded. The
-	// payer should send a message to the payment bot to fund this compound, if
-	// they wish to pay the payee.
-	CreatedCoin []byte `protobuf:"bytes,2,opt,name=createdCoin,proto3" json:"createdCoin,omitempty"`
-	// The payee should fill this out to describe what the payment is for or
-	// about.
-	Memo                 string   `protobuf:"bytes,3,opt,name=memo,proto3" json:"memo,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *PaymentInvoice) Reset()         { *m = PaymentInvoice{} }
-func (m *PaymentInvoice) String() string { return proto.CompactTextString(m) }
-func (*PaymentInvoice) ProtoMessage()    {}
-func (*PaymentInvoice) Descriptor() ([]byte, []int) {
-	return fileDescriptor_d938547f84707355, []int{3}
-}
-
-func (m *PaymentInvoice) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_PaymentInvoice.Unmarshal(m, b)
-}
-func (m *PaymentInvoice) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_PaymentInvoice.Marshal(b, m, deterministic)
-}
-func (m *PaymentInvoice) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_PaymentInvoice.Merge(m, src)
-}
-func (m *PaymentInvoice) XXX_Size() int {
-	return xxx_messageInfo_PaymentInvoice.Size(m)
-}
-func (m *PaymentInvoice) XXX_DiscardUnknown() {
-	xxx_messageInfo_PaymentInvoice.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_PaymentInvoice proto.InternalMessageInfo
-
-func (m *PaymentInvoice) GetTime() int64 {
-	if m != nil {
-		return m.Time
-	}
-	return 0
-}
-
-func (m *PaymentInvoice) GetCreatedCoin() []byte {
-	if m != nil {
-		return m.CreatedCoin
-	}
-	return nil
-}
-
-func (m *PaymentInvoice) GetMemo() string {
-	if m != nil {
-		return m.Memo
-	}
-	return ""
-}
-
-func init() {
-	proto.RegisterEnum("parse.Type", Type_name, Type_value)
-	proto.RegisterEnum("parse.TransactionListTag", TransactionListTag_name, TransactionListTag_value)
-	proto.RegisterEnum("parse.TransactionListOrder", TransactionListOrder_name, TransactionListOrder_value)
-	proto.RegisterType((*TextMessage)(nil), "parse.TextMessage")
-	proto.RegisterType((*ChannelMessage)(nil), "parse.ChannelMessage")
-	proto.RegisterType((*PaymentResponse)(nil), "parse.PaymentResponse")
-	proto.RegisterType((*PaymentInvoice)(nil), "parse.PaymentInvoice")
-}
-
-func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) }
-
-var fileDescriptor_d938547f84707355 = []byte{
-	// 622 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x54, 0xdb, 0x72, 0xda, 0x3a,
-	0x14, 0x8d, 0x81, 0x5c, 0xd8, 0x10, 0x10, 0x0a, 0x39, 0xe1, 0xe4, 0x9c, 0x49, 0x18, 0x9e, 0x98,
-	0x3c, 0xf4, 0xa5, 0x5f, 0xe0, 0x18, 0x05, 0x34, 0xc1, 0xb2, 0x23, 0xc9, 0x69, 0x93, 0x17, 0x8f,
-	0xeb, 0x68, 0x52, 0xa6, 0xc1, 0x66, 0x6c, 0xb7, 0x93, 0xfc, 0x4b, 0x3f, 0xa0, 0xfd, 0x91, 0x7e,
-	0x57, 0x47, 0x06, 0x5f, 0x26, 0x4f, 0x68, 0xaf, 0xb5, 0xb4, 0xb4, 0xd7, 0xde, 0x83, 0xa1, 0x93,
-	0xbd, 0x6d, 0x54, 0xfa, 0x61, 0x93, 0xc4, 0x59, 0x8c, 0xf7, 0x37, 0x41, 0x92, 0xaa, 0xc9, 0x1d,
-	0x74, 0xa4, 0x7a, 0xcd, 0x6c, 0x95, 0xa6, 0xc1, 0xb3, 0xc2, 0x43, 0xd8, 0x0f, 0xe3, 0x97, 0x38,
-	0x19, 0x35, 0xc6, 0xc6, 0x74, 0xc0, 0xb7, 0x05, 0x1e, 0xc1, 0xe1, 0x7a, 0x2b, 0x18, 0x35, 0xc7,
-	0xc6, 0xb4, 0xcd, 0x8b, 0x12, 0x63, 0x68, 0x65, 0xab, 0xb5, 0x1a, 0xb5, 0xc6, 0xc6, 0xb4, 0xc9,
-	0xf3, 0xf3, 0x64, 0x01, 0x3d, 0xeb, 0x6b, 0x10, 0x45, 0xea, 0xa5, 0x70, 0xfd, 0x1f, 0xda, 0xe9,
-	0x46, 0x05, 0xdf, 0x54, 0x42, 0x67, 0xb9, 0x43, 0x97, 0x57, 0x40, 0xdd, 0xbd, 0x95, 0x73, 0x45,
-	0x39, 0xf9, 0x04, 0x7d, 0x37, 0x78, 0x5b, 0xab, 0x28, 0xe3, 0x2a, 0xdd, 0xc4, 0x51, 0xaa, 0xb4,
-	0x38, 0xfd, 0x1e, 0x86, 0x2a, 0x4d, 0x47, 0xc6, 0xd8, 0x98, 0x1e, 0xf1, 0xa2, 0xc4, 0xe7, 0x70,
-	0x94, 0xec, 0x54, 0x79, 0xf7, 0x6d, 0x5e, 0xd6, 0xb8, 0x07, 0x8d, 0xf2, 0xe5, 0x06, 0x9d, 0x4d,
-	0x1e, 0xa1, 0xb7, 0x33, 0xa6, 0xd1, 0x8f, 0x78, 0x15, 0x56, 0x41, 0x8c, 0x2a, 0x08, 0x1e, 0x43,
-	0x27, 0x4c, 0x54, 0x90, 0xa9, 0x27, 0x2b, 0x5e, 0x45, 0xb9, 0x69, 0x97, 0xd7, 0x21, 0x7d, 0x6b,
-	0xad, 0xd6, 0xf1, 0x6e, 0x2a, 0xf9, 0xf9, 0xea, 0x4f, 0x13, 0x5a, 0xf2, 0x6d, 0xa3, 0x70, 0x07,
-	0x0e, 0x99, 0xe3, 0xcb, 0x07, 0x97, 0xa0, 0x3d, 0x8c, 0xa0, 0x2b, 0xc9, 0x67, 0xe9, 0xdb, 0x44,
-	0x08, 0x73, 0x4e, 0x90, 0x81, 0x4f, 0xa0, 0x6f, 0x2d, 0x4c, 0xc6, 0xc8, 0xb2, 0x04, 0x1b, 0x78,
-	0x08, 0x88, 0x51, 0xeb, 0x96, 0x99, 0x36, 0xf1, 0x39, 0xb9, 0xf3, 0x88, 0x90, 0xe8, 0x00, 0x9f,
-	0xc2, 0xa0, 0x86, 0x0a, 0xd7, 0x61, 0x82, 0xa0, 0x43, 0xed, 0xe9, 0xcd, 0xae, 0x7d, 0xd7, 0x13,
-	0x0b, 0xff, 0x96, 0x3c, 0x20, 0xc0, 0xff, 0xc2, 0x69, 0x1d, 0xa9, 0xc4, 0x1d, 0xdc, 0x87, 0x8e,
-	0xa6, 0xe6, 0x44, 0xe6, 0xda, 0x2e, 0x1e, 0xc1, 0xb0, 0x06, 0x54, 0xd2, 0xe3, 0xc2, 0x97, 0x93,
-	0x39, 0x15, 0x92, 0x70, 0xd4, 0x2b, 0x7c, 0x0b, 0xa4, 0x12, 0xf7, 0x71, 0x0f, 0x40, 0x53, 0x82,
-	0x98, 0xdc, 0x5a, 0x20, 0x84, 0xcf, 0xe0, 0xa4, 0xaa, 0x2b, 0xe1, 0x40, 0x13, 0xae, 0xf9, 0x60,
-	0x13, 0x26, 0x7d, 0xc9, 0x4d, 0x26, 0x4c, 0x4b, 0x52, 0x87, 0xa1, 0xa1, 0xce, 0x5c, 0x10, 0xa5,
-	0xfc, 0x54, 0x8f, 0xa7, 0x40, 0x29, 0xbb, 0x77, 0xa8, 0x45, 0xd0, 0x3f, 0x75, 0x90, 0x13, 0x8b,
-	0x50, 0x57, 0xa2, 0x33, 0x3c, 0x80, 0x63, 0x4e, 0x74, 0x04, 0xc9, 0xe9, 0x7c, 0x4e, 0x38, 0xba,
-	0xa8, 0x20, 0xcb, 0x61, 0x37, 0x94, 0xdb, 0xe8, 0x12, 0x9f, 0x01, 0x7e, 0xe7, 0xe7, 0x7b, 0x14,
-	0xfd, 0xba, 0xa9, 0x13, 0x3b, 0x4f, 0x4d, 0xfc, 0xbe, 0xb9, 0xfa, 0x69, 0x00, 0x96, 0x49, 0x10,
-	0xa5, 0x41, 0x98, 0xad, 0xe2, 0x68, 0xb9, 0x4a, 0x33, 0x19, 0x3c, 0xeb, 0x65, 0x38, 0x9e, 0xbc,
-	0x76, 0x3c, 0x36, 0x2b, 0x56, 0x24, 0xd0, 0x9e, 0x4e, 0x41, 0xd9, 0x3b, 0xd4, 0xd0, 0x43, 0x76,
-	0x09, 0x9b, 0x51, 0x36, 0xaf, 0x87, 0x16, 0xa8, 0x81, 0x2f, 0xe0, 0xdc, 0x72, 0x6c, 0x77, 0x49,
-	0x24, 0x99, 0xf9, 0xc5, 0xcd, 0x5d, 0x23, 0x02, 0x35, 0xf1, 0x25, 0xfc, 0x57, 0xf1, 0xe5, 0x83,
-	0xa5, 0xa0, 0x75, 0x95, 0xc1, 0xf0, 0x5d, 0x77, 0x4e, 0xf2, 0xa4, 0xf4, 0x9f, 0x75, 0x28, 0xa9,
-	0x4d, 0x84, 0x34, 0x6d, 0xd7, 0x9f, 0x11, 0x61, 0x6d, 0xdf, 0x47, 0x7b, 0x7a, 0x03, 0x15, 0x63,
-	0x96, 0x84, 0xa1, 0x7b, 0xbf, 0x37, 0x97, 0x1e, 0xa9, 0xcb, 0x1b, 0x7a, 0xd8, 0x5b, 0xb4, 0x92,
-	0x36, 0xaf, 0x3b, 0x8f, 0xed, 0x70, 0xbd, 0x7a, 0xcd, 0xbf, 0x21, 0x5f, 0x0e, 0xf2, 0x9f, 0x8f,
-	0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x3f, 0xff, 0xd7, 0x16, 0x59, 0x04, 0x00, 0x00,
-}
diff --git a/cmixproto/types.proto b/cmixproto/types.proto
deleted file mode 100644
index d64600bbe301fad6852519326b6d2648d9ab4372..0000000000000000000000000000000000000000
--- a/cmixproto/types.proto
+++ /dev/null
@@ -1,248 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2018 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// Call ./generate.sh to generate the protocol buffer code
-
-syntax = "proto3";
-
-package parse;
-option go_package = "cmixproto";
-
-enum Type {
-    // Used as a wildcard for listeners to listen to all existing types.
-    // Think of it as "No type in particular"
-    NO_TYPE = 0;
-
-
-    // See proto buf documentation below
-    TEXT_MESSAGE = 1;
-    // See proto buf
-    CHANNEL_MESSAGE = 2;
-
-
-    // Nickname request and response messages
-    NICKNAME_REQUEST = 6;
-    NICKNAME_RESPONSE = 7;
-
-    // None of the UDB message types are proto bufs because I haven't had time
-    // to migrate UDB fully to the new systems yet.
-
-    // I was considering migrating these types to proto bufs to make them more
-    // compact for transmission, but you would have to compress them to even
-    // have a chance of fitting the whole key in one Cmix message. In any case,
-    // I don't think the benefit is there for the time investment.
-
-    // The prefixes of the UDB response messages are made redundant by the
-    // message types in this very enumeration, so at some point we can remove
-    // them from the UDB code that generates the responses.
-
-
-    // The push key message includes two string fields, separated by a space.
-    
-    // First field is the key fingerprint, which the UDB uses as an key into
-    // the map of, uhh, the keys. This can be any string that doesn't have a
-    // space in it.
-    
-    // Second field is the key data itself. This should be 2048 bits long
-    // (according to the message length that our prime allows) and is
-    // base64-encoded.
-    UDB_PUSH_KEY = 10;
-    // The push key response message is a string. If the key push was a
-    // success, the UDB should respond with a message that starts with "PUSHKEY
-    // COMPLETE", followed by the fingerprint of the key that was pushed.
-    // If the response doesn't begin with "PUSHKEY COMPLETE", the message is
-    // an error message and should be shown to the user.
-    UDB_PUSH_KEY_RESPONSE = 11;
-    // The get key message includes a single string field with the key
-    // fingerprint of the key that needs gettin'. This is the same fingerprint
-    // you would have pushed.
-    UDB_GET_KEY = 12;
-    // The get key response message is a string. The first space-separated
-    // field should always be "GETKEY". The second field is the fingerprint of
-    // the key. The third field is "NOTFOUND" if the UDB didn't find the key,
-    // or the key itself, encoded in base64, otherwise.
-    UDB_GET_KEY_RESPONSE = 13;
-    // The register message is unchanged from the OG UDB code, except that
-    // the REGISTER command in front has been replaced with the type string
-    // corresponding to this entry in the enumeration.
-
-    // To wit: The first argument in the list of space-separated fields is
-    // the type of the registration. Currently the only allowed type is
-    // "EMAIL". The second argument is the value of the type you're registering
-    // with. In all currently acceptable registration types, this would be an
-    // email address. If you could register with your phone, it would be your
-    // phone number, and so on. Then, the key fingerprint of the user's key is
-    // the third argument. To register successfully, you must have already
-    // pushed the key with that fingerprint.
-    UDB_REGISTER = 14;
-    // The registration response is just a string. It will be either an error
-    // message to show to the user, or the message "REGISTRATION COMPLETE" if
-    // registration was successful.
-    UDB_REGISTER_RESPONSE = 15;
-    // The search message is just another space separated list. The first field
-    // will contain the type of registered user you're searching for, namely
-    // "EMAIL". The second field with contain the value of that type that
-    // you're searching for.
-    UDB_SEARCH = 16;
-    // The search response is a list of fields. The first is always "SEARCH".
-    // The second is always the value that the user searched for. The third is
-    // "FOUND" or "NOTFOUND" depending on whether the UDB found the user. If
-    // the user was FOUND, the last field will contain their key fingerprint,
-    // which you can use with GET_KEY to get the keys you need to talk with
-    // that user. Otherwise, this fourth field won't exist.
-    UDB_SEARCH_RESPONSE = 17;
-
-
-    // The client sends payment transaction messages to the payment bot to
-    // fund compound coins with seed coins. In the current implementation,
-    // there's one compound that gets funded that's from the payee. This comes
-    // across in a PAYMENT_INVOICE. And there's a second compound that contains
-    // the change from the seeds that the payer is using to fund the invoice.
-    // The rest are the seeds that are the source of the payment.
-
-    // All of the seeds and compounds are in an ordered list, and they get
-    // categorized and processed on the payment bot.
-
-    // To get a message of this type, call the methods in the wallet.
-    // TODO expose these methods over the API
-    PAYMENT_TRANSACTION = 20;
-    // See proto buf
-    PAYMENT_RESPONSE = 21;
-    // See proto buf
-    PAYMENT_INVOICE = 22;
-    // This message type includes only the message hash of the original
-    // invoice message. Since there are no fields to delimit, it's not a
-    // proto buffer. When the payee gets a receipt back, they know that the
-    // other person probably paid them, and to check the next published
-    // blockchain for the images of their coins to make sure.
-    // The wallet sends this message after receiving a PAYMENT_RESPONSE
-    // indicating success.
-    PAYMENT_RECEIPT = 23;
-
-    // End to End Rekey message types
-    // Trigger a rekey, this message is used locally in client only
-    REKEY_TRIGGER = 30;
-    // Rekey confirmation message. Sent by partner to confirm completion of a rekey
-    REKEY_CONFIRM = 31;
-
-    // These are specialized messages that convey the information that
-    // the UI needs to know once the wallet's finished updating. They shouldn't
-    // go over the network. Types 9000-9999 are reserved for messages like this.
-
-    // This message type is a single fixed-length hash of the invoice
-    // that the client just received. The client can look up this hash in the
-    // inbound transaction list to display the most recently received invoice
-    // in the UI.
-    PAYMENT_INVOICE_UI = 9000;
-    // This message type is a single fixed-length hash of the original invoice
-    // that this client sent to the paying client. The UI can look up the
-    // corresponding transaction in the list of completed transactions and
-    // display payment success on the UI. The wallet sends this message
-    // locally after receiving a PAYMENT_RECEIPT message.
-    PAYMENT_RECEIPT_UI = 9001;
-}
-
-// Use this enumeration to get specific transactions from specific transaction
-// lists from the wallet
-enum TransactionListTag {
-
-    // Transactions in this list are invoices this user made to another user
-    // Most importantly, they include the preimage that this user wants fulfilled,
-    // but the image of this preimage is what the client will send in the invoice.
-    // Transactions enter this list when this user invoices another user.
-    OUTBOUND_REQUESTS = 0;
-
-    // Transactions in this list are invoices that this user received from
-    // other users. Most importantly, this includes the image that this user
-    // will fund.
-    INBOUND_REQUESTS = 1;
-
-    // Transactions in this list are currently processing on a payment bot.
-    // Transactions move from INBOUND_REQUESTS to PENDING_TRANSACTIONS after
-    // a Pay() call.
-    PENDING_TRANSACTIONS = 2;
-
-    // Transactions in this list are completed transactions that increased
-    // the value of this user's wallet. NOTE: They correspond to transactions
-    // originally in the OUTBOUND_REQUESTS list that went through.
-    COMPLETED_INBOUND_PAYMENTS = 3;
-
-    // Transactions in this list are completed transactions that decreased
-    // the value of this user's wallet. NOTE: They correspond to transactions
-    // originally in the INBOUND_REQUESTS list that went through.
-    COMPLETED_OUTBOUND_PAYMENTS = 4;
-}
-
-// Use this enumeration to request different sort orders of transaction lists
-// when getting all IDs in a transaction list
-enum TransactionListOrder {
-    // Most recently initiated transaction first
-    TIMESTAMP_DESCENDING = 0;
-    // Stalest transaction first
-    TIMESTAMP_ASCENDING = 1;
-    // Biggest transaction first
-    VALUE_DESCENDING = 2;
-    // Smallest transaction first
-    VALUE_ASCENDING = 3;
-}
-
-// Text message types
-
-// Is Type.TEXT_MESSAGE
-// Used for conveying simple text messages between users
-message TextMessage {
-    // Terminal text foreground color. Used by the console UI
-    sint32 color = 2;
-    // The message text itself. Varies in length
-    string message = 3;
-    // Timestamp (Unix time in seconds)
-    // You can use this to display the time when the other user sent the message
-    // TODO Remove this when all messages have timestamps
-    int64 time = 4;
-}
-
-// Is Type.CHANNEL_MESSAGE
-// This is the type of all messages that come from the channelbot.
-message ChannelMessage {
-    // This is the original speaker of the channel message, who sent the
-    // message to the channel bot.
-    bytes speakerID = 3;
-    // This is a serialized parse message under the hood. When writing a
-    // listener for a channel message on a client, you need to unpack the
-    // serialized parse message and rebroadcast it through the listeners.
-    bytes message = 4;
-}
-
-// Payment message types
-
-// Is Type.PAYMENT_RESPONSE
-message PaymentResponse {
-    // Indicates whether the payment succeeded or failed
-    bool success = 1;
-    // Response message from the payment bot. You should display this to the
-    // user.
-    string response = 2;
-    // TODO Is it correct to use the whole hash?
-    // This is the hash of the payment message that the payment bot received.
-    // The client uses it to remove the correct pending transaction from the
-    // list of pending transactions.
-    bytes ID = 3;
-}
-
-// Is Type.PAYMENT_INVOICE
-message PaymentInvoice {
-    // Timestamp (Unix time in seconds)
-    // Not currently used but could be useful for the user to verify the
-    // correctness of an invoice.
-    int64 time = 1;
-    // This is a single compound coin that the invoicer wants to be funded. The
-    // payer should send a message to the payment bot to fund this compound, if
-    // they wish to pay the payee.
-    bytes createdCoin = 2;
-    // The payee should fill this out to describe what the payment is for or
-    // about.
-    string memo = 3;
-}
diff --git a/crypto/decrypt.go b/crypto/decrypt.go
deleted file mode 100644
index 9004d5aa028585d9ff05dd7894f6d9d80041ea32..0000000000000000000000000000000000000000
--- a/crypto/decrypt.go
+++ /dev/null
@@ -1,77 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package crypto
-
-import (
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/primitives/format"
-)
-
-// E2EDecrypt uses the E2E key to decrypt the message
-// It returns an error in case of HMAC verification failure
-// or in case of a decryption error (related to padding)
-// If it succeeds, it modifies the passed message
-func E2EDecrypt(grp *cyclic.Group, key *cyclic.Int,
-	msg *format.Message) error {
-	// First thing to do is check MAC
-	if !hash.VerifyHMAC(msg.Contents.Get(), msg.GetMAC(), key.Bytes()) {
-		return errors.New("HMAC verification failed for E2E message")
-	}
-	var iv [e2e.AESBlockSize]byte
-	fp := msg.GetKeyFP()
-	copy(iv[:], fp[:e2e.AESBlockSize])
-	// decrypt the timestamp in the associated data
-	decryptedTimestamp, err := e2e.DecryptAES256WithIV(
-		key.Bytes(), iv, msg.GetTimestamp())
-	if err != nil {
-		return errors.New("Timestamp decryption failed for E2E message: " + err.Error())
-	}
-	// TODO deserialize this somewhere along the line and provide methods
-	// to mobile developers on the bindings to interact with the timestamps
-	decryptedTimestamp = append(decryptedTimestamp, 0)
-	msg.SetTimestamp(decryptedTimestamp)
-	// Decrypt e2e
-	decryptedPayload, err := e2e.Decrypt(grp, key, msg.Contents.Get())
-
-	if err != nil {
-		return errors.New("Failed to decrypt E2E message: " + err.Error())
-	}
-	msg.Contents.SetRightAligned(decryptedPayload)
-	return nil
-}
-
-// E2EDecryptUnsafe uses the E2E key to decrypt the message
-// It returns an error in case of HMAC verification failure
-// It doesn't expect the payload to be padded
-// If it succeeds, it modifies the passed message
-func E2EDecryptUnsafe(grp *cyclic.Group, key *cyclic.Int,
-	msg *format.Message) error {
-	// First thing to do is check MAC
-	if !hash.VerifyHMAC(msg.Contents.Get(), msg.GetMAC(), key.Bytes()) {
-		return errors.New("HMAC verification failed for E2E message")
-	}
-	var iv [e2e.AESBlockSize]byte
-	fp := msg.GetKeyFP()
-	copy(iv[:], fp[:e2e.AESBlockSize])
-	// decrypt the timestamp in the associated data
-	decryptedTimestamp, err := e2e.DecryptAES256WithIV(
-		key.Bytes(), iv, msg.GetTimestamp())
-	if err != nil {
-		return errors.New("Timestamp decryption failed for E2E message: " + err.Error())
-	}
-	// TODO deserialize this somewhere along the line and provide methods
-	// to mobile developers on the bindings to interact with the timestamps
-	decryptedTimestamp = append(decryptedTimestamp, 0)
-	msg.SetTimestamp(decryptedTimestamp)
-	// Decrypt e2e
-	decryptedPayload := e2e.DecryptUnsafe(grp, key, msg.Contents.Get())
-	msg.Contents.Set(decryptedPayload)
-	return nil
-}
diff --git a/crypto/encrypt.go b/crypto/encrypt.go
deleted file mode 100644
index 7686521401474733e45bc47b508e90501d4ef8ea..0000000000000000000000000000000000000000
--- a/crypto/encrypt.go
+++ /dev/null
@@ -1,112 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package crypto
-
-import (
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/cmix"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/xx_network/comms/connect"
-)
-
-// CMIX Encrypt performs the encryption
-// of the msg to a team of nodes
-// It returns a new msg
-func CMIXEncrypt(session user.Session, topology *connect.Circuit, salt []byte,
-	msg *format.Message) (*format.Message, [][]byte) {
-	// Generate the encryption key
-	nodeKeys := session.GetNodeKeys(topology)
-
-	baseKeys := make([]*cyclic.Int, len(nodeKeys))
-	for i, key := range nodeKeys {
-		baseKeys[i] = key.TransmissionKey
-	}
-
-	ecrMsg := cmix.ClientEncrypt(session.GetCmixGroup(), msg, salt, baseKeys)
-
-	h, err := hash.NewCMixHash()
-	if err != nil {
-		globals.Log.ERROR.Printf("Cound not get hash for KMAC generation: %+v", h)
-	}
-
-	KMAC := cmix.GenerateKMACs(salt, baseKeys, h)
-
-	return ecrMsg, KMAC
-}
-
-// E2EEncrypt uses the E2E key to encrypt msg
-// to its intended recipient
-// It also properly populates the associated data
-// It modifies the passed msg instead of returning a new one
-func E2EEncrypt(grp *cyclic.Group,
-	key *cyclic.Int, keyFP format.Fingerprint,
-	msg *format.Message) {
-	msg.SetKeyFP(keyFP)
-
-	// Encrypt the timestamp using key
-	// Timestamp bytes were previously stored
-	// and GO only uses 15 bytes, so use those
-	var iv [e2e.AESBlockSize]byte
-	copy(iv[:], keyFP[:e2e.AESBlockSize])
-	encryptedTimestamp, err :=
-		e2e.EncryptAES256WithIV(key.Bytes(), iv,
-			msg.GetTimestamp()[:15])
-	if err != nil {
-		panic(err)
-	}
-	msg.SetTimestamp(encryptedTimestamp)
-
-	// E2E encrypt the msg
-	encPayload, err := e2e.Encrypt(grp, key, msg.Contents.GetRightAligned())
-	if err != nil {
-		globals.Log.ERROR.Panicf(err.Error())
-	}
-	msg.Contents.Set(encPayload)
-
-	// MAC is HMAC(key, ciphertext)
-	// Currently, the MAC doesn't include any of the associated data
-	MAC := hash.CreateHMAC(encPayload, key.Bytes())
-	msg.SetMAC(MAC)
-}
-
-// E2EEncryptUnsafe uses the E2E key to encrypt msg
-// to its intended recipient
-// It doesn't apply padding to the payload, so it can be unsafe
-// if the payload is small
-// It also properly populates the associated data
-// It modifies the passed msg instead of returning a new one
-func E2EEncryptUnsafe(grp *cyclic.Group,
-	key *cyclic.Int, keyFP format.Fingerprint,
-	msg *format.Message) {
-	msg.SetKeyFP(keyFP)
-
-	// Encrypt the timestamp using key
-	// Timestamp bytes were previously stored
-	// and GO only uses 15 bytes, so use those
-	var iv [e2e.AESBlockSize]byte
-	copy(iv[:], keyFP[:e2e.AESBlockSize])
-	encryptedTimestamp, err :=
-		e2e.EncryptAES256WithIV(key.Bytes(), iv,
-			msg.GetTimestamp()[:15])
-	if err != nil {
-		panic(err)
-	}
-	msg.SetTimestamp(encryptedTimestamp)
-
-	// E2E encrypt the msg
-	encPayload := e2e.EncryptUnsafe(grp, key, msg.Contents.Get())
-	msg.Contents.Set(encPayload)
-
-	// MAC is HMAC(key, ciphertext)
-	// Currently, the MAC doesn't include any of the associated data
-	MAC := hash.CreateHMAC(encPayload, key.Bytes())
-	msg.SetMAC(MAC)
-}
diff --git a/crypto/encryptdecrypt_test.go b/crypto/encryptdecrypt_test.go
deleted file mode 100644
index 7cc9ad2c852126e10e63b560486946d7864f3066..0000000000000000000000000000000000000000
--- a/crypto/encryptdecrypt_test.go
+++ /dev/null
@@ -1,398 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package crypto
-
-import (
-	"bytes"
-	"encoding/binary"
-	"gitlab.com/elixxir/client/user"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/cmix"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"golang.org/x/crypto/blake2b"
-	"os"
-	"testing"
-	"time"
-)
-
-const numNodes = 5
-
-var salt = []byte(
-	"fdecfa52a8ad1688dbfa7d16df74ebf27e535903c469cefc007ebbe1ee895064")
-
-var session user.Session
-var serverPayloadAKey *cyclic.Int
-var serverPayloadBKey *cyclic.Int
-
-var topology *connect.Circuit
-
-func setup() {
-
-	cmixGrp, e2eGrp := getGroups()
-
-	user.InitUserRegistry(cmixGrp)
-
-	UID := new(id.ID)
-	binary.BigEndian.PutUint64(UID[:], 18)
-	UID.SetType(id.User)
-	u, ok := user.Users.GetUser(UID)
-	if !ok {
-		panic("Didn't get user 18 from registry")
-	}
-
-	var nodeSlice []*id.ID
-
-	//build topology
-	for i := 0; i < numNodes; i++ {
-		nodeId := new(id.ID)
-		nodeId[0] = byte(i)
-		nodeId.SetType(id.Node)
-		nodeSlice = append(nodeSlice, nodeId)
-	}
-
-	topology = connect.NewCircuit(nodeSlice)
-
-	tempKey := cmixGrp.NewInt(1)
-	serverPayloadAKey = cmixGrp.NewInt(1)
-	serverPayloadBKey = cmixGrp.NewInt(1)
-
-	h, _ := blake2b.New256(nil)
-
-	session = user.NewSession(nil, u, nil, nil,
-		nil, nil, nil,
-		nil, nil, cmixGrp, e2eGrp, "password")
-
-	for i := 0; i < numNodes; i++ {
-
-		nk := user.NodeKeys{}
-
-		h.Reset()
-		h.Write(salt)
-
-		nk.TransmissionKey = cmixGrp.NewInt(int64(2 + i))
-		cmix.NodeKeyGen(cmixGrp, salt, nk.TransmissionKey, tempKey)
-		cmixGrp.Mul(serverPayloadAKey, tempKey, serverPayloadAKey)
-
-		cmix.NodeKeyGen(cmixGrp, h.Sum(nil), nk.TransmissionKey, tempKey)
-		cmixGrp.Mul(serverPayloadBKey, tempKey, serverPayloadBKey)
-
-		session.PushNodeKey(topology.GetNodeAtIndex(i), nk)
-
-	}
-
-}
-
-func TestMain(m *testing.M) {
-	setup()
-	os.Exit(m.Run())
-}
-
-func TestFullEncryptDecrypt(t *testing.T) {
-	cmixGrp, e2eGrp := getGroups()
-
-	sender := id.NewIdFromUInt(38, id.User, t)
-	recipient := id.NewIdFromUInt(29, id.User, t)
-	msg := format.NewMessage()
-	msg.SetRecipient(recipient)
-	msgPayload := []byte("help me, i'm stuck in an" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory")
-	// Normally, msgPayload would be the right length due to padding
-	//msgPayload = append(msgPayload, make([]byte,
-	//	format.ContentsLen-len(msgPayload)-format.PadMinLen)...)
-	msg.Contents.SetRightAligned(msgPayload)
-	now := time.Now()
-	nowBytes, _ := now.MarshalBinary()
-	// Normally, nowBytes would be the right length due to AES encryption
-	nowBytes = append(nowBytes, make([]byte, format.TimestampLen-len(nowBytes))...)
-	msg.SetTimestamp(nowBytes)
-
-	key := e2eGrp.NewInt(42)
-	h, _ := hash.NewCMixHash()
-	h.Write(key.Bytes())
-	fp := format.Fingerprint{}
-	copy(fp[:], h.Sum(nil))
-
-	// E2E Encryption
-	E2EEncrypt(e2eGrp, key, fp, msg)
-
-	// CMIX Encryption
-	encMsg, _ := CMIXEncrypt(session, topology, salt, msg)
-
-	// Server will decrypt payload (which is OK because the payload is now e2e)
-	// This block imitates what the server does during the realtime
-	payloadA := cmixGrp.NewIntFromBytes(encMsg.GetPayloadA())
-	payloadB := cmixGrp.NewIntFromBytes(encMsg.GetPayloadB())
-	// Multiply payloadA and associated data by serverPayloadBkey
-	cmixGrp.Mul(payloadA, serverPayloadAKey, payloadA)
-	// Multiply payloadB data only by serverPayloadAkey
-	cmixGrp.Mul(payloadB, serverPayloadBKey, payloadB)
-
-	decMsg := format.NewMessage()
-	decMsg.SetPayloadA(payloadA.LeftpadBytes(uint64(format.PayloadLen)))
-	decMsg.SetDecryptedPayloadB(payloadB.LeftpadBytes(uint64(format.PayloadLen)))
-
-	// E2E Decryption
-	err := E2EDecrypt(e2eGrp, key, decMsg)
-
-	if err != nil {
-		t.Errorf("E2EDecrypt returned error: %v", err.Error())
-	}
-
-	decryptedRecipient, err := decMsg.GetRecipient()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !decryptedRecipient.Cmp(recipient) {
-		t.Errorf("Recipient differed from expected: Got %q, expected %q",
-			decryptedRecipient, sender)
-	}
-	if !bytes.Equal(decMsg.Contents.GetRightAligned(), msgPayload) {
-		t.Errorf("Decrypted payload differed from expected: Got %q, "+
-			"expected %q", decMsg.Contents.Get(), msgPayload)
-	}
-}
-
-// E2E unsafe functions should only be used when the payload
-// to be sent occupies the whole payload structure, i.e. 256 bytes
-func TestFullEncryptDecrypt_Unsafe(t *testing.T) {
-	cmixGrp, e2eGrp := getGroups()
-	sender := id.NewIdFromUInt(38, id.User, t)
-	recipient := id.NewIdFromUInt(29, id.User, t)
-	msg := format.NewMessage()
-	msg.SetRecipient(recipient)
-	msgPayload := []byte(
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-			" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-			" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-			" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-			" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-			" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-			" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory")
-	msg.Contents.Set(msgPayload[:format.ContentsLen])
-
-	msg.SetTimestamp(make([]byte, 16))
-
-	key := e2eGrp.NewInt(42)
-	h, _ := hash.NewCMixHash()
-	h.Write(key.Bytes())
-	fp := format.Fingerprint{}
-	copy(fp[:], h.Sum(nil))
-
-	// E2E Encryption without padding
-	E2EEncryptUnsafe(e2eGrp, key, fp, msg)
-
-	// CMIX Encryption
-	encMsg, _ := CMIXEncrypt(session, topology, salt, msg)
-
-	// Server will decrypt payload (which is OK because the payload is now e2e)
-	// This block imitates what the server does during the realtime
-	var encryptedNet *pb.Slot
-	{
-		payload := cmixGrp.NewIntFromBytes(encMsg.GetPayloadA())
-		assocData := cmixGrp.NewIntFromBytes(encMsg.GetPayloadB())
-		// Multiply payload and associated data by transmission key only
-		cmixGrp.Mul(payload, serverPayloadAKey, payload)
-		// Multiply associated data only by transmission key
-		cmixGrp.Mul(assocData, serverPayloadBKey, assocData)
-		encryptedNet = &pb.Slot{
-			SenderID: sender.Bytes(),
-			Salt:     salt,
-			PayloadA: payload.LeftpadBytes(uint64(format.PayloadLen)),
-			PayloadB: assocData.LeftpadBytes(uint64(format.PayloadLen)),
-		}
-	}
-
-	decMsg := format.NewMessage()
-	decMsg.SetPayloadA(encryptedNet.PayloadA)
-	decMsg.SetDecryptedPayloadB(encryptedNet.PayloadB)
-
-	// E2E Decryption
-	err := E2EDecryptUnsafe(e2eGrp, key, decMsg)
-
-	if err != nil {
-		t.Errorf("E2EDecryptUnsafe returned error: %v", err.Error())
-	}
-
-	decryptedRecipient, err := decMsg.GetRecipient()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !decryptedRecipient.Cmp(recipient) {
-		t.Errorf("Recipient differed from expected: Got %q, expected %q",
-			decryptedRecipient, sender)
-	}
-	if !bytes.Equal(decMsg.Contents.Get(), msgPayload[:format.ContentsLen]) {
-		t.Errorf("Decrypted payload differed from expected: Got %q, "+
-			"expected %q", decMsg.Contents.Get(), msgPayload[:format.ContentsLen])
-	}
-}
-
-// Test that E2EEncrypt panics if the payload is too big (can't be padded)
-func TestE2EEncrypt_Panic(t *testing.T) {
-	_, e2eGrp := getGroups()
-	recipient := id.NewIdFromUInt(29, id.User, t)
-	msg := format.NewMessage()
-	msg.SetRecipient(recipient)
-	msgPayload := []byte("help me, i'm stuck in an" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory" +
-		" EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory")
-	msgPayload = msgPayload[:format.ContentsLen]
-	msg.Contents.Set(msgPayload)
-	msg.SetTimestamp(make([]byte, 16))
-
-	key := e2eGrp.NewInt(42)
-	h, _ := hash.NewCMixHash()
-	h.Write(key.Bytes())
-	fp := format.Fingerprint{}
-	copy(fp[:], h.Sum(nil))
-
-	defer func() {
-		if r := recover(); r == nil {
-			t.Errorf("E2EEncrypt should panic on payload too large")
-		}
-	}()
-
-	// E2E Encryption Panics
-	E2EEncrypt(e2eGrp, key, fp, msg)
-}
-
-// Test that E2EDecrypt and E2EDecryptUnsafe handle errors correctly
-func TestE2EDecrypt_Errors(t *testing.T) {
-	_, e2eGrp := getGroups()
-	recipient := id.NewIdFromUInt(29, id.User, t)
-	msg := format.NewMessage()
-	msg.SetRecipient(recipient)
-	msgPayload := []byte("help me, i'm stuck in an EnterpriseTextLabelDescriptorSetPipelineStateFactoryBeanFactory ")
-	msg.Contents.SetRightAligned(msgPayload)
-	msg.SetTimestamp(make([]byte, 16))
-
-	key := e2eGrp.NewInt(42)
-	h, _ := hash.NewCMixHash()
-	h.Write(key.Bytes())
-	fp := format.Fingerprint{}
-	copy(fp[:], h.Sum(nil))
-
-	// E2E Encryption
-	E2EEncrypt(e2eGrp, key, fp, msg)
-
-	// Copy message
-	badMsg := format.NewMessage()
-	badMsg.SetPayloadA(msg.GetPayloadA())
-	badMsg.SetPayloadB(msg.GetPayloadB())
-
-	// Corrupt MAC to make decryption fail
-	badMsg.SetMAC([]byte("sakfaskfajskasfkkaskfanjffffjnaf"))
-
-	// E2E Decryption returns error
-	err := E2EDecrypt(e2eGrp, key, badMsg)
-
-	if err == nil {
-		t.Errorf("E2EDecrypt should have returned error")
-	} else {
-		t.Logf("E2EDecrypt error: %v", err.Error())
-	}
-
-	// Unsafe E2E Decryption returns error
-	err = E2EDecryptUnsafe(e2eGrp, key, badMsg)
-
-	if err == nil {
-		t.Errorf("E2EDecryptUnsafe should have returned error")
-	} else {
-		t.Logf("E2EDecryptUnsafe error: %v", err.Error())
-	}
-
-	// Set correct MAC again
-	badMsg.SetMAC(msg.GetMAC())
-
-	// Corrupt timestamp to make decryption fail
-	badMsg.SetTimestamp([]byte("ABCDEF1234567890"))
-
-	// E2E Decryption returns error
-	err = E2EDecrypt(e2eGrp, key, badMsg)
-
-	if err == nil {
-		t.Errorf("E2EDecrypt should have returned error")
-	} else {
-		t.Logf("E2EDecrypt error: %v", err.Error())
-	}
-
-	// Unsafe E2E Decryption returns error
-	err = E2EDecryptUnsafe(e2eGrp, key, badMsg)
-
-	if err == nil {
-		t.Errorf("E2EDecryptUnsafe should have returned error")
-	} else {
-		t.Logf("E2EDecryptUnsafe error: %v", err.Error())
-	}
-
-	// Set correct Timestamp again
-	badMsg.SetTimestamp(msg.GetTimestamp())
-
-	// Corrupt payload to make decryption fail
-	badMsg.Contents.SetRightAligned([]byte(
-		"sakomnsfjeiknheuijhgfyaistuajhfaiuojfkhufijsahufiaij"))
-
-	// Calculate new MAC to avoid failing on that verification again
-	newMAC := hash.CreateHMAC(badMsg.Contents.Get(), key.Bytes())
-	badMsg.SetMAC(newMAC)
-
-	// E2E Decryption returns error
-	err = E2EDecrypt(e2eGrp, key, badMsg)
-
-	if err == nil {
-		t.Errorf("E2EDecrypt should have returned error")
-	} else {
-		t.Logf("E2EDecrypt error: %v", err.Error())
-	}
-}
-
-func getGroups() (*cyclic.Group, *cyclic.Group) {
-
-	cmixGrp := cyclic.NewGroup(
-		large.NewIntFromString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+
-			"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+
-			"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+
-			"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+
-			"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"+
-			"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"+
-			"83655D23DCA3AD961C62F356208552BB9ED529077096966D"+
-			"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"+
-			"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"+
-			"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"+
-			"15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16),
-		large.NewIntFromString("2", 16))
-
-	e2eGrp := cyclic.NewGroup(
-		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B"+
-			"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE"+
-			"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F"+
-			"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041"+
-			"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45"+
-			"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209"+
-			"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29"+
-			"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E"+
-			"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2"+
-			"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696"+
-			"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E"+
-			"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873"+
-			"847AEF49F66E43873", 16),
-		large.NewIntFromString("2", 16))
-
-	return cmixGrp, e2eGrp
-
-}
diff --git a/globals/log.go b/globals/log.go
deleted file mode 100644
index e9bfc4a45df68296c87ff7eebc42727fd8f73a35..0000000000000000000000000000000000000000
--- a/globals/log.go
+++ /dev/null
@@ -1,54 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package globals
-
-import (
-	jww "github.com/spf13/jwalterweatherman"
-	"io"
-	"io/ioutil"
-	"log"
-	"os"
-)
-
-// Log is logging everything to this notepad so that the CUI can replace it
-// with its own notepad and get logging statements from the client
-var Log = jww.NewNotepad(jww.LevelInfo, jww.LevelInfo, os.Stdout,
-	ioutil.Discard, "CLIENT", log.Ldate|log.Ltime)
-
-// InitLog initializes logging thresholds and the log path.
-// verbose turns on debug logging, setting the log path to nil
-// uses std out.
-func InitLog(verbose bool, logPath string) *jww.Notepad {
-	logLevel := jww.LevelInfo
-	logFlags := (log.Ldate | log.Ltime)
-	stdOut := io.Writer(os.Stdout)
-	logFile := ioutil.Discard
-
-	// If the verbose flag is set, print all logs and
-	// print microseconds as well
-	if verbose {
-		logLevel = jww.LevelDebug
-		logFlags = (log.Ldate | log.Ltime | log.Lmicroseconds)
-	}
-	// If the logpath is empty or not set to - (stdout),
-	// set up the log file and do not log to stdout
-	if logPath != "" && logPath != "-" {
-		// Create log file, overwrites if existing
-		lF, err := os.OpenFile(logPath,
-			os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
-		if err != nil {
-			Log.WARN.Println("Invalid or missing log path," +
-				" stdout used.")
-		} else {
-			logFile = io.Writer(lF)
-			stdOut = ioutil.Discard
-		}
-	}
-
-	return jww.NewNotepad(logLevel, logLevel, stdOut, logFile,
-		"CLIENT", logFlags)
-}
diff --git a/globals/statusEvents.go b/globals/statusEvents.go
deleted file mode 100644
index 834db9f61d9ab557c127c09ac83209c41ff64b77..0000000000000000000000000000000000000000
--- a/globals/statusEvents.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package globals
-
-//Registration
-const REG_KEYGEN = 1       //Generating Cryptographic Keys
-const REG_PRECAN = 2       //Doing a Precanned Registration (Not Secure)
-const REG_UID_GEN = 3      //Generating User ID
-const REG_PERM = 4         //Validating User Identity With Permissioning Server
-const REG_NODE = 5         //Registering with Nodes
-const REG_FAIL = 6         //Failed to Register with Nodes
-const REG_SECURE_STORE = 7 //Creating Local Secure Session
-const REG_SAVE = 8         //Storing Session
-//UDB registration
-const UDB_REG_PUSHKEY = 9   //Pushing Cryptographic Material to the User Discovery Bot
-const UDB_REG_PUSHUSER = 10 //Registering User with the User Discovery Bot
-//UDB Search
-const UDB_SEARCH_LOOK = 11        //Searching for User in User Discovery
-const UDB_SEARCH_GETKEY = 12      //Getting Keying Material From User Discovery
-const UDB_SEARCH_BUILD_CREDS = 13 //Building secure end to end relationship
diff --git a/globals/storage.go b/globals/storage.go
deleted file mode 100644
index aecd54789a000a50eb0fc766db9937a8cd00f6a3..0000000000000000000000000000000000000000
--- a/globals/storage.go
+++ /dev/null
@@ -1,202 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package globals
-
-import (
-	"os"
-	"sync"
-)
-
-const (
-	NoSave uint8 = iota
-	LocationA
-	LocationB
-)
-
-type Storage interface {
-	SetLocation(string, string) error
-	GetLocation() (string, string)
-	SaveA([]byte) error
-	SaveB([]byte) error
-	LoadA() []byte
-	LoadB() []byte
-	IsEmpty() bool
-}
-
-type DefaultStorage struct {
-	locationA string
-	locationB string
-	sync.Mutex
-}
-
-func (ds *DefaultStorage) SetLocation(locationA, locationB string) error {
-	ds.Lock()
-	ds.locationA = locationA
-	ds.locationB = locationB
-	ds.Unlock()
-	return nil
-}
-
-func (ds *DefaultStorage) GetLocation() (string, string) {
-	ds.Lock()
-	defer ds.Unlock()
-	return ds.locationA, ds.locationB
-}
-
-func (ds *DefaultStorage) IsEmpty() bool {
-	_, err := os.Stat(ds.locationA)
-	firstEmpty := err != nil && os.IsNotExist(err)
-	_, err = os.Stat(ds.locationB)
-	secondEmpty := err != nil && os.IsNotExist(err)
-	return firstEmpty && secondEmpty
-}
-
-func (ds *DefaultStorage) SaveA(data []byte) error {
-	return dsSaveHelper(ds.locationA, data)
-}
-
-func (ds *DefaultStorage) LoadA() []byte {
-	return dsLoadHelper(ds.locationA)
-}
-
-func (ds *DefaultStorage) SaveB(data []byte) error {
-	return dsSaveHelper(ds.locationB, data)
-}
-
-func (ds *DefaultStorage) LoadB() []byte {
-	return dsLoadHelper(ds.locationB)
-}
-
-type RamStorage struct {
-	DataA []byte
-	DataB []byte
-}
-
-func (rs *RamStorage) SetLocation(string, string) error {
-	return nil
-}
-
-func (rs *RamStorage) GetLocation() (string, string) {
-	return "", ""
-}
-
-func (rs *RamStorage) SaveA(data []byte) error {
-	rs.DataA = make([]byte, len(data))
-	copy(rs.DataA, data)
-	return nil
-}
-
-func (rs *RamStorage) SaveB(data []byte) error {
-	rs.DataB = make([]byte, len(data))
-	copy(rs.DataB, data)
-	return nil
-}
-
-func (rs *RamStorage) LoadA() []byte {
-	b := make([]byte, len(rs.DataA))
-	copy(b, rs.DataA)
-
-	return b
-}
-
-func (rs *RamStorage) LoadB() []byte {
-	b := make([]byte, len(rs.DataB))
-	copy(b, rs.DataB)
-
-	return b
-}
-
-func (rs *RamStorage) IsEmpty() bool {
-	return (rs.DataA == nil || len(rs.DataA) == 0) && (rs.DataB == nil || len(rs.DataB) == 0)
-}
-
-func dsLoadHelper(loc string) []byte {
-	// Check if the file exists, return nil if it does not
-	finfo, err1 := os.Stat(loc)
-
-	if err1 != nil {
-		Log.ERROR.Printf("Default Storage Load: Unknown Error Occurred on"+
-			" file check: \n  %v", err1.Error())
-		return nil
-	}
-
-	b := make([]byte, finfo.Size())
-
-	// Open the file, return nil if it cannot be opened
-	f, err2 := os.Open(loc)
-
-	defer func() {
-		if f != nil {
-			f.Close()
-		} else {
-			Log.WARN.Println("Could not close file, file is nil")
-		}
-	}()
-
-	if err2 != nil {
-		Log.ERROR.Printf("Default Storage Load: Unknown Error Occurred on"+
-			" file open: \n  %v", err2.Error())
-		return nil
-	}
-
-	// Read the data from the file, return nil if read fails
-	_, err3 := f.Read(b)
-
-	if err3 != nil {
-		Log.ERROR.Printf("Default Storage Load: Unknown Error Occurred on"+
-			" file read: \n  %v", err3.Error())
-		return nil
-	}
-
-	return b
-
-}
-
-func dsSaveHelper(loc string, data []byte) error {
-	//check if the file exists, delete if it does
-	_, err1 := os.Stat(loc)
-
-	if err1 == nil {
-		errRmv := os.Remove(loc)
-		if errRmv != nil {
-			Log.WARN.Printf("Could not remove Storage File B: %s", errRmv)
-		}
-	} else if !os.IsNotExist(err1) {
-		Log.ERROR.Printf("Default Storage Save: Unknown Error Occurred on"+
-			" file check: \n  %v",
-			err1.Error())
-		return err1
-	}
-
-	//create new file
-	f, err2 := os.Create(loc)
-
-	defer func() {
-		if f != nil {
-			f.Close()
-		} else {
-			Log.WARN.Println("Could not close file, file is nil")
-		}
-	}()
-
-	if err2 != nil {
-		Log.ERROR.Printf("Default Storage Save: Unknown Error Occurred on"+
-			" file creation: \n %v", err2.Error())
-		return err2
-	}
-
-	//Save to file
-	_, err3 := f.Write(data)
-
-	if err3 != nil {
-		Log.ERROR.Printf("Default Storage Save: Unknown Error Occurred on"+
-			" file write: \n %v", err3.Error())
-		return err3
-	}
-
-	return nil
-}
diff --git a/globals/storage_test.go b/globals/storage_test.go
deleted file mode 100644
index 96abc9532bd258097ffd73da5c4430def403fe5d..0000000000000000000000000000000000000000
--- a/globals/storage_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package globals
-
-import (
-	"os"
-	"reflect"
-	"testing"
-)
-
-func TestInitStorage(t *testing.T) {
-	TestDataA := []byte{12, 14, 54}
-	TestDataB := []byte{69, 42, 32}
-	TestSaveLocA := "testStorageA.data"
-	TestSaveLocB := "testStorageB.data"
-
-	// Test DefaultStorage initialization without existing storage
-	storage := &DefaultStorage{}
-	//Check that storage is empty prior to any Save calls
-	if !storage.IsEmpty() {
-		t.Errorf("ds.IsEmpty failed to detect an empty storage")
-	}
-
-	storage.SetLocation(TestSaveLocA, TestSaveLocB)
-
-	// Test DS saveA
-	err := storage.SaveA(TestDataA)
-	if err != nil {
-		t.Errorf("ds.Save failed to create a save file A at: %v",
-			TestSaveLocA)
-	}
-	// Check that save file was made
-	if !exists(TestSaveLocA) {
-		t.Errorf("ds.Save failed to create a save file A at: %v",
-			TestSaveLocA)
-	}
-	//Check that the storage is not empty after a saveA call
-	if storage.IsEmpty() {
-		t.Errorf("ds.IsEmpty failed to detect a non-empty storage")
-	}
-
-	// Test DS loadA
-	actualData := storage.LoadA()
-	if reflect.DeepEqual(actualData, TestDataA) != true {
-		t.Errorf("ds.Load failed to load expected data on A. Expected:%v Actual:%v",
-			TestDataA, actualData)
-	}
-
-	// Test DS saveB
-	err = storage.SaveB(TestDataB)
-	if err != nil {
-		t.Errorf("ds.Save failed to create a save file B at: %v",
-			TestSaveLocB)
-	}
-	// Check that save file was made
-	if !exists(TestSaveLocB) {
-		t.Errorf("ds.Save failed to create a save file B at: %v",
-			TestSaveLocB)
-	}
-
-	// Test DS loadA
-	actualData = storage.LoadB()
-	if reflect.DeepEqual(actualData, TestDataB) != true {
-		t.Errorf("ds.Load failed to load expected data on B. Expected:%v Actual:%v",
-			TestDataB, actualData)
-	}
-
-	// Test RamStorage
-	store := RamStorage{}
-	actualData = nil
-	// Test A
-	store.SaveA(TestDataA)
-	actualData = store.LoadA()
-	if reflect.DeepEqual(actualData, TestDataA) != true {
-		t.Errorf("rs.Load failed to load expected data A. Expected:%v Actual:%v",
-			TestDataA, actualData)
-	}
-	//Test B
-	store.SaveB(TestDataB)
-	actualData = store.LoadB()
-	if reflect.DeepEqual(actualData, TestDataB) != true {
-		t.Errorf("rs.Load failed to load expected data B. Expected:%v Actual:%v",
-			TestDataB, actualData)
-	}
-	os.Remove(TestSaveLocA)
-	os.Remove(TestSaveLocB)
-}
-
-// exists returns whether the given file or directory exists or not
-func exists(path string) bool {
-	_, err := os.Stat(path)
-	if err == nil {
-		return true
-	}
-	if os.IsNotExist(err) {
-		return false
-	}
-	return true
-}
-
-func TestDefaultStorage_GetLocation(t *testing.T) {
-	locationA := "hi"
-	locationB := "hi2"
-
-	ds := DefaultStorage{locationA: locationA, locationB: locationB}
-
-	recievedLocA, recievedLocB := ds.GetLocation()
-
-	if recievedLocA != locationA {
-		t.Errorf("defaultStorage.GetLocation returned incorrect location A. Expected:%v Actual:%v",
-			locationA, recievedLocA)
-	}
-
-	if recievedLocB != locationB {
-		t.Errorf("defaultStorage.GetLocation returned incorrect location B. Expected:%v Actual:%v",
-			locationB, recievedLocB)
-	}
-}
-
-func TestRamStorage_GetLocation(t *testing.T) {
-
-	ds := RamStorage{}
-
-	a, b := ds.GetLocation()
-
-	if a != "" && b != "" {
-		t.Errorf("RamStorage.GetLocation returned incorrect location. Actual: '', ''; Expected:'%v','%v'",
-			a, b)
-	}
-}
-
-func Test_dsLoadHelper_LocError(t *testing.T) {
-	testLoc := "~a/test"
-
-	result := dsLoadHelper(testLoc)
-
-	if result != nil {
-		t.Errorf("dsLoadHelper() did not error on invalid path.")
-	}
-}
diff --git a/globals/terminator.go b/globals/terminator.go
deleted file mode 100644
index 5b17199f2a3d75466da1e17088135d7620d572fe..0000000000000000000000000000000000000000
--- a/globals/terminator.go
+++ /dev/null
@@ -1,47 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package globals
-
-import (
-	"time"
-)
-
-type ThreadTerminator chan chan bool
-
-func NewThreadTerminator() ThreadTerminator {
-	t := make(chan chan bool, 1)
-	return t
-}
-
-func (t ThreadTerminator) Terminate() {
-	t <- nil
-}
-
-// Try's to kill a thread controlled by a termination channel for the length of
-// the timeout, returns its success. pass 0 for no timeout
-func (t ThreadTerminator) BlockingTerminate(timeout uint64) bool {
-
-	killNotify := make(chan bool)
-	defer close(killNotify)
-
-	if timeout != 0 {
-		timer := time.NewTimer(time.Duration(timeout) * time.Millisecond)
-		defer timer.Stop()
-
-		t <- killNotify
-
-		select {
-		case _ = <-killNotify:
-			return true
-		case <-timer.C:
-			return false
-		}
-	} else {
-		_ = <-killNotify
-		return true
-	}
-}
diff --git a/globals/terminator_test.go b/globals/terminator_test.go
deleted file mode 100644
index b43c8cc03cb4e315c945f1e2983c5f85f555bc8c..0000000000000000000000000000000000000000
--- a/globals/terminator_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package globals
-
-import (
-	"testing"
-	"time"
-)
-
-func TestNewThreadTerminator(t *testing.T) {
-
-	term := NewThreadTerminator()
-
-	var success bool
-
-	go func(term ThreadTerminator) {
-		term <- nil
-	}(term)
-
-	timer := time.NewTimer(time.Duration(1000) * time.Millisecond)
-	defer timer.Stop()
-
-	select {
-	case _ = <-term:
-		success = true
-	case <-timer.C:
-		success = false
-	}
-
-	if !success {
-		t.Errorf("NewThreadTerminator: Could not use the ThreadTerminator to" +
-			" stop a thread")
-	}
-
-}
-
-func TestBlockingTerminate(t *testing.T) {
-
-	term := NewThreadTerminator()
-
-	go func(term ThreadTerminator) {
-		var killNotify chan<- bool
-
-		q := false
-
-		for !q {
-			select {
-			case killNotify = <-term:
-				q = true
-			}
-
-			close(term)
-
-			killNotify <- true
-
-		}
-	}(term)
-
-	success := term.BlockingTerminate(1000)
-
-	if !success {
-		t.Errorf("BlockingTerminate: Thread did not terminate in time")
-	}
-
-}
diff --git a/globals/version_vars.go b/globals/version_vars.go
deleted file mode 100644
index 515d73091b0bb8be7d2f6fceecb4d456689ae81f..0000000000000000000000000000000000000000
--- a/globals/version_vars.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-// This file was generated by robots at
-// 2020-09-17 15:15:34.009207648 -0700 PDT m=+0.006867337
-package globals
-
-const GITVERSION = `4edecf1 Update minor version number`
-const SEMVER = "1.5.0"
-const DEPENDENCIES = `module gitlab.com/elixxir/client
-
-go 1.13
-
-require (
-	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
-	github.com/golang/protobuf v1.4.2
-	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
-	github.com/pelletier/go-toml v1.6.0 // indirect
-	github.com/pkg/errors v0.9.1
-	github.com/smartystreets/assertions v1.0.1 // indirect
-	github.com/spf13/afero v1.2.2 // indirect
-	github.com/spf13/cast v1.3.1 // indirect
-	github.com/spf13/cobra v1.0.0
-	github.com/spf13/jwalterweatherman v1.1.0
-	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.6.2
-	gitlab.com/elixxir/comms v0.0.0-20200917221445-8a509560122a
-	gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a
-	gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842
-	gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6
-	gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820
-	golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
-	gopkg.in/ini.v1 v1.52.0 // indirect
-)
-`
diff --git a/go.mod b/go.mod
index f09582005d3f13c4f7bd1648403dba085cf99263..cbd7c3d26f28e77fded7b281126283cacffa2857 100644
--- a/go.mod
+++ b/go.mod
@@ -4,22 +4,33 @@ go 1.13
 
 require (
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
-	github.com/golang/protobuf v1.4.2
+	github.com/golang/protobuf v1.4.3
 	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
-	github.com/pelletier/go-toml v1.6.0 // indirect
+	github.com/magiconair/properties v1.8.4 // indirect
+	github.com/mitchellh/mapstructure v1.4.0 // indirect
+	github.com/pelletier/go-toml v1.8.1 // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/smartystreets/assertions v1.0.1 // indirect
-	github.com/spf13/afero v1.2.2 // indirect
+	github.com/spf13/afero v1.5.1 // indirect
 	github.com/spf13/cast v1.3.1 // indirect
-	github.com/spf13/cobra v1.0.0
+	github.com/spf13/cobra v1.1.1
 	github.com/spf13/jwalterweatherman v1.1.0
-	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.6.2
-	gitlab.com/elixxir/comms v0.0.0-20200917221445-8a509560122a
-	gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a
-	gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842
-	gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6
-	gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820
-	golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
-	gopkg.in/ini.v1 v1.52.0 // indirect
+	github.com/spf13/viper v1.7.1
+	gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228
+	gitlab.com/elixxir/comms v0.0.4-0.20210311180506-28ae742c5e35
+	gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2
+	gitlab.com/elixxir/ekv v0.1.4
+	gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b
+	gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01
+	gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96
+	gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a
+	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
+	golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
+	golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
+	google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect
+	google.golang.org/grpc v1.34.0 // indirect
+	google.golang.org/protobuf v1.25.0
+	gopkg.in/ini.v1 v1.62.0 // indirect
 )
+
+replace google.golang.org/grpc => github.com/grpc/grpc-go v1.27.1
diff --git a/go.sum b/go.sum
index 190067e18ca7432bdcd981a1d87fefc33e36e04a..15bdda0ba26fa5f23d7aa3c5993d95b458cf8a7d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,19 +1,35 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ=
+github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -23,15 +39,15 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -40,13 +56,15 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -57,6 +75,9 @@ github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -65,50 +86,104 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc/grpc-go v1.27.1 h1:EluyjU5nlbuNJSEktNl600PIpzbO2OcvZWfWV1jFvKM=
+github.com/grpc/grpc-go v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
+github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
+github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nyaruka/phonenumbers v1.0.60 h1:nnAcNwmZflhegiImm6MkvjlRRyoaSw1ox/jGPAewWTg=
+github.com/nyaruka/phonenumbers v1.0.60/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
-github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
+github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -120,7 +195,10 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -131,25 +209,27 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=
+github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
+github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
-github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
-github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
@@ -157,103 +237,203 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-gitlab.com/elixxir/comms v0.0.0-20200707210150-b8ebd0951d23/go.mod h1:OsWMZ1O/R9fOkm+PoHnR3rkXfFtipGoPs73FuKuurHY=
-gitlab.com/elixxir/comms v0.0.0-20200917221445-8a509560122a h1:WsrvOH/UUkOImFg79R6O93b/ZR40KiyHNXKf2rSRXGk=
-gitlab.com/elixxir/comms v0.0.0-20200917221445-8a509560122a/go.mod h1:L2Va13j2AbQkpkveOQmNzrQD37uI5NKeBhYH+LWMOx0=
-gitlab.com/elixxir/crypto v0.0.0-20200707005343-97f868cbd930 h1:9qzfwyR12OYgn3j30qcHZHHVfWshWnH54lcAHppEROQ=
-gitlab.com/elixxir/crypto v0.0.0-20200707005343-97f868cbd930/go.mod h1:LHBAaEf48a0/AjU118rjoworH0LgXifhAqmNX3ZRvME=
-gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a h1:peZpulfSqLSceA5ovtzQ5MPgQt4YbJY8FzpV2S2Nrhc=
-gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a/go.mod h1:LHBAaEf48a0/AjU118rjoworH0LgXifhAqmNX3ZRvME=
-gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842 h1:m1zDQ6UadpuMnV7nvnyR+DUXE3AisRnVjajTb1xZE4c=
-gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842/go.mod h1:bXY0kgbV5BHYda4YY5/hiG5bjimGK+R3PYub5yM9C/s=
-gitlab.com/elixxir/primitives v0.0.0-20200706165052-9fe7a4fb99a3 h1:GTfflZBNLeBq3UApYog0J3+hytdkoRsDduGQji2wyEU=
-gitlab.com/elixxir/primitives v0.0.0-20200706165052-9fe7a4fb99a3/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6 h1:7xLD8w5qAKN1YqG2UiMiN3rODUACyQME83uDlVhvWLo=
-gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820 h1:vdozJQgrnznmHJLS38aOXprLKZXClnHJ9ljY/70aWuI=
-gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820/go.mod h1:J+GJ6fn71a4xnYVvbcrhtvWSOQIqqhaGcaej5xB3/JY=
+github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0=
+github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
+github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/blake3 v0.0.4 h1:vtZ4X8B2lKXZFg2Xyg6Wo36mvmnJvc2VQYTtA4RDCkI=
+github.com/zeebo/blake3 v0.0.4/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34=
+github.com/zeebo/blake3 v0.1.0 h1:sP3n5SxSbzU8x4Svc4ZcQv7SmQOqCkiKBeAZWP+hePo=
+github.com/zeebo/blake3 v0.1.0/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34=
+github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05 h1:4pW5fMvVkrgkMXdvIsVRRTs69DWYA8uNNQsu1stfVKU=
+github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05/go.mod h1:Gr+78ptB0MwXxm//LBaEvBiaXY7hXJ6KGe2V32X2F6E=
+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.20210310191320-05cba0d1a468 h1:EnfzSAdV+3WwWJ6cGY5xENrHwafy8tIUtz5qvDEZ4sw=
+gitlab.com/elixxir/comms v0.0.4-0.20210310191320-05cba0d1a468/go.mod h1:96cMuVVlarB+I6nuFKdq4zCagQkbhVK/MUzRk3yOymI=
+gitlab.com/elixxir/comms v0.0.4-0.20210310191636-1bca0ddac665 h1:2tWjyhX21DBXeAjiHJTFL/MCpb9L9mg7NE09sS8tb2k=
+gitlab.com/elixxir/comms v0.0.4-0.20210310191636-1bca0ddac665/go.mod h1:96cMuVVlarB+I6nuFKdq4zCagQkbhVK/MUzRk3yOymI=
+gitlab.com/elixxir/comms v0.0.4-0.20210310223853-60622bd841a8 h1:jhka79rXEc7hHn6uDbAjY7NhhKYWwtQ+iHtsa/Jfw1w=
+gitlab.com/elixxir/comms v0.0.4-0.20210310223853-60622bd841a8/go.mod h1:96cMuVVlarB+I6nuFKdq4zCagQkbhVK/MUzRk3yOymI=
+gitlab.com/elixxir/comms v0.0.4-0.20210311180506-28ae742c5e35 h1:t/ILeoWel5Im+zLQUX2FIroZvrfAkxOaL3DCA8enKcE=
+gitlab.com/elixxir/comms v0.0.4-0.20210311180506-28ae742c5e35/go.mod h1:96cMuVVlarB+I6nuFKdq4zCagQkbhVK/MUzRk3yOymI=
+gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4 h1:28ftZDeYEko7xptCZzeFWS1Iam95dj46TWFVVlKmw6A=
+gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
+gitlab.com/elixxir/crypto v0.0.3 h1:znCt/x2bL4y8czTPaaFkwzdgSgW3BJc/1+dxyf1jqVw=
+gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
+gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2 h1:JMbUxcOjFpdCBUMZS5g8CWfNdPJ6pP8xsAZbnLj66jc=
+gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2/go.mod h1:TMZMB24OsjF6y3LCyBMzDucbOx1cGQCCeuKV9lJA/DU=
+gitlab.com/elixxir/ekv v0.1.4 h1:NLVMwsFEKArWcsDHu2DbXlm9374iSgn7oIA3rVSsvjc=
+gitlab.com/elixxir/ekv v0.1.4/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 h1:q61anawANlNAExfkeQEE1NCsNih6vNV1FFLoUQX6txQ=
+gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE=
+gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b h1:TswWfqiZqsdPLeWsfe7VJHMlV01W792kRHGYfYwb2Lk=
+gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b/go.mod h1:/e3a4KPqmA9V22qKSZ9prfYYNzIzvLI8xh7noVV091w=
+gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
+gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01 h1:f93iz7mTHt3r37O97vaQD8otohihLN3OnAEEbDGQdVs=
+gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01/go.mod h1:aNPRHmPssXc1JMJ83DAknT2C2iMgKL1wH3//AqQrhQc=
+gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE=
+gitlab.com/xx_network/crypto v0.0.4 h1:lpKOL5mTJ2awWMfgBy30oD/UvJVrWZzUimSHlOdZZxo=
+gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk=
+gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96 h1:VZGJNhuU6YunKyK4MbNZf25UxQsmU1bH5SnbK93tI7Q=
+gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96/go.mod h1:TtaHpuX0lcuTTtcq+pz+lMusjyTgvSohIHFOlVwN1uU=
+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 h1:CCVslUwNC7Ul7NG5nu3ThGTSVUt1TxNRX+47f5TUwnk=
+gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug=
+gitlab.com/xx_network/primitives v0.0.2 h1:r45yKenJ9e7PylI1ZXJ1Es09oYNaYXjxVy9+uYlwo7Y=
+gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc=
+gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a h1:Ume9QbJ4GoJh7v5yg/YVDjowJHx/VFeOC/A4PJZUm9g=
+gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/ring v0.0.2 h1:TlPjlbFdhtJrwvRgIg4ScdngMTaynx/ByHBRZiXCoL0=
+gitlab.com/xx_network/ring v0.0.2/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0 h1:eIYIE7EC5/Wv5Kbz8bJPaq+TN3kq3W8S+LSm62vM0DY=
 golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
-golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+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=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+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=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200514193133-8feb7f20f2a2 h1:RwW6+LxyOQJ7oeoZ76GIJlwt/O0J5cN2fk+q/jK27kQ=
-google.golang.org/genproto v0.0.0-20200514193133-8feb7f20f2a2/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200709005830-7a2ca40e9dc3 h1:JwLN1jVnmIsfE4HkDVe2AblFAbo0Z+4cjteDSOnv6oE=
-google.golang.org/genproto v0.0.0-20200709005830-7a2ca40e9dc3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 h1:Zk6zlGXdtYdcY5TL+VrbTfmifvk3VvsXopCpszsHPBA=
+google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -273,18 +453,28 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
-gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/interfaces/auth.go b/interfaces/auth.go
new file mode 100644
index 0000000000000000000000000000000000000000..68e18be6fc51bb90a67b082f396582bb79b88625
--- /dev/null
+++ b/interfaces/auth.go
@@ -0,0 +1,45 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package interfaces
+
+import (
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type RequestCallback func(requestor contact.Contact, message string)
+type ConfirmCallback func(partner contact.Contact)
+
+type Auth interface {
+	// Adds a general callback to be used on auth requests. This will be preempted
+	// by any specific callback
+	AddGeneralRequestCallback(cb RequestCallback)
+	// Adds a general callback to be used on auth requests. This will not be
+	// preempted by any specific callback. It is recommended that the specific
+	// callbacks are used, this is primarily for debugging.
+	AddOverrideRequestCallback(cb RequestCallback)
+	// Adds a specific callback to be used on auth requests. This will preempt a
+	// general callback, meaning the request will be heard on this callback and not
+	// the general. Request will still be heard on override callbacks.
+	AddSpecificRequestCallback(id *id.ID, cb RequestCallback)
+	// Removes a specific callback to be used on auth requests.
+	RemoveSpecificRequestCallback(id *id.ID)
+	// Adds a general callback to be used on auth confirms. This will be preempted
+	// by any specific callback
+	AddGeneralConfirmCallback(cb ConfirmCallback)
+	// Adds a general callback to be used on auth confirms. This will not be
+	// preempted by any specific callback. It is recommended that the specific
+	// callbacks are used, this is primarily for debugging.
+	AddOverrideConfirmCallback(cb ConfirmCallback)
+	// Adds a specific callback to be used on auth confirms. This will preempt a
+	// general callback, meaning the request will be heard on this callback and not
+	// the general. Request will still be heard on override callbacks.
+	AddSpecificConfirmCallback(id *id.ID, cb ConfirmCallback)
+	// Removes a specific callback to be used on auth confirm.
+	RemoveSpecificConfirmCallback(id *id.ID)
+}
diff --git a/interfaces/bloom.go b/interfaces/bloom.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b5a002e0b5d9b48a7512d3f861009c7ad7dc109
--- /dev/null
+++ b/interfaces/bloom.go
@@ -0,0 +1,4 @@
+package interfaces
+
+const BloomFilterSize = 648 // In Bits
+const BloomFilterHashes = 10
diff --git a/interfaces/clientError.go b/interfaces/clientError.go
new file mode 100644
index 0000000000000000000000000000000000000000..39c6706c7fb83b1df657c46d46f733c55c9b562d
--- /dev/null
+++ b/interfaces/clientError.go
@@ -0,0 +1,9 @@
+package interfaces
+
+type ClientError struct {
+	Source  string
+	Message string
+	Trace   string
+}
+
+type ClientErrorReport func(source, message, trace string)
diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go
new file mode 100644
index 0000000000000000000000000000000000000000..498500b039a72da3ba9336e2815d34706bf86dbd
--- /dev/null
+++ b/interfaces/contact/contact.go
@@ -0,0 +1,160 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package contact
+
+import (
+	"bytes"
+	"crypto"
+	"encoding/base64"
+	"encoding/binary"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+const sizeByteLength = 2
+const fingerprintLength = 15
+
+// Contact implements the Contact interface defined in interface/contact.go,
+// in go, the structure is meant to be edited directly, the functions are for
+// bindings compatibility
+type Contact struct {
+	ID             *id.ID
+	DhPubKey       *cyclic.Int
+	OwnershipProof []byte
+	Facts          fact.FactList
+}
+
+// Marshal saves the Contact in a compact byte slice. The byte slice has
+// the following structure (not to scale).
+//
+// +----------+----------------+---------+----------+----------+----------------+----------+
+// | DhPubKey | OwnershipProof |  Facts  |    ID    |          |                |          |
+// |   size   |      size      |   size  |          | DhPubKey | OwnershipProof | FactList |
+// |  2 bytes |     2 bytes    | 2 bytes | 33 bytes |          |                |          |
+// +----------+----------------+---------+----------+----------+----------------+----------+
+func (c Contact) Marshal() []byte {
+	var buff bytes.Buffer
+	b := make([]byte, sizeByteLength)
+
+	// Write size of DhPubKey
+	var dhPubKey []byte
+	if c.DhPubKey != nil {
+		dhPubKey = c.DhPubKey.BinaryEncode()
+		binary.PutVarint(b, int64(len(dhPubKey)))
+	}
+	buff.Write(b)
+
+	// Write size of OwnershipProof
+	binary.PutVarint(b, int64(len(c.OwnershipProof)))
+	buff.Write(b)
+
+	// Write length of Facts
+	factList := c.Facts.Stringify()
+	binary.PutVarint(b, int64(len(factList)))
+	buff.Write(b)
+
+	// Write ID
+	if c.ID != nil {
+		buff.Write(c.ID.Marshal())
+	} else {
+		// Handle nil ID
+		buff.Write(make([]byte, id.ArrIDLen))
+	}
+
+	// Write DhPubKey
+	buff.Write(dhPubKey)
+
+	// Write OwnershipProof
+	buff.Write(c.OwnershipProof)
+
+	// Write fact list
+	buff.Write([]byte(factList))
+
+	return buff.Bytes()
+}
+
+// Unmarshal decodes the byte slice produced by Contact.Marshal into a Contact.
+func Unmarshal(b []byte) (Contact, error) {
+	if len(b) < sizeByteLength*3+id.ArrIDLen {
+		return Contact{}, errors.Errorf("Length of provided buffer (%d) too "+
+			"short; length must be at least %d.",
+			len(b), sizeByteLength*3+id.ArrIDLen)
+	}
+
+	c := Contact{DhPubKey: &cyclic.Int{}}
+	var err error
+	buff := bytes.NewBuffer(b)
+
+	// Get size of each field
+	dhPubKeySize, _ := binary.Varint(buff.Next(sizeByteLength))
+	ownershipProofSize, _ := binary.Varint(buff.Next(sizeByteLength))
+	factsSize, _ := binary.Varint(buff.Next(sizeByteLength))
+
+	// Get and unmarshal ID
+	c.ID, err = id.Unmarshal(buff.Next(id.ArrIDLen))
+	if err != nil {
+		return Contact{}, errors.Errorf("Failed to unmarshal Contact ID: %+v", err)
+	}
+
+	// Handle nil ID
+	if bytes.Equal(c.ID.Marshal(), make([]byte, id.ArrIDLen)) {
+		c.ID = nil
+	}
+
+	// Get and decode DhPubKey
+	if dhPubKeySize == 0 {
+		// Handle nil key
+		c.DhPubKey = nil
+	} else {
+		if err = c.DhPubKey.BinaryDecode(buff.Next(int(dhPubKeySize))); err != nil {
+			return Contact{}, errors.Errorf("Failed to binary decode Contact DhPubKey: %+v", err)
+		}
+	}
+
+	// Get OwnershipProof
+	if ownershipProofSize == 0 {
+		// Handle nil OwnershipProof
+		c.OwnershipProof = nil
+	} else {
+		c.OwnershipProof = buff.Next(int(ownershipProofSize))
+	}
+
+	// Get and unstringify fact list
+	c.Facts, _, err = fact.UnstringifyFactList(string(buff.Next(int(factsSize))))
+	if err != nil {
+		return Contact{}, errors.Errorf("Failed to unstringify Contact fact list: %+v", err)
+	}
+
+	return c, nil
+}
+
+// GetFingerprint creates a 15 character long fingerprint of the contact off of
+// the ID and DH public key.
+func (c Contact) GetFingerprint() string {
+	// Generate hash
+	sha := crypto.SHA256
+	h := sha.New()
+
+	// Hash ID and DH public key
+	h.Write(c.ID.Bytes())
+	h.Write(c.DhPubKey.Bytes())
+	data := h.Sum(nil)
+
+	// Base64 encode hash and truncate it
+	return base64.StdEncoding.EncodeToString(data)[:fingerprintLength]
+}
+
+// Equal determines if the two contacts have the same values.
+func Equal(a, b Contact) bool {
+	return a.ID.Cmp(b.ID) &&
+		a.DhPubKey.Cmp(b.DhPubKey) == 0 &&
+		bytes.Equal(a.OwnershipProof, b.OwnershipProof) &&
+		a.Facts.Stringify() == b.Facts.Stringify()
+}
diff --git a/interfaces/contact/contact_test.go b/interfaces/contact/contact_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2d43f1818986c26f81ad5f389dce028e88ec10cd
--- /dev/null
+++ b/interfaces/contact/contact_test.go
@@ -0,0 +1,232 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package contact
+
+import (
+	"crypto"
+	"encoding/base64"
+	"encoding/json"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Tests marshaling and unmarshalling of a common Contact.
+func TestContact_Marshal_Unmarshal(t *testing.T) {
+	expectedContact := Contact{
+		ID:       id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey: getCycInt(256),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+			{Fact: "devinputvalidation@elixxir.io", T: fact.Email},
+			{Fact: "6502530000US", T: fact.Phone},
+			{Fact: "6502530001US", T: fact.Phone},
+		},
+	}
+
+	buff := expectedContact.Marshal()
+
+	testContact, err := Unmarshal(buff)
+	if err != nil {
+		t.Errorf("Unmarshal() produced an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expectedContact, testContact) {
+		t.Errorf("Unmarshaled Contact does not match expected."+
+			"\nexpected: %#v\nreceived: %#v", expectedContact, testContact)
+	}
+}
+
+// Tests marshaling and unmarshalling of a Contact with nil fields.
+func TestContact_Marshal_Unmarshal_Nil(t *testing.T) {
+	expectedContact := Contact{}
+
+	buff := expectedContact.Marshal()
+
+	testContact, err := Unmarshal(buff)
+	if err != nil {
+		t.Errorf("Unmarshal() produced an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expectedContact, testContact) {
+		t.Errorf("Unmarshaled Contact does not match expected."+
+			"\nexpected: %#v\nreceived: %#v", expectedContact, testContact)
+	}
+}
+
+// Tests the size of marshaling and JSON marshaling of a Contact with a large
+// amount of data.
+func TestContact_Marshal_Size(t *testing.T) {
+	expectedContact := Contact{
+		ID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey:       getCycInt(512),
+		OwnershipProof: make([]byte, 1024),
+		Facts: fact.FactList{
+			{Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongUsername", T: fact.Username},
+			{Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongEmail@elixxir.io", T: fact.Email},
+			{Fact: "6502530000US", T: fact.Phone},
+		},
+	}
+	rand.Read(expectedContact.OwnershipProof)
+
+	buff := expectedContact.Marshal()
+
+	marshalBuff, err := json.Marshal(expectedContact)
+	if err != nil {
+		t.Errorf("Marshal() produced an error: %+v", err)
+	}
+
+	t.Logf("size of buff:        %d", len(buff))
+	t.Logf("size of marshalBuff: %d", len(marshalBuff))
+	t.Logf("ratio: %.2f%%", float32(len(buff))/float32(len(marshalBuff))*100)
+	t.Logf("%s", marshalBuff)
+
+	if len(marshalBuff) < len(buff) {
+		t.Errorf("JSON Contact smaller than marshaled contact."+
+			"\nJSON:    %d\nmarshal: %d", len(marshalBuff), len(buff))
+	}
+}
+
+// Unit test of GetFingerprint.
+func TestContact_GetFingerprint(t *testing.T) {
+	c := Contact{
+		ID:       id.NewIdFromString("Samwise", id.User, t),
+		DhPubKey: getCycInt(512),
+	}
+
+	testFP := c.GetFingerprint()
+	if len(testFP) != fingerprintLength {
+		t.Errorf("GetFingerprint() returned fingerprint with unexpected length."+
+			"\nexpected length: %d\nreceived length: %d",
+			fingerprintLength, len(testFP))
+	}
+
+	// Generate expected fingerprint
+	h := crypto.SHA256.New()
+	h.Write(c.ID.Bytes())
+	h.Write(c.DhPubKey.Bytes())
+	expectedFP := base64.StdEncoding.EncodeToString(h.Sum(nil))[:fingerprintLength]
+
+	if strings.Compare(expectedFP, testFP) != 0 {
+		t.Errorf("GetFingerprint() returned expected fingerprint."+
+			"\nexpected: %s\nreceived: %s", expectedFP, testFP)
+	}
+
+}
+
+// Consistency test for changes in underlying dependencies.
+func TestContact_GetFingerprint_Consistency(t *testing.T) {
+	expected := []string{
+		"rBUw1n4jtH4uEYq", "Z/Jm1OUwDaql5cd", "+vHLzY+yH96zAiy",
+		"cZm5Iz78ViOIlnh", "9LqrcbFEIV4C4LX", "ll4eykGpMWYlxw+",
+		"6YQshWJhdPL6ajx", "Y6gTPVEzow4IHOm", "6f/rT2vWxDC9tdt",
+		"rwqbDT+PoeA6Iww", "YN4IFijP/GZ172O", "ScbHVQc2T9SXQ2m",
+		"50mfbCXQ+LIqiZn", "cyRYdMKXByiFdtC", "7g6ujy7iIbJVl4F",
+	}
+
+	for i := range expected {
+		c := Contact{
+			ID:       id.NewIdFromUInt(uint64(i), id.User, t),
+			DhPubKey: getGroup().NewInt(25),
+		}
+
+		fp := c.GetFingerprint()
+		if expected[i] != fp {
+			t.Errorf("GetFingerprint() did not output the expected fingerprint (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expected[i], fp)
+		}
+	}
+}
+
+// Happy path.
+func TestEqual(t *testing.T) {
+	a := Contact{
+		ID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey:       getCycInt(512),
+		OwnershipProof: make([]byte, 1024),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+			{Fact: "devinputvalidation@elixxir.io", T: fact.Email},
+		},
+	}
+	rand.Read(a.OwnershipProof)
+	b := Contact{
+		ID:             a.ID,
+		DhPubKey:       a.DhPubKey,
+		OwnershipProof: a.OwnershipProof,
+		Facts:          a.Facts,
+	}
+	c := Contact{
+		ID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey:       getCycInt(512),
+		OwnershipProof: make([]byte, 1024),
+	}
+
+	if !Equal(a, b) {
+		t.Errorf("Equal reported two equal contacts as different."+
+			"\na: %+v\nb: +%v", a, b)
+	}
+
+	if Equal(a, c) {
+		t.Errorf("Equal reported two unequal contacts as the same."+
+			"\na: %+v\nc: +%v", a, c)
+	}
+}
+
+func getCycInt(size int) *cyclic.Int {
+	var primeString = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E0" +
+		"88A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F" +
+		"14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDE" +
+		"E386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48" +
+		"361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED52907709" +
+		"6966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E8603" +
+		"9B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D22" +
+		"61898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458" +
+		"DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619" +
+		"DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE1" +
+		"17577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A" +
+		"92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150B" +
+		"DA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B" +
+		"2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
+		"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFF" +
+		"FFF"
+
+	buff, err := csprng.GenerateInGroup([]byte(primeString), size, csprng.NewSystemRNG())
+	if err != nil {
+		panic(err)
+	}
+
+	grp := cyclic.NewGroup(large.NewIntFromString(primeString, 16),
+		large.NewInt(2)).NewIntFromBytes(buff)
+
+	return grp
+}
+
+func getGroup() *cyclic.Group {
+	return cyclic.NewGroup(
+		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D4941"+
+			"3394C049B7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688"+
+			"B55B3DD2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861"+
+			"575E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC"+
+			"718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FF"+
+			"B1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBC"+
+			"A23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD"+
+			"161C7738F32BF29A841698978825B4111B4BC3E1E198455095958333D776D8B2B"+
+			"EEED3A1A1A221A6E37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C"+
+			"4F50D7D7803D2D4F278DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F"+
+			"1390B5D3FEACAF1696015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F"+
+			"96789C38E89D796138E6319BE62E35D87B1048CA28BE389B575E994DCA7554715"+
+			"84A09EC723742DC35873847AEF49F66E43873", 16),
+		large.NewIntFromString("2", 16))
+}
diff --git a/interfaces/healthTracker.go b/interfaces/healthTracker.go
new file mode 100644
index 0000000000000000000000000000000000000000..39441984ee0088b9e82e33e7b7a11ab689288f00
--- /dev/null
+++ b/interfaces/healthTracker.go
@@ -0,0 +1,15 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package interfaces
+
+type HealthTracker interface {
+	AddChannel(chan bool)
+	AddFunc(f func(bool))
+	IsHealthy() bool
+	WasHealthy() bool
+}
diff --git a/interfaces/message/encryptionType.go b/interfaces/message/encryptionType.go
new file mode 100644
index 0000000000000000000000000000000000000000..cdd86c06af973526a5589d8073664af6c75762ff
--- /dev/null
+++ b/interfaces/message/encryptionType.go
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+type EncryptionType uint8
+
+const (
+	None EncryptionType = 0
+	E2E  EncryptionType = 1
+)
+
+func (et EncryptionType) String() string {
+	switch et {
+	case None:
+		return "None"
+	case E2E:
+		return "E2E"
+	default:
+		return "Unknown"
+	}
+}
diff --git a/interfaces/message/receiveMessage.go b/interfaces/message/receiveMessage.go
new file mode 100644
index 0000000000000000000000000000000000000000..fad6fb750ccc21cc9f03391056a8559a53cd5159
--- /dev/null
+++ b/interfaces/message/receiveMessage.go
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"time"
+)
+
+type Receive struct {
+	ID          e2e.MessageID
+	Payload     []byte
+	MessageType Type
+	Sender      *id.ID
+	RecipientID *id.ID
+	EphemeralID ephemeral.Id
+	Timestamp   time.Time
+	Encryption  EncryptionType
+}
diff --git a/interfaces/message/sendMessage.go b/interfaces/message/sendMessage.go
new file mode 100644
index 0000000000000000000000000000000000000000..88795e95ca1aa7a906a2ef953ee059b0fc8af325
--- /dev/null
+++ b/interfaces/message/sendMessage.go
@@ -0,0 +1,16 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import "gitlab.com/xx_network/primitives/id"
+
+type Send struct {
+	Recipient   *id.ID
+	Payload     []byte
+	MessageType Type
+}
diff --git a/interfaces/message/type.go b/interfaces/message/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..71a1c72ff431ab1c6164856fe892e72af8c21a68
--- /dev/null
+++ b/interfaces/message/type.go
@@ -0,0 +1,52 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+const TypeLen = 4
+
+type Type uint32
+
+const (
+	/*general message types*/
+	// Used as a wildcard for listeners to listen to all existing types.
+	// Think of it as "No type in particular"
+	NoType Type = 0
+
+	// A message with no message structure
+	// this is a reserved type, messages sent via SendCmix automatically gain
+	// this type. Sent messages with this type will be rejected and received
+	// non Cmix messages will be ignored
+	Raw Type = 1
+
+	//General text message, contains human readable text
+	Text Type = 2
+
+	/*User Discovery message types*/
+	//Message structures defined in the UD package
+
+	// A search for users based on facts.  A series of hashed facts are passed
+	// to UDB
+	UdSearch = 10
+
+	// The response to the UD search. It contains a list of contact objects
+	// matching the sent facts
+	UdSearchResponse = 11
+
+	// Searched for the DH public key associated with the passed User ID
+	UdLookup = 12
+
+	// Response to UdLookup, it contains the associated public key if one is
+	// available
+	UdLookupResponse = 13
+
+	/*End to End Rekey message types*/
+	// Trigger a rekey, this message is used locally in client only
+	KeyExchangeTrigger = 30
+	// Rekey confirmation message. Sent by partner to confirm completion of a rekey
+	KeyExchangeConfirm = 31
+)
diff --git a/interfaces/networkManager.go b/interfaces/networkManager.go
new file mode 100644
index 0000000000000000000000000000000000000000..da548c855e48a30a5fff0895842f04975e00c88b
--- /dev/null
+++ b/interfaces/networkManager.go
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package interfaces
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+type NetworkManager interface {
+	SendE2E(m message.Send, p params.E2E) ([]id.Round, e2e.MessageID, error)
+	SendUnsafe(m message.Send, p params.Unsafe) ([]id.Round, error)
+	SendCMIX(message format.Message, recipient *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error)
+	GetInstance() *network.Instance
+	GetHealthTracker() HealthTracker
+	Follow(report ClientErrorReport) (stoppable.Stoppable, error)
+	CheckGarbledMessages()
+	InProgressRegistrations() int
+}
+
+//for use in key exchange which needs to be callable inside of network
+type SendE2E func(m message.Send, p params.E2E) ([]id.Round, e2e.MessageID, error)
diff --git a/interfaces/params/CMIX.go b/interfaces/params/CMIX.go
new file mode 100644
index 0000000000000000000000000000000000000000..e4142eb5e509dd8ffce8cfd6b3e7a8d33d4fdc46
--- /dev/null
+++ b/interfaces/params/CMIX.go
@@ -0,0 +1,44 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"encoding/json"
+	"time"
+)
+
+type CMIX struct {
+	//maximum number of rounds to try and send on
+	RoundTries uint
+	Timeout    time.Duration
+	RetryDelay time.Duration
+}
+
+func GetDefaultCMIX() CMIX {
+	return CMIX{
+		RoundTries: 10,
+		Timeout:    25 * time.Second,
+		RetryDelay: 1 * time.Second,
+	}
+}
+
+func (c CMIX) Marshal() ([]byte, error) {
+	return json.Marshal(c)
+}
+
+// Obtain default CMIX parameters, or override with given parameters if set
+func GetCMIXParameters(params string) (CMIX, error) {
+	p := GetDefaultCMIX()
+	if len(params) > 0 {
+		err := json.Unmarshal([]byte(params), &p)
+		if err != nil {
+			return CMIX{}, err
+		}
+	}
+	return p, nil
+}
diff --git a/interfaces/params/CMIX_test.go b/interfaces/params/CMIX_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..06d1968bbbd46aeef40e0a5c343c9e863493b5ae
--- /dev/null
+++ b/interfaces/params/CMIX_test.go
@@ -0,0 +1,55 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"testing"
+	"time"
+)
+
+func TestGetDefaultCMIX(t *testing.T) {
+	c := GetDefaultCMIX()
+	if c.RoundTries != 10 || c.Timeout != 25*time.Second {
+		t.Errorf("GetDefaultCMIX did not return expected values")
+	}
+}
+
+// New params path
+func TestGetCMIXParameters(t *testing.T) {
+	p := GetDefaultCMIX()
+
+	expected := p.RoundTries + 1
+	p.RoundTries = expected
+	jsonString, err := p.Marshal()
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	q, err := GetCMIXParameters(string(jsonString))
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.RoundTries != expected {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.RoundTries, expected)
+	}
+}
+
+// No new params path
+func TestGetCMIXParameters_Default(t *testing.T) {
+	p := GetDefaultCMIX()
+
+	q, err := GetCMIXParameters("")
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.RoundTries != p.RoundTries {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.RoundTries, p.RoundTries)
+	}
+}
diff --git a/interfaces/params/E2E.go b/interfaces/params/E2E.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e0aa95f80803aa138849101d6ae06b4559c20e8
--- /dev/null
+++ b/interfaces/params/E2E.go
@@ -0,0 +1,101 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"encoding/json"
+	"fmt"
+	"gitlab.com/elixxir/crypto/e2e"
+)
+
+type E2E struct {
+	Type       SendType
+	RetryCount int
+	CMIX
+}
+
+func GetDefaultE2E() E2E {
+	return E2E{
+		Type:       Standard,
+		CMIX:       GetDefaultCMIX(),
+		RetryCount: 10,
+	}
+}
+func (e E2E) Marshal() ([]byte, error) {
+	return json.Marshal(e)
+}
+
+// Obtain default E2E parameters, or override with given parameters if set
+func GetE2EParameters(params string) (E2E, error) {
+	p := GetDefaultE2E()
+	if len(params) > 0 {
+		err := json.Unmarshal([]byte(params), &p)
+		if err != nil {
+			return E2E{}, err
+		}
+	}
+	return p, nil
+}
+
+type SendType uint8
+
+const (
+	Standard    SendType = 0
+	KeyExchange SendType = 1
+)
+
+func (st SendType) String() string {
+	switch st {
+	case Standard:
+		return "Standard"
+	case KeyExchange:
+		return "KeyExchange"
+	default:
+		return fmt.Sprintf("Unknown SendType %v", uint8(st))
+	}
+}
+
+// Network E2E Params
+
+// DEFAULT KEY GENERATION PARAMETERS
+// Hardcoded limits for keys
+// With 16 receiving states we can hold
+// 16*64=1024 dirty bits for receiving keys
+// With that limit, and setting maxKeys to 800,
+// we need a Threshold of 224, and a scalar
+// smaller than 1.28 to ensure we never generate
+// more than 1024 keys
+// With 1 receiving states for ReKeys we can hold
+// 64 Rekeys
+const (
+	minKeys   uint16  = 500
+	maxKeys   uint16  = 800
+	ttlScalar float64 = 1.2 // generate 20% extra keys
+	threshold uint16  = 224
+	numReKeys uint16  = 16
+)
+
+type E2ESessionParams struct {
+	MinKeys   uint16
+	MaxKeys   uint16
+	NumRekeys uint16
+	e2e.TTLParams
+}
+
+func GetDefaultE2ESessionParams() E2ESessionParams {
+	return E2ESessionParams{
+		MinKeys:   minKeys,
+		MaxKeys:   maxKeys,
+		NumRekeys: numReKeys,
+	}
+}
+
+func (p E2ESessionParams) String() string {
+	return fmt.Sprintf("Params{ MinKeys: %d, MaxKeys: %d, NumRekeys: %d }",
+		p.MinKeys, p.MaxKeys, p.NumRekeys)
+}
diff --git a/interfaces/params/E2E_test.go b/interfaces/params/E2E_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1f4b599bdec30b5ed2461d7e6bdee3c172a279d5
--- /dev/null
+++ b/interfaces/params/E2E_test.go
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import "testing"
+
+func TestGetDefaultE2E(t *testing.T) {
+	if GetDefaultE2E().Type != Standard {
+		t.Errorf("GetDefaultE2E did not return Standard")
+	}
+}
+
+func TestSendType_String(t *testing.T) {
+	e := E2E{Type: Standard}
+	if e.Type.String() != "Standard" {
+		t.Errorf("Running String on Standard E2E type got %s", e.Type.String())
+	}
+
+	e = E2E{Type: KeyExchange}
+	if e.Type.String() != "KeyExchange" {
+		t.Errorf("Running String on KeyExchange E2E type got %s", e.Type.String())
+	}
+
+	e = E2E{Type: SendType(40)}
+	if e.Type.String() != "Unknown SendType 40" {
+		t.Errorf("Running String on unknown E2E type got %s", e.Type.String())
+	}
+}
+
+// New params path
+func TestGetE2EParameters(t *testing.T) {
+	p := GetDefaultE2E()
+
+	expected := p.RoundTries + 1
+	p.RoundTries = expected
+	jsonString, err := p.Marshal()
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	q, err := GetE2EParameters(string(jsonString))
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.RoundTries != expected {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.RoundTries, expected)
+	}
+}
+
+// No new params path
+func TestGetE2EParameters_Default(t *testing.T) {
+	p := GetDefaultE2E()
+
+	q, err := GetE2EParameters("")
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.RoundTries != p.RoundTries {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.RoundTries, p.RoundTries)
+	}
+}
+
+// Test that the GetDefaultParams function returns the right default data
+func Test_GetDefaultParams(t *testing.T) {
+	p := GetDefaultE2ESessionParams()
+	if p.MinKeys != minKeys {
+		t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.MinKeys, minKeys)
+	}
+	if p.MaxKeys != maxKeys {
+		t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.MaxKeys, maxKeys)
+	}
+	if p.NumRekeys != numReKeys {
+		t.Errorf("MinKeys mismatch\r\tGot: %d\r\tExpected: %d", p.NumRekeys, numReKeys)
+	}
+}
diff --git a/interfaces/params/keyExchange.go b/interfaces/params/keyExchange.go
new file mode 100644
index 0000000000000000000000000000000000000000..c30b49c6168f52fb7ad97098f123bb226486ee11
--- /dev/null
+++ b/interfaces/params/keyExchange.go
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"time"
+)
+
+type Rekey struct {
+	RoundTimeout time.Duration
+}
+
+func GetDefaultRekey() Rekey {
+	return Rekey{
+		RoundTimeout: time.Minute,
+	}
+}
diff --git a/interfaces/params/message.go b/interfaces/params/message.go
new file mode 100644
index 0000000000000000000000000000000000000000..fbf9779829b939145cf7bc1277fa79b5617b826a
--- /dev/null
+++ b/interfaces/params/message.go
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"time"
+)
+
+type Messages struct {
+	MessageReceptionBuffLen        uint
+	MessageReceptionWorkerPoolSize uint
+	MaxChecksGarbledMessage        uint
+	GarbledMessageWait             time.Duration
+}
+
+func GetDefaultMessage() Messages {
+	return Messages{
+		MessageReceptionBuffLen:        500,
+		MessageReceptionWorkerPoolSize: 4,
+		MaxChecksGarbledMessage:        10,
+		GarbledMessageWait:             15 * time.Minute,
+	}
+}
diff --git a/interfaces/params/network.go b/interfaces/params/network.go
new file mode 100644
index 0000000000000000000000000000000000000000..b73efc7a3ee56c5d2f24d18643194a22852dbc92
--- /dev/null
+++ b/interfaces/params/network.go
@@ -0,0 +1,62 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"encoding/json"
+	"time"
+)
+
+type Network struct {
+	TrackNetworkPeriod time.Duration
+	// maximum number of rounds to check in a single iterations network updates
+	MaxCheckedRounds uint
+	// Size of the buffer of nodes to register
+	RegNodesBufferLen uint
+	// Longest delay between network events for Health tracker to denote that
+	// the network is in a bad state
+	NetworkHealthTimeout time.Duration
+	//Number of parallel node registration the client is capable of
+	ParallelNodeRegistrations uint
+
+	Rounds
+	Messages
+	Rekey
+
+	E2EParams E2ESessionParams
+}
+
+func GetDefaultNetwork() Network {
+	n := Network{
+		TrackNetworkPeriod:   100 * time.Millisecond,
+		MaxCheckedRounds:     500,
+		RegNodesBufferLen:    500,
+		NetworkHealthTimeout: 30 * time.Second,
+		E2EParams:            GetDefaultE2ESessionParams(),
+		ParallelNodeRegistrations: 8,
+	}
+	n.Rounds = GetDefaultRounds()
+	n.Messages = GetDefaultMessage()
+	return n
+}
+
+func (n Network) Marshal() ([]byte, error) {
+	return json.Marshal(n)
+}
+
+// Obtain default Network parameters, or override with given parameters if set
+func GetNetworkParameters(params string) (Network, error) {
+	p := GetDefaultNetwork()
+	if len(params) > 0 {
+		err := json.Unmarshal([]byte(params), &p)
+		if err != nil {
+			return Network{}, err
+		}
+	}
+	return p, nil
+}
diff --git a/interfaces/params/network_test.go b/interfaces/params/network_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b8b609678f279048a3dcd6ec9b9dc634607e6f7
--- /dev/null
+++ b/interfaces/params/network_test.go
@@ -0,0 +1,44 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2021 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import "testing"
+
+// New params path
+func TestGetNetworkParameters(t *testing.T) {
+	p := GetDefaultNetwork()
+
+	expected := p.MaxCheckedRounds + 1
+	p.MaxCheckedRounds = expected
+	jsonString, err := p.Marshal()
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	q, err := GetNetworkParameters(string(jsonString))
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.MaxCheckedRounds != expected {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.MaxCheckedRounds, expected)
+	}
+}
+
+// No new params path
+func TestGetNetworkParameters_Default(t *testing.T) {
+	p := GetDefaultNetwork()
+
+	q, err := GetNetworkParameters("")
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.MaxCheckedRounds != p.MaxCheckedRounds {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.MaxCheckedRounds, p.MaxCheckedRounds)
+	}
+}
diff --git a/interfaces/params/rounds.go b/interfaces/params/rounds.go
new file mode 100644
index 0000000000000000000000000000000000000000..dbf0d5a00fdbb07ad813244f75f84eda34ddcd57
--- /dev/null
+++ b/interfaces/params/rounds.go
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import (
+	"time"
+)
+
+type Rounds struct {
+	// Maximum number of times to attempt to retrieve a round from a gateway
+	// before giving up on it
+	MaxAttemptsCheckingARound uint
+	// Number of historical rounds required to automatically send a historical
+	// rounds query
+	MaxHistoricalRounds uint
+	// Maximum period of time a pending historical round query will wait before
+	// it is transmitted
+	HistoricalRoundsPeriod time.Duration
+	// Number of worker threads for retrieving messages from gateways
+	NumMessageRetrievalWorkers uint
+
+	// Length of historical rounds channel buffer
+	HistoricalRoundsBufferLen uint
+	// Length of round lookup channel buffer
+	LookupRoundsBufferLen uint
+
+	// Toggles if historical rounds should always be used
+	ForceHistoricalRounds bool
+}
+
+func GetDefaultRounds() Rounds {
+	return Rounds{
+		MaxAttemptsCheckingARound:  5,
+		MaxHistoricalRounds:        100,
+		HistoricalRoundsPeriod:     100 * time.Millisecond,
+		NumMessageRetrievalWorkers: 8,
+
+		HistoricalRoundsBufferLen: 1000,
+		LookupRoundsBufferLen:     2000,
+		ForceHistoricalRounds:     false,
+	}
+}
diff --git a/interfaces/params/unsafe.go b/interfaces/params/unsafe.go
new file mode 100644
index 0000000000000000000000000000000000000000..556559a88a253107ab803521a550c403e5040113
--- /dev/null
+++ b/interfaces/params/unsafe.go
@@ -0,0 +1,34 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import "encoding/json"
+
+type Unsafe struct {
+	CMIX
+}
+
+func GetDefaultUnsafe() Unsafe {
+	return Unsafe{CMIX: GetDefaultCMIX()}
+}
+
+func (u Unsafe) Marshal() ([]byte, error) {
+	return json.Marshal(u)
+}
+
+// Obtain default Unsafe parameters, or override with given parameters if set
+func GetUnsafeParameters(params string) (Unsafe, error) {
+	p := GetDefaultUnsafe()
+	if len(params) > 0 {
+		err := json.Unmarshal([]byte(params), &p)
+		if err != nil {
+			return Unsafe{}, err
+		}
+	}
+	return p, nil
+}
diff --git a/interfaces/params/unsafe_test.go b/interfaces/params/unsafe_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..49763fdf11e978201d18f29dacdc5f68bd0c2ec7
--- /dev/null
+++ b/interfaces/params/unsafe_test.go
@@ -0,0 +1,44 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2021 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+package params
+
+import "testing"
+
+// New params path
+func TestGetUnsafeParameters(t *testing.T) {
+	p := GetDefaultUnsafe()
+
+	expected := p.RoundTries + 1
+	p.RoundTries = expected
+	jsonString, err := p.Marshal()
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	q, err := GetUnsafeParameters(string(jsonString))
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.RoundTries != expected {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.RoundTries, expected)
+	}
+}
+
+// No new params path
+func TestGetUnsafeParameters_Default(t *testing.T) {
+	p := GetDefaultUnsafe()
+
+	q, err := GetUnsafeParameters("")
+	if err != nil {
+		t.Errorf("%+v", err)
+	}
+
+	if q.RoundTries != p.RoundTries {
+		t.Errorf("Parameters failed to change! Got %d, Expected %d", q.RoundTries, p.RoundTries)
+	}
+}
diff --git a/interfaces/roundEvents.go b/interfaces/roundEvents.go
new file mode 100644
index 0000000000000000000000000000000000000000..7228649def38a6199a7650df1b811373ad7f0307
--- /dev/null
+++ b/interfaces/roundEvents.go
@@ -0,0 +1,39 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package interfaces
+
+import (
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// The round events interface allows the registration of an event which triggers
+// when a round reaches one or more states
+
+type RoundEvents interface {
+	// designates a callback to call on the specified event
+	// rid is the id of the round the event occurs on
+	// callback is the callback the event is triggered on
+	// timeout is the amount of time before an error event is returned
+	// valid states are the states which the event should trigger on
+	AddRoundEvent(rid id.Round, callback ds.RoundEventCallback,
+		timeout time.Duration, validStates ...states.Round) *ds.EventCallback
+
+	// designates a go channel to signal the specified event
+	// rid is the id of the round the event occurs on
+	// eventChan is the channel the event is triggered on
+	// timeout is the amount of time before an error event is returned
+	// valid states are the states which the event should trigger on
+	AddRoundEventChan(rid id.Round, eventChan chan ds.EventReturn,
+		timeout time.Duration, validStates ...states.Round) *ds.EventCallback
+
+	//Allows the un-registration of a round event before it triggers
+	Remove(rid id.Round, e *ds.EventCallback)
+}
diff --git a/interfaces/switchboard.go b/interfaces/switchboard.go
new file mode 100644
index 0000000000000000000000000000000000000000..a90cee09f23190e385bc8dca63518e121c4d3d66
--- /dev/null
+++ b/interfaces/switchboard.go
@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package interfaces
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// public switchboard interface which only allows registration and does not
+// allow speaking messages
+type Switchboard interface {
+	// Registers a new listener. Returns the ID of the new listener.
+	// Keep this around if you want to be able to delete the listener later.
+	//
+	// name is used for debug printing and not checked for uniqueness
+	//
+	// user: 0 for all, or any user ID to listen for messages from a particular
+	// user. 0 can be id.ZeroUser or id.ZeroID
+	// messageType: 0 for all, or any message type to listen for messages of
+	// that type. 0 can be switchboard.AnyType
+	// newListener: something implementing the Listener interface. Do not
+	// pass nil to this.
+	//
+	// If a message matches multiple listeners, all of them will hear the
+	// message.
+	RegisterListener(user *id.ID, messageType message.Type,
+		newListener switchboard.Listener) switchboard.ListenerID
+
+	// Registers a new listener built around the passed function.
+	// Returns the ID of the new listener.
+	// Keep this around if you want to be able to delete the listener later.
+	//
+	// name is used for debug printing and not checked for uniqueness
+	//
+	// user: 0 for all, or any user ID to listen for messages from a particular
+	// user. 0 can be id.ZeroUser or id.ZeroID
+	// messageType: 0 for all, or any message type to listen for messages of
+	// that type. 0 can be switchboard.AnyType
+	// newListener: a function implementing the ListenerFunc function type.
+	// Do not pass nil to this.
+	//
+	// If a message matches multiple listeners, all of them will hear the
+	// message.
+	RegisterFunc(name string, user *id.ID, messageType message.Type,
+		newListener switchboard.ListenerFunc) switchboard.ListenerID
+
+	// Registers a new listener built around the passed channel.
+	// Returns the ID of the new listener.
+	// Keep this around if you want to be able to delete the listener later.
+	//
+	// name is used for debug printing and not checked for uniqueness
+	//
+	// user: 0 for all, or any user ID to listen for messages from a particular
+	// user. 0 can be id.ZeroUser or id.ZeroID
+	// messageType: 0 for all, or any message type to listen for messages of
+	// that type. 0 can be switchboard.AnyType
+	// newListener: an item channel.
+	// Do not pass nil to this.
+	//
+	// If a message matches multiple listeners, all of them will hear the
+	// message.
+	RegisterChannel(name string, user *id.ID, messageType message.Type,
+		newListener chan message.Receive) switchboard.ListenerID
+
+	// Unregister removes the listener with the specified ID so it will no
+	// longer get called
+	Unregister(listenerID switchboard.ListenerID)
+}
diff --git a/interfaces/user/user.go b/interfaces/user/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..1aaea8a31d67d0dd1facb50cbb7170a0055abf92
--- /dev/null
+++ b/interfaces/user/user.go
@@ -0,0 +1,43 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type User struct {
+	//General Identity
+	TransmissionID   *id.ID
+	TransmissionSalt []byte
+	TransmissionRSA  *rsa.PrivateKey
+	ReceptionID      *id.ID
+	ReceptionSalt    []byte
+	ReceptionRSA     *rsa.PrivateKey
+	Precanned        bool
+
+	//cmix Identity
+	CmixDhPrivateKey *cyclic.Int
+	CmixDhPublicKey  *cyclic.Int
+
+	//e2e Identity
+	E2eDhPrivateKey *cyclic.Int
+	E2eDhPublicKey  *cyclic.Int
+}
+
+func (u User) GetContact() contact.Contact {
+	return contact.Contact{
+		ID:       u.ReceptionID.DeepCopy(),
+		DhPubKey: u.E2eDhPublicKey,
+		Facts:    make([]fact.Fact, 0),
+	}
+}
diff --git a/interfaces/utility/trackResults.go b/interfaces/utility/trackResults.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd766624d475141edbad94731f3a3da092e4bcfd
--- /dev/null
+++ b/interfaces/utility/trackResults.go
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/states"
+)
+
+// Function to follow the results of events. It returns true if the collection of
+// events resolved well, and then a count of how many rounds failed and how
+// many roundEvents timed out.
+func TrackResults(resultsCh chan ds.EventReturn, numResults int) (bool, int, int) {
+	numTimeOut, numRoundFail := 0, 0
+	for numResponses := 0; numResponses < numResults; numResponses++ {
+		er := <-resultsCh
+		if er.TimedOut {
+			numTimeOut++
+		} else if states.Round(er.RoundInfo.State) == states.FAILED {
+			numRoundFail++
+		}
+	}
+
+	return (numTimeOut + numRoundFail) == 0, numRoundFail, numTimeOut
+}
diff --git a/io/collate.go b/io/collate.go
deleted file mode 100644
index c2d74635ed915576f807767f4f17d065e5b928aa..0000000000000000000000000000000000000000
--- a/io/collate.go
+++ /dev/null
@@ -1,179 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package io
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"sync"
-	"time"
-)
-
-type multiPartMessage struct {
-	parts            [][]byte
-	numPartsReceived uint8
-}
-
-const PendingMessageKeyLenBits = uint64(256)
-const PendingMessageKeyLen = PendingMessageKeyLenBits / 8
-
-type PendingMessageKey [PendingMessageKeyLen]byte
-
-type Collator struct {
-	pendingMessages map[PendingMessageKey]*multiPartMessage
-	// TODO do we need a lock here? or can we assume that requests will come
-	// from only one thread?
-	mux sync.Mutex
-}
-
-func NewCollator() *Collator {
-	return &Collator{
-		pendingMessages: make(map[PendingMessageKey]*multiPartMessage),
-	}
-}
-
-// AddMessage validates its input and silently does nothing on failure
-// TODO should this return an error?
-// TODO this should take a different type as parameter.
-// TODO this takes too many types. i should split it up.
-// This method returns a byte slice with the assembled message if it's
-// received a completed message.
-func (mb *Collator) AddMessage(message *format.Message, sender *id.ID,
-	timeout time.Duration) (*parse.Message, error) {
-
-	payload := message.Contents.GetRightAligned()
-	recipient, err := message.GetRecipient()
-	if err != nil {
-		return nil, err
-	}
-
-	//get the time
-	timestamp := time.Time{}
-
-	err = timestamp.UnmarshalBinary(message.GetTimestamp()[:len(message.GetTimestamp())-1])
-
-	if err != nil {
-		globals.Log.WARN.Printf("Failed to parse timestamp for message %v: %+v",
-			message.GetTimestamp(), errors.New(err.Error()))
-	}
-
-	partition, err := parse.ValidatePartition(payload)
-
-	if err != nil {
-		return nil, errors.WithMessage(err, "Received an invalid partition: ")
-	} else {
-		if partition.MaxIndex == 0 {
-			//this is the only part of the message. we should take the fast
-			//path and skip putting it in the map
-			typedBody, err := parse.Parse(partition.Body)
-			// Log an error if the message is malformed and return nothing
-			if err != nil {
-				return nil, errors.WithMessage(err, "Malformed message received")
-			}
-
-			msg := parse.Message{
-				TypedBody:    *typedBody,
-				InferredType: parse.Unencrypted,
-				Sender:       sender,
-				Receiver:     recipient,
-				Timestamp:    timestamp,
-			}
-
-			return &msg, nil
-		} else {
-			// assemble the map key into a new chunk of memory
-			var key PendingMessageKey
-			h := sha256.New()
-			h.Write(partition.ID)
-			h.Write(sender.Bytes())
-			keyHash := h.Sum(nil)
-			copy(key[:], keyHash[:PendingMessageKeyLen])
-
-			mb.mux.Lock()
-			defer mb.mux.Unlock()
-			message, ok := mb.pendingMessages[key]
-			if !ok {
-				// this is a multi-part message we haven't seen before.
-				// make a new array of partitions for this key
-				newMessage := make([][]byte, partition.MaxIndex+1)
-				newMessage[partition.Index] = partition.Body
-
-				message = &multiPartMessage{
-					parts:            newMessage,
-					numPartsReceived: 1,
-				}
-
-				mb.pendingMessages[key] = message
-
-				// start timeout for these partitions
-				// TODO vary timeout depending on number of messages?
-				time.AfterFunc(timeout, func() {
-					mb.mux.Lock()
-					defer mb.mux.Unlock()
-					_, ok := mb.pendingMessages[key]
-					if ok {
-						delete(mb.pendingMessages, key)
-					}
-				})
-			} else {
-				// append to array for this key
-				message.numPartsReceived++
-				message.parts[partition.Index] = partition.Body
-			}
-			if message.numPartsReceived > partition.MaxIndex {
-				// Construct message
-				fullMsg, err := parse.Assemble(message.parts)
-				if err != nil {
-					delete(mb.pendingMessages, key)
-					return nil, errors.WithMessage(err, "Malformed message: padding error, ")
-				}
-				typedBody, err := parse.Parse(fullMsg)
-				// Log an error if the message is malformed and return nothing
-				if err != nil {
-					delete(mb.pendingMessages, key)
-					return nil, errors.WithMessage(err, "Malformed message received")
-				}
-
-				msg := parse.Message{
-					TypedBody:    *typedBody,
-					InferredType: parse.Unencrypted,
-					Sender:       sender,
-					Receiver:     recipient,
-					Timestamp:    timestamp,
-				}
-
-				delete(mb.pendingMessages, key)
-				return &msg, nil
-			} else {
-				// need more parts
-				return nil, nil
-			}
-		}
-	}
-}
-
-// Debug: dump all messages that are currently in the map
-func (mb *Collator) dump() string {
-	dump := ""
-	mb.mux.Lock()
-	for key := range mb.pendingMessages {
-		if mb.pendingMessages[key].parts != nil {
-			for i, part := range mb.pendingMessages[key].parts {
-				dump += fmt.Sprintf("Part %v: %v\n", i, part)
-			}
-			dump += fmt.Sprintf("Total parts received: %v\n",
-				mb.pendingMessages[key].numPartsReceived)
-		}
-	}
-	mb.mux.Unlock()
-	return dump
-}
diff --git a/io/collate_test.go b/io/collate_test.go
deleted file mode 100644
index 274497f2519a478511de3c8f24ceffd1d2bb5195..0000000000000000000000000000000000000000
--- a/io/collate_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package io
-
-import (
-	"bytes"
-	"encoding/hex"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"math/rand"
-	"testing"
-	"time"
-)
-
-func TestCollator_AddMessage(t *testing.T) {
-
-	uid := id.NewIdFromUInt(69, id.User, t)
-
-	collator := &Collator{
-		pendingMessages: make(map[PendingMessageKey]*multiPartMessage),
-	}
-	var bodies [][]byte
-	for length := 5; length < 20*format.TotalLen; length += 20 {
-		newBody := make([]byte, length)
-		_, err := rand.Read(newBody)
-		if err != nil {
-			t.Errorf("Couldn't generate enough random bytes: %v", err.Error())
-		}
-
-		bodies = append(bodies, newBody)
-	}
-	for i := range bodies {
-		partitions, err := parse.Partition([]byte(bodies[i]), []byte{5})
-		if err != nil {
-			t.Errorf("Error partitioning messages: %v", err.Error())
-		}
-
-		var result *parse.Message
-		for j := range partitions {
-
-			fm := format.NewMessage()
-			fm.SetRecipient(id.NewIdFromUInt(6, id.User, t))
-			fm.Contents.SetRightAligned(partitions[j])
-
-			result, err = collator.AddMessage(fm, uid, time.Minute)
-			if err != nil {
-				t.Fatal(err)
-			}
-		}
-
-		typedBody, err := parse.Parse(bodies[i])
-
-		// This always fails because of the trailing zeroes. Question is, how
-		// much does it matter in regular usage? Protobufs know their length
-		// already, and strings should respect null terminators,
-		// so it's probably not actually that much of a problem.
-		if !bytes.Contains(result.Body, typedBody.Body) {
-			t.Errorf("Input didn't match output for %v. \n  Got: %v\n  Expected %v",
-				i, hex.EncodeToString(result.Body),
-				hex.EncodeToString(typedBody.Body))
-		}
-	}
-}
-
-func TestCollator_AddMessage_Timeout(t *testing.T) {
-
-	uid := id.NewIdFromUInt(69, id.User, t)
-
-	collator := &Collator{
-		pendingMessages: make(map[PendingMessageKey]*multiPartMessage),
-	}
-	//enough for four partitions, probably
-	body := make([]byte, 3*format.ContentsLen)
-	partitions, err := parse.Partition(body, []byte{88})
-	if err != nil {
-		t.Errorf("Error partitioning messages: %v", err.Error())
-	}
-	var result *parse.Message
-	for i := range partitions {
-		fm := format.NewMessage()
-		now := time.Now()
-		nowBytes, _ := now.MarshalBinary()
-		nowBytes = append(nowBytes, make([]byte, format.TimestampLen-len(nowBytes))...)
-		fm.SetTimestamp(nowBytes)
-		fm.SetRecipient(id.NewIdFromUInt(6, id.User, t))
-		fm.Contents.SetRightAligned(partitions[i])
-
-		result, err = collator.AddMessage(fm, uid, 80*time.Millisecond)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if result != nil {
-			t.Error("Got a result from collator when it should be timing out" +
-				" submessages")
-		}
-		time.Sleep(50 * time.Millisecond)
-	}
-
-	time.Sleep(80 * time.Millisecond)
-	if len(collator.pendingMessages) != 0 {
-		t.Error("Multi-part message didn't get timed out properly")
-	}
-}
diff --git a/io/interface.go b/io/interface.go
deleted file mode 100644
index 3352147ee359249751df963abe00487a6fd579e1..0000000000000000000000000000000000000000
--- a/io/interface.go
+++ /dev/null
@@ -1,33 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package io
-
-import (
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"time"
-)
-
-// Communication interface implements send/receive functionality with the server
-type Communications interface {
-	// SendMessage to the server
-
-	// TODO(nen) Can we get rid of the crypto type param here?
-	SendMessage(session user.Session, topology *connect.Circuit,
-		recipientID *id.ID, cryptoType parse.CryptoType, message []byte,
-		transmissionHost *connect.Host) error
-	// SendMessage without partitions to the server
-	// This is used to send rekey messages
-	SendMessageNoPartition(session user.Session, topology *connect.Circuit,
-		recipientID *id.ID, cryptoType parse.CryptoType, message []byte,
-		transmissionHost *connect.Host) error
-	// MessageReceiver thread to get new messages
-	MessageReceiver(session user.Session, delay time.Duration,
-		receptionHost *connect.Host, callback func(error))
-}
diff --git a/io/receive.go b/io/receive.go
deleted file mode 100644
index ef5c5dbcba7c66037efa0d67935e762916c3cd07..0000000000000000000000000000000000000000
--- a/io/receive.go
+++ /dev/null
@@ -1,336 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package io
-
-import (
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/crypto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"gitlab.com/xx_network/comms/connect"
-	"strings"
-	"time"
-)
-
-const reportDuration = 30 * time.Second
-
-var errE2ENotFound = errors.New("E2EKey for matching fingerprint not found, can't process message")
-
-// MessageReceiver is a polling thread for receiving messages
-func (rm *ReceptionManager) MessageReceiver(session user.Session, delay time.Duration,
-	receptionHost *connect.Host, callback func(error)) {
-	// FIXME: It's not clear we should be doing decryption here.
-	if session == nil {
-		globals.Log.FATAL.Panicf("No user session available")
-	}
-	pollingMessage := pb.ClientRequest{
-		UserID: session.GetCurrentUser().User.Bytes(),
-	}
-	quit := session.GetQuitChan()
-
-	NumChecks := 0
-	NumMessages := 0
-
-	reportTicker := time.NewTicker(reportDuration)
-
-	var encryptedMessages []*format.Message
-
-	globals.Log.DEBUG.Printf("Gateway Polling for Message Reception Begun")
-	receptionTicker := time.NewTicker(delay)
-
-	for {
-
-		NumChecks++
-		select {
-		case <-quit:
-			globals.Log.DEBUG.Printf("Stopped message receiver\n")
-			return
-		case <-receptionTicker.C:
-
-			//check if a report on the polling status is due, report to logs if
-			//it is
-			select {
-			case <-reportTicker.C:
-				globals.Log.DEBUG.Printf("Over the passed %v "+
-					"gateway has been checked %v time and %v messages recieved",
-					reportDuration, NumChecks, NumMessages)
-			default:
-			}
-
-			NumChecks++
-
-			var err error
-			encryptedMessages, err = rm.receiveMessagesFromGateway(session, &pollingMessage, receptionHost)
-
-			if err != nil {
-
-				if strings.Contains(err.Error(), "Client has exceeded communications rate limit") {
-					globals.Log.WARN.Printf("Rate limit excceded on gateway, pausing polling for 5 seconds")
-					time.Sleep(5 * time.Second)
-				} else if !strings.Contains(err.Error(), "Could not find any message IDs for this user") {
-					go callback(err)
-					return
-				}
-			}
-			NumMessages += len(encryptedMessages)
-		case <-rm.rekeyChan:
-			encryptedMessages = session.PopGarbledMessages()
-		}
-
-		if len(encryptedMessages) != 0 {
-
-			decryptedMessages, senders, garbledMessages := rm.decryptMessages(session, encryptedMessages)
-
-			if len(garbledMessages) != 0 {
-				session.AppendGarbledMessage(garbledMessages...)
-			}
-
-			if decryptedMessages != nil {
-				for i := range decryptedMessages {
-					// TODO Handle messages that do not need partitioning
-					assembledMessage, err := rm.collator.AddMessage(decryptedMessages[i],
-						senders[i], time.Minute)
-					if err != nil {
-						go callback(err)
-					}
-					if assembledMessage != nil {
-						// we got a fully assembled message. let's broadcast it
-						broadcastMessageReception(assembledMessage, session.GetSwitchboard())
-					}
-				}
-			}
-		}
-	}
-}
-
-func handleE2EReceiving(session user.Session,
-	message *format.Message) (*id.ID, bool, error) {
-	keyFingerprint := message.GetKeyFP()
-
-	// Lookup reception key
-	recpKey := session.GetKeyStore().
-		GetRecvKey(keyFingerprint)
-
-	rekey := false
-	if recpKey == nil {
-		// TODO Handle sending error message to SW
-		return nil, false, fmt.Errorf("E2EKey for matching fingerprint not found, can't process message")
-	} else if recpKey.GetOuterType() == parse.Rekey {
-		// If key type is rekey, the message is a rekey from partner
-		rekey = true
-	}
-
-	sender := recpKey.GetManager().GetPartner()
-
-	globals.Log.DEBUG.Printf("E2E decrypting message")
-	var err error
-	if rekey {
-		err = crypto.E2EDecryptUnsafe(session.GetE2EGroup(), recpKey.GetKey(), message)
-	} else {
-		err = crypto.E2EDecrypt(session.GetE2EGroup(), recpKey.GetKey(), message)
-	}
-
-	if err != nil {
-		// TODO handle Garbled message to SW
-	}
-
-	// Get partner from Key Manager of receiving key
-	// since there is no space in message for senderID
-	// Get decrypted partner public key from message
-	// Send rekey message to switchboard
-	if rekey {
-		partner := recpKey.GetManager().GetPartner()
-		partnerPubKey := message.Contents.Get()
-		rekeyMsg := &parse.Message{
-			Sender: partner,
-			TypedBody: parse.TypedBody{
-				MessageType: int32(cmixproto.Type_NO_TYPE),
-				Body:        partnerPubKey,
-			},
-			InferredType: parse.Rekey,
-			Receiver:     session.GetCurrentUser().User,
-		}
-		go session.GetSwitchboard().Speak(rekeyMsg)
-	}
-	return sender, rekey, err
-}
-
-func (rm *ReceptionManager) receiveMessagesFromGateway(session user.Session,
-	pollingMessage *pb.ClientRequest, receiveGateway *connect.Host) ([]*format.Message, error) {
-	// Get the last message ID received
-	pollingMessage.LastMessageID = session.GetLastMessageID()
-	// FIXME: dont do this over an over
-
-	// Gets a list of mssages that are newer than the last one recieved
-	messageIDs, err := rm.Comms.SendCheckMessages(receiveGateway, pollingMessage)
-
-	if err != nil {
-		return nil, err
-	}
-
-	if len(messageIDs.IDs) < 0 {
-		globals.Log.DEBUG.Printf("Checking novelty of %v messageIDs", len(messageIDs.IDs))
-	}
-
-	messages := make([]*format.Message, len(messageIDs.IDs))
-	mIDs := make([]string, len(messageIDs.IDs))
-
-	// fixme: this could miss messages if the client has not seen them but
-	// the gateway say them before a message the client has seen
-
-	// Loops through every new message and retrieves it
-	bufLoc := 0
-	for _, messageID := range messageIDs.IDs {
-		// Get the first unseen message from the list of IDs
-		rm.recievedMesageLock.RLock()
-		_, received := rm.receivedMessages[messageID]
-		rm.recievedMesageLock.RUnlock()
-		if !received {
-			globals.Log.INFO.Printf("Got a message waiting on the gateway: %v",
-				messageID)
-			// We haven't seen this message before.
-			// So, we should retrieve it from the gateway.
-			newMessage, err := rm.Comms.SendGetMessage(receiveGateway,
-				&pb.ClientRequest{
-					UserID: session.GetCurrentUser().User.
-						Bytes(),
-					LastMessageID: messageID,
-				})
-			if err != nil {
-				globals.Log.WARN.Printf(
-					"Couldn't receive message with ID %v while"+
-						" polling gateway", messageID)
-			} else {
-				if newMessage.PayloadA == nil ||
-					newMessage.PayloadB == nil {
-					globals.Log.INFO.Println("Message fields not populated")
-					continue
-				}
-
-				msg := format.NewMessage()
-				msg.SetPayloadA(newMessage.PayloadA)
-				msg.SetDecryptedPayloadB(newMessage.PayloadB)
-
-				globals.Log.WARN.Printf(
-					"Loc: %d, %v", bufLoc, messageID)
-				messages[bufLoc] = msg
-				mIDs[bufLoc] = messageID
-				bufLoc++
-			}
-		}
-	}
-	// record that the messages were recieved so they are not re-retrieved
-	if bufLoc > 0 {
-		for i := 0; i < bufLoc; i++ {
-			globals.Log.INFO.Printf(
-				"Adding message ID %v to received message IDs", mIDs[i])
-			rm.recievedMesageLock.Lock()
-			rm.receivedMessages[mIDs[i]] = struct{}{}
-			rm.recievedMesageLock.Unlock()
-		}
-		session.SetLastMessageID(mIDs[bufLoc-1])
-		err = session.StoreSession()
-		if err != nil {
-			globals.Log.ERROR.Printf("Could not store session "+
-				"after messages received from gateway: %+v", err)
-		}
-	}
-
-	return messages[:bufLoc], nil
-}
-
-func (rm *ReceptionManager) decryptMessages(session user.Session,
-	encryptedMessages []*format.Message) ([]*format.Message, []*id.ID,
-	[]*format.Message) {
-
-	messages := make([]*format.Message, len(encryptedMessages))
-	senders := make([]*id.ID, len(encryptedMessages))
-	messagesSendersLoc := 0
-
-	garbledMessages := make([]*format.Message, len(encryptedMessages))
-	garbledMessagesLoc := 0
-
-	for _, msg := range encryptedMessages {
-		var err error = nil
-		var rekey bool
-		var unpadded []byte
-		var sender *id.ID
-		garbled := false
-
-		// If message is E2E, handle decryption
-		if e2e.IsUnencrypted(msg) {
-			// If message is non E2E, need to un-pad payload
-			unpadded, err = e2e.Unpad(msg.Contents.Get())
-			if err == nil {
-				msg.Contents.SetRightAligned(unpadded)
-			}
-
-			keyFP := msg.AssociatedData.GetKeyFP()
-			sender, err = makeUserID(keyFP[:])
-		} else {
-			sender, rekey, err = handleE2EReceiving(session, msg)
-
-			if err == errE2ENotFound {
-				garbled = true
-				err = nil
-			}
-		}
-
-		if err != nil {
-			globals.Log.WARN.Printf(
-				"Message did not decrypt properly, "+
-					"not adding to messages array: %v", err.Error())
-		} else if rekey {
-			globals.Log.INFO.Printf("Correctly processed rekey message," +
-				" not adding to messages array")
-		} else if garbled {
-			garbledMessages[garbledMessagesLoc] = msg
-			garbledMessagesLoc++
-		} else {
-			messages[messagesSendersLoc] = msg
-			senders[messagesSendersLoc] = sender
-			messagesSendersLoc++
-		}
-	}
-
-	return messages[:messagesSendersLoc], senders[:messagesSendersLoc], garbledMessages[:garbledMessagesLoc]
-}
-
-func broadcastMessageReception(message *parse.Message,
-	listeners *switchboard.Switchboard) {
-
-	listeners.Speak(message)
-}
-
-// Put a sender ID in a byte slice and set its type to user
-func makeUserID(senderID []byte) (*id.ID, error) {
-	senderIDBytes := make([]byte, id.ArrIDLen)
-	copy(senderIDBytes, senderID[:])
-	userID, err := id.Unmarshal(senderIDBytes)
-	if userID != nil {
-		userID.SetType(id.User)
-	}
-	return userID, err
-}
-
-// skipErrChecker checks checks if the error is fatal or should be ignored
-func skipErrChecker(err error) bool {
-	if strings.Contains(err.Error(), "Could not find any message IDs for this user") {
-		return true
-	}
-
-	return false
-
-}
diff --git a/io/receptionManager.go b/io/receptionManager.go
deleted file mode 100644
index 467ab0ebd069c22dcad1600d90e5e9967afd1205..0000000000000000000000000000000000000000
--- a/io/receptionManager.go
+++ /dev/null
@@ -1,94 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// Package io asynchronous sending functionality. This is managed by an outgoing
-// messages channel and managed by the sender thread kicked off during
-// initialization.
-package io
-
-import (
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/comms/client"
-	"gitlab.com/elixxir/primitives/id"
-	"sync"
-	"time"
-)
-
-type ConnAddr string
-
-func (a ConnAddr) String() string {
-	return string(a)
-}
-
-// ReceptionManager implements the Communications interface
-type ReceptionManager struct {
-	// Comms pointer to send/recv messages
-	Comms *client.Comms
-
-	nextId   func() []byte
-	collator *Collator
-
-	//Flags if the network is using tls or note
-	Tls bool
-	// blockTransmissions will use a mutex to prevent multiple threads from sending
-	// messages at the same time.
-	blockTransmissions bool // pass into receiver
-	// transmitDelay is the minimum delay between transmissions.
-	transmitDelay time.Duration // same
-	// Map that holds a record of the messages that this client successfully
-	// received during this session
-	receivedMessages   map[string]struct{}
-	recievedMesageLock sync.RWMutex
-
-	sendLock sync.Mutex
-
-	rekeyChan chan struct{}
-}
-
-// Build a new reception manager object using inputted key fields
-func NewReceptionManager(rekeyChan chan struct{}, uid *id.ID, privKey, pubKey, salt []byte) (*ReceptionManager, error) {
-	comms, err := client.NewClientComms(uid, pubKey, privKey, salt)
-	if err != nil {
-		return nil, errors.Wrap(err, "Failed to get client comms using constructor: %+v")
-	}
-
-	cm := &ReceptionManager{
-		nextId:             parse.IDCounter(),
-		collator:           NewCollator(),
-		blockTransmissions: true,
-		transmitDelay:      1000 * time.Millisecond,
-		receivedMessages:   make(map[string]struct{}),
-		Comms:              comms,
-		rekeyChan:          rekeyChan,
-		Tls:                true,
-	}
-
-	return cm, nil
-}
-
-// Connects to the permissioning server, if we know about it, to get the latest
-// version from it
-func (rm *ReceptionManager) GetRemoteVersion() (string, error) {
-	permissioningHost, ok := rm.Comms.GetHost(&id.Permissioning)
-	if !ok {
-		return "", errors.Errorf("Failed to find permissioning host with id %s", id.Permissioning)
-	}
-	registrationVersion, err := rm.Comms.
-		SendGetCurrentClientVersionMessage(permissioningHost)
-	if err != nil {
-		return "", errors.Wrap(err, "Couldn't get current version from permissioning")
-	}
-	return registrationVersion.Version, nil
-}
-
-func (rm *ReceptionManager) DisableBlockingTransmission() { // flag passed into receiver
-	rm.blockTransmissions = false
-}
-
-func (rm *ReceptionManager) SetRateLimit(delay time.Duration) { // pass into received
-	rm.transmitDelay = delay
-}
diff --git a/io/send.go b/io/send.go
deleted file mode 100644
index f4d5fc97507ce718c6bdf66704facc797746a143..0000000000000000000000000000000000000000
--- a/io/send.go
+++ /dev/null
@@ -1,230 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package io
-
-import (
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/crypto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/cmix"
-	"gitlab.com/elixxir/crypto/csprng"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"time"
-)
-
-// SendMessage to the provided Recipient
-// TODO: It's not clear why we wouldn't hand off a sender object (with
-// the keys) here. I won't touch crypto at this time, though...
-// TODO This method would be cleaner if it took a parse.Message (particularly
-// w.r.t. generating message IDs for multi-part messages.)
-func (rm *ReceptionManager) SendMessage(session user.Session, topology *connect.Circuit,
-	recipientID *id.ID, cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	// FIXME: We should really bring the plaintext parts of the NewMessage logic
-	// into this module, then have an EncryptedMessage type that is sent to/from
-	// the cMix network. This NewMessage does way too many things: break the
-	// message into parts, generate mic's, etc -- the crypto library should only
-	// know about the crypto and advertise a max message payload size
-
-	// TBD: Is there a really good reason why we'd ever have more than one user
-	// in this library? why not pass a sender object instead?
-	globals.Log.DEBUG.Printf("Sending message to %q: %q", *recipientID, message)
-	parts, err := parse.Partition([]byte(message),
-		rm.nextId())
-	if err != nil {
-		return fmt.Errorf("SendMessage Partition() error: %v", err.Error())
-	}
-	// Every part should have the same timestamp
-	now := time.Now()
-	// GO Timestamp binary serialization is 15 bytes, which
-	// allows the encrypted timestamp to fit in 16 bytes
-	// using AES encryption
-	// The timestamp will be encrypted later
-	// NOTE: This sets 15 bytes, not 16
-	nowBytes, err := now.MarshalBinary()
-	if err != nil {
-		return fmt.Errorf("SendMessage MarshalBinary() error: %v", err.Error())
-	}
-	// Add a byte for later encryption (15->16 bytes)
-	extendedNowBytes := append(nowBytes, 0)
-	for i := range parts {
-		message := format.NewMessage()
-		message.SetRecipient(recipientID)
-		message.SetTimestamp(extendedNowBytes)
-		message.Contents.SetRightAligned(parts[i])
-		err = rm.send(session, topology, cryptoType, message, false, transmissionHost)
-		if err != nil {
-			return errors.Wrap(err, "SendMessage send() error:")
-		}
-	}
-	return nil
-}
-
-// Send Message without doing partitions
-// This function will be needed for example to send a Rekey
-// message, where a new public key will take up the whole message
-func (rm *ReceptionManager) SendMessageNoPartition(session user.Session,
-	topology *connect.Circuit, recipientID *id.ID, cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	size := len(message)
-	if size > format.TotalLen {
-		return fmt.Errorf("SendMessageNoPartition() error: message to be sent is too big")
-	}
-	now := time.Now()
-	// GO Timestamp binary serialization is 15 bytes, which
-	// allows the encrypted timestamp to fit in 16 bytes
-	// using AES encryption
-	// The timestamp will be encrypted later
-	// NOTE: This sets 15 bytes, not 16
-	nowBytes, err := now.MarshalBinary()
-	if err != nil {
-		return fmt.Errorf("SendMessageNoPartition MarshalBinary() error: %v", err.Error())
-	}
-	msg := format.NewMessage()
-	msg.SetRecipient(recipientID)
-	// Add a byte to support later encryption (15 -> 16 bytes)
-	nowBytes = append(nowBytes, 0)
-	msg.SetTimestamp(nowBytes)
-	msg.Contents.Set(message)
-	globals.Log.DEBUG.Printf("Sending message to %v: %x", *recipientID, message)
-
-	err = rm.send(session, topology, cryptoType, msg, true, transmissionHost)
-	if err != nil {
-		return fmt.Errorf("SendMessageNoPartition send() error: %v", err.Error())
-	}
-	return nil
-}
-
-// send actually sends the message to the server
-func (rm *ReceptionManager) send(session user.Session, topology *connect.Circuit,
-	cryptoType parse.CryptoType,
-	message *format.Message,
-	rekey bool, transmitGateway *connect.Host) error {
-	// Enable transmission blocking if enabled
-	if rm.blockTransmissions {
-		rm.sendLock.Lock()
-		defer func() {
-			time.Sleep(rm.transmitDelay)
-			rm.sendLock.Unlock()
-		}()
-	}
-
-	// Check message type
-	if cryptoType == parse.E2E {
-		handleE2ESending(session, message, rekey)
-	} else {
-		padded, err := e2e.Pad(message.Contents.GetRightAligned(), format.ContentsLen)
-		if err != nil {
-			return err
-		}
-		message.Contents.Set(padded)
-		e2e.SetUnencrypted(message)
-		message.SetKeyFP(*format.NewFingerprint(session.GetCurrentUser().User.Marshal()[:32]))
-	}
-	// CMIX Encryption
-	salt := cmix.NewSalt(csprng.Source(&csprng.SystemRNG{}), 32)
-	encMsg, kmacs := crypto.CMIXEncrypt(session, topology, salt, message)
-
-	msgPacket := &pb.Slot{
-		SenderID: session.GetCurrentUser().User.Marshal(),
-		PayloadA: encMsg.GetPayloadA(),
-		PayloadB: encMsg.GetPayloadB(),
-		Salt:     salt,
-		KMACs:    kmacs,
-	}
-
-	return rm.Comms.SendPutMessage(transmitGateway, msgPacket)
-}
-
-func handleE2ESending(session user.Session,
-	message *format.Message,
-	rekey bool) {
-	recipientID, err := message.GetRecipient()
-	if err != nil {
-		globals.Log.ERROR.Panic(err)
-	}
-
-	var key *keyStore.E2EKey
-	var action keyStore.Action
-	// Get KeyManager for this partner
-	km := session.GetKeyStore().GetSendManager(recipientID)
-	if km == nil {
-		partners := session.GetKeyStore().GetPartners()
-		globals.Log.INFO.Printf("Valid Partner IDs: %+v", partners)
-		globals.Log.FATAL.Panicf("Couldn't get KeyManager to E2E encrypt message to"+
-			" user %v", *recipientID)
-	}
-
-	// FIXME: This is a hack to prevent a crash, this function should be
-	//        able to block until this condition is true.
-	for end, timeout := false, time.After(60*time.Second); !end; {
-		if rekey {
-			// Get send Rekey
-			key, action = km.PopRekey()
-		} else {
-			// Get send key
-			key, action = km.PopKey()
-		}
-		if key != nil {
-			end = true
-		}
-
-		select {
-		case <-timeout:
-			end = true
-		default:
-		}
-	}
-
-	if key == nil {
-		globals.Log.FATAL.Panicf("Couldn't get key to E2E encrypt message to"+
-			" user %v", *recipientID)
-	} else if action == keyStore.Purge {
-		// Destroy this key manager
-		km := key.GetManager()
-		km.Destroy(session.GetKeyStore())
-		globals.Log.WARN.Printf("Destroying E2E Send Keys Manager for partner: %v", *recipientID)
-	} else if action == keyStore.Deleted {
-		globals.Log.FATAL.Panicf("Key Manager is deleted when trying to get E2E Send Key")
-	}
-
-	if action == keyStore.Rekey {
-		// Send RekeyTrigger message to switchboard
-		rekeyMsg := &parse.Message{
-			Sender: session.GetCurrentUser().User,
-			TypedBody: parse.TypedBody{
-				MessageType: int32(cmixproto.Type_REKEY_TRIGGER),
-				Body:        []byte{},
-			},
-			InferredType: parse.None,
-			Receiver:     recipientID,
-		}
-		go session.GetSwitchboard().Speak(rekeyMsg)
-	}
-
-	globals.Log.DEBUG.Printf("E2E encrypting message")
-	if rekey {
-		crypto.E2EEncryptUnsafe(session.GetE2EGroup(),
-			key.GetKey(),
-			key.KeyFingerprint(),
-			message)
-	} else {
-		crypto.E2EEncrypt(session.GetE2EGroup(),
-			key.GetKey(),
-			key.KeyFingerprint(),
-			message)
-	}
-}
diff --git a/keyExchange/confirm.go b/keyExchange/confirm.go
new file mode 100644
index 0000000000000000000000000000000000000000..ce31c0c6fa933b3fccd203bf6b0bfdbd968818c5
--- /dev/null
+++ b/keyExchange/confirm.go
@@ -0,0 +1,91 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/e2e"
+)
+
+func startConfirm(sess *storage.Session, c chan message.Receive,
+	stop *stoppable.Single) {
+	for true {
+		select {
+		case <-stop.Quit():
+			return
+		case confirmation := <-c:
+			handleConfirm(sess, confirmation)
+		}
+	}
+}
+
+func handleConfirm(sess *storage.Session, confirmation message.Receive) {
+	//ensure the message was encrypted properly
+	if confirmation.Encryption != message.E2E {
+		jww.ERROR.Printf("Received non-e2e encrypted Key Exchange "+
+			"confirm from partner %s", confirmation.Sender)
+		return
+	}
+
+	//Get the partner
+	partner, err := sess.E2e().GetPartner(confirmation.Sender)
+	if err != nil {
+		jww.ERROR.Printf("Received Key Exchange Confirmation with unknown "+
+			"partner %s", confirmation.Sender)
+		return
+	}
+
+	//unmarshal the payload
+	confimedSessionID, err := unmarshalConfirm(confirmation.Payload)
+	if err != nil {
+		jww.ERROR.Printf("Failed to unmarshal Key Exchange Trigger with "+
+			"partner %s: %s", confirmation.Sender, err)
+		return
+	}
+
+	//get the confirmed session
+	confirmedSession := partner.GetSendSession(confimedSessionID)
+	if confirmedSession == nil {
+		jww.ERROR.Printf("Failed to find confirmed session %s from "+
+			"partner %s: %s", confimedSessionID, confirmation.Sender, err)
+		return
+	}
+
+	// Attempt to confirm the session. if this fails just print to the log.
+	// This is expected sometimes because some errors cases can cause multiple
+	// sends. For example if the sending device runs out of battery after it
+	// sends but before it records the send it will resend on reload
+	if err := confirmedSession.TrySetNegotiationStatus(e2e.Confirmed); err != nil {
+		jww.WARN.Printf("Failed to set the negotiation status for the "+
+			"confirmation of session %s from partner %s. This is expected in "+
+			"some edge cases but could be a sign of an issue if it percists: %s",
+			confirmedSession, partner.GetPartnerID(), err)
+	}
+}
+
+func unmarshalConfirm(payload []byte) (e2e.SessionID, error) {
+
+	msg := &RekeyConfirm{}
+	if err := proto.Unmarshal(payload, msg); err != nil {
+		return e2e.SessionID{}, errors.Errorf("Failed to "+
+			"unmarshal payload: %s", err)
+	}
+
+	confimedSessionID := e2e.SessionID{}
+	if err := confimedSessionID.Unmarshal(msg.SessionID); err != nil {
+		return e2e.SessionID{}, errors.Errorf("Failed to unmarshal"+
+			" sessionID: %s", err)
+	}
+
+	return confimedSessionID, nil
+}
diff --git a/keyExchange/confirm_test.go b/keyExchange/confirm_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2e619c684c0985ded4887178113ade1d489af1d
--- /dev/null
+++ b/keyExchange/confirm_test.go
@@ -0,0 +1,78 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"github.com/golang/protobuf/proto"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/e2e"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+// Smoke test for handleTrigger
+func TestHandleConfirm(t *testing.T) {
+	// Generate alice and bob's session
+	aliceSession, _ := InitTestingContextGeneric(t)
+	bobSession, _ := InitTestingContextGeneric(t)
+
+	// Maintain an ID for bob
+	bobID := id.NewIdFromBytes([]byte("test"), t)
+
+	// Pull the keys for Alice and Bob
+	alicePrivKey := aliceSession.E2e().GetDHPrivateKey()
+	bobPubKey := bobSession.E2e().GetDHPublicKey()
+
+	// Add bob as a partner
+	aliceSession.E2e().AddPartner(bobID, bobPubKey, alicePrivKey,
+		params.GetDefaultE2ESessionParams(),
+		params.GetDefaultE2ESessionParams())
+
+	// Generate a session ID, bypassing some business logic here
+	sessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup)
+
+	// Get Alice's manager for Bob
+	receivedManager, err := aliceSession.E2e().GetPartner(bobID)
+	if err != nil {
+		t.Errorf("Bob is not recognized as Alice's partner: %v", err)
+	}
+
+	// Trigger negotiations, so that negotiation statuses
+	// can be transitioned
+	receivedManager.TriggerNegotiations()
+
+	// Generate the message
+	rekey, _ := proto.Marshal(&RekeyConfirm{
+		SessionID: sessionID.Marshal(),
+	})
+
+	receiveMsg := message.Receive{
+		Payload:     rekey,
+		MessageType: message.KeyExchangeConfirm,
+		Sender:      bobID,
+		Timestamp:   time.Now(),
+		Encryption:  message.E2E,
+	}
+
+	// Handle the confirmation
+	handleConfirm(aliceSession, receiveMsg)
+
+	// Get Alice's session for Bob
+	confirmedSession := receivedManager.GetSendSession(sessionID)
+
+	// Check that the session is in the proper status
+	newSession := receivedManager.GetSendSession(sessionID)
+	if newSession.NegotiationStatus() != e2e.Confirmed {
+		t.Errorf("Session not in confirmed status!"+
+			"\n\tExpected: Confirmed"+
+			"\n\tReceived: %s", confirmedSession.NegotiationStatus())
+	}
+
+}
diff --git a/keyExchange/exchange.go b/keyExchange/exchange.go
new file mode 100644
index 0000000000000000000000000000000000000000..caf7c7a596762c95ea9da22e1cb5825dc80d566f
--- /dev/null
+++ b/keyExchange/exchange.go
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const keyExchangeTriggerName = "KeyExchangeTrigger"
+const keyExchangeConfirmName = "KeyExchangeConfirm"
+const keyExchangeMulti = "KeyExchange"
+
+func Start(switchboard *switchboard.Switchboard, sess *storage.Session, net interfaces.NetworkManager,
+	params params.Rekey) stoppable.Stoppable {
+
+	// register the rekey trigger thread
+	triggerCh := make(chan message.Receive, 100)
+	triggerID := switchboard.RegisterChannel(keyExchangeTriggerName,
+		&id.ID{}, message.KeyExchangeTrigger, triggerCh)
+
+	// create the trigger stoppable
+	triggerStop := stoppable.NewSingle(keyExchangeTriggerName)
+	triggerStopCleanup := stoppable.NewCleanup(triggerStop,
+		func(duration time.Duration) error {
+			switchboard.Unregister(triggerID)
+			return nil
+		})
+
+	// start the trigger thread
+	go startTrigger(sess, net, triggerCh, triggerStop.Quit(), params)
+
+	//register the rekey confirm thread
+	confirmCh := make(chan message.Receive, 100)
+	confirmID := switchboard.RegisterChannel(keyExchangeConfirmName,
+		&id.ID{}, message.KeyExchangeConfirm, confirmCh)
+
+	// register the confirm stoppable
+	confirmStop := stoppable.NewSingle(keyExchangeConfirmName)
+	confirmStopCleanup := stoppable.NewCleanup(confirmStop,
+		func(duration time.Duration) error {
+			switchboard.Unregister(confirmID)
+			return nil
+		})
+
+	// start the confirm thread
+	go startConfirm(sess, confirmCh, confirmStop)
+
+	//bundle the stoppables and return
+	exchangeStop := stoppable.NewMulti(keyExchangeMulti)
+	exchangeStop.Add(triggerStopCleanup)
+	exchangeStop.Add(confirmStopCleanup)
+	return exchangeStop
+}
diff --git a/keyExchange/exchange_test.go b/keyExchange/exchange_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2c1729995c28d528c19ffe88a92ae425d2b57e0d
--- /dev/null
+++ b/keyExchange/exchange_test.go
@@ -0,0 +1,111 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"fmt"
+	"github.com/golang/protobuf/proto"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/e2e"
+	"gitlab.com/elixxir/client/switchboard"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+var exchangeAliceId, exchangeBobId *id.ID
+var aliceSession, bobSession *storage.Session
+var aliceSwitchboard, bobSwitchboard *switchboard.Switchboard
+var aliceManager, bobManager interfaces.NetworkManager
+
+func TestFullExchange(t *testing.T) {
+	// Initialzie alice's and bob's session, switchboard and network managers
+	aliceSession, aliceSwitchboard, aliceManager = InitTestingContextFullExchange(t)
+	bobSession, bobSwitchboard, bobManager = InitTestingContextFullExchange(t)
+
+	// Assign ID's to alice and bob
+	exchangeAliceId = id.NewIdFromBytes([]byte("1234"), t)
+	exchangeBobId = id.NewIdFromBytes([]byte("test"), t)
+
+	// Pull alice's and bob's keys for later use
+	alicePrivKey := aliceSession.E2e().GetDHPrivateKey()
+	alicePubKey := aliceSession.E2e().GetDHPublicKey()
+	bobPrivKey := bobSession.E2e().GetDHPrivateKey()
+	bobPubKey := bobSession.E2e().GetDHPublicKey()
+
+	// Generate bob's new keypair
+	newBobPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, genericGroup, csprng.NewSystemRNG())
+	newBobPubKey := dh.GeneratePublicKey(newBobPrivKey, genericGroup)
+
+	// Add Alice and Bob as partners
+	aliceSession.E2e().AddPartner(exchangeBobId, bobPubKey, alicePrivKey,
+		params.GetDefaultE2ESessionParams(),
+		params.GetDefaultE2ESessionParams())
+	bobSession.E2e().AddPartner(exchangeAliceId, alicePubKey, bobPrivKey,
+		params.GetDefaultE2ESessionParams(),
+		params.GetDefaultE2ESessionParams())
+
+	// Start the listeners for alice and bob
+	rekeyParams := params.GetDefaultRekey()
+	rekeyParams.RoundTimeout = 1 * time.Second
+	Start(aliceSwitchboard, aliceSession, aliceManager, rekeyParams)
+	Start(bobSwitchboard, bobSession, bobManager, rekeyParams)
+
+	// Generate a session ID, bypassing some business logic here
+	oldSessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup)
+
+	// Generate the message
+	rekeyTrigger, _ := proto.Marshal(&RekeyTrigger{
+		SessionID: oldSessionID.Marshal(),
+		PublicKey: newBobPubKey.Bytes(),
+	})
+
+	triggerMsg := message.Receive{
+		Payload:     rekeyTrigger,
+		MessageType: message.KeyExchangeTrigger,
+		Sender:      exchangeBobId,
+		Timestamp:   time.Now(),
+		Encryption:  message.E2E,
+	}
+
+	// Get Alice's manager for reception from Bob
+	receivedManager, err := aliceSession.E2e().GetPartner(exchangeBobId)
+	if err != nil {
+		t.Errorf("Failed to get bob's manager: %v", err)
+	}
+
+	// Speak the message to Bob, triggers the SendE2E in utils_test
+	aliceSwitchboard.Speak(triggerMsg)
+
+	// Allow the test time to work it's goroutines
+	time.Sleep(1 * time.Second)
+
+	// Get Alice's session for Bob
+	confirmedSession := receivedManager.GetSendSession(oldSessionID)
+
+	// Generate the new session ID based off of Bob's new keys
+	baseKey := dh.GenerateSessionKey(alicePrivKey, newBobPubKey, genericGroup)
+	newSessionID := e2e.GetSessionIDFromBaseKeyForTesting(baseKey, t)
+
+	// Check that the Alice's session for Bob is in the proper status
+	newSession := receivedManager.GetReceiveSession(newSessionID)
+	fmt.Printf("newSession: %v\n", newSession)
+	if newSession == nil || newSession.NegotiationStatus() != e2e.Confirmed {
+		t.Errorf("Session not in confirmed status!"+
+			"\n\tExpected: Confirmed"+
+			"\n\tReceived: %s", confirmedSession.NegotiationStatus())
+	}
+
+	fmt.Printf("after status: %v\n", confirmedSession.NegotiationStatus())
+
+}
diff --git a/keyExchange/generate.sh b/keyExchange/generate.sh
new file mode 100644
index 0000000000000000000000000000000000000000..08904d8b1a8ffa53f9247b4c5338fb948d65dc51
--- /dev/null
+++ b/keyExchange/generate.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+protoc --go_out=. xchange.proto
diff --git a/keyExchange/rekey.go b/keyExchange/rekey.go
new file mode 100644
index 0000000000000000000000000000000000000000..f37e2e679715a3750ec40a8f9deed924476c32ad
--- /dev/null
+++ b/keyExchange/rekey.go
@@ -0,0 +1,145 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/e2e"
+	"gitlab.com/elixxir/comms/network"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/primitives/states"
+	"time"
+)
+
+func CheckKeyExchanges(instance *network.Instance, sendE2E interfaces.SendE2E,
+	sess *storage.Session, manager *e2e.Manager, sendTimeout time.Duration) {
+	sessions := manager.TriggerNegotiations()
+	for _, session := range sessions {
+		go trigger(instance, sendE2E, sess, manager, session, sendTimeout)
+	}
+}
+
+// There are two types of key negotiations that can be triggered, creating a new
+// session and negotiation, or resetting a negotiation for an already created
+// session. They run the same negotiation, the former does it on a newly created
+// session while the latter on an extant session
+func trigger(instance *network.Instance, sendE2E interfaces.SendE2E,
+	sess *storage.Session, manager *e2e.Manager, session *e2e.Session,
+	sendTimeout time.Duration) {
+	var negotiatingSession *e2e.Session
+	jww.INFO.Printf("Negotation triggered for session %s with "+
+		"status: %s", session, session.NegotiationStatus())
+	switch session.NegotiationStatus() {
+	// If the passed session is triggering a negotiation on a new session to
+	// replace itself, then create the session
+	case e2e.NewSessionTriggered:
+		//create the session, pass a nil private key to generate a new one
+		negotiatingSession = manager.NewSendSession(nil,
+			sess.E2e().GetE2ESessionParams())
+		//move the state of the triggering session forward
+		session.SetNegotiationStatus(e2e.NewSessionCreated)
+
+	// If the session is set to send a negotation
+	case e2e.Sending:
+		negotiatingSession = session
+	default:
+		jww.FATAL.Panicf("Session %s provided invalid e2e "+
+			"negotiating status: %s", session, session.NegotiationStatus())
+	}
+
+	// send the rekey notification to the partner
+	err := negotiate(instance, sendE2E, sess, negotiatingSession, sendTimeout)
+	// if sending the negotiation fails, revert the state of the session to
+	// unconfirmed so it will be triggered in the future
+	if err != nil {
+		jww.ERROR.Printf("Failed to do Key Negotiation with "+
+			"session %s: %s", session, err)
+	}
+}
+
+func negotiate(instance *network.Instance, sendE2E interfaces.SendE2E,
+	sess *storage.Session, session *e2e.Session,
+	sendTimeout time.Duration) error {
+	e2eStore := sess.E2e()
+
+	//generate public key
+	pubKey := diffieHellman.GeneratePublicKey(session.GetMyPrivKey(),
+		e2eStore.GetGroup())
+
+	//build the payload
+	payload, err := proto.Marshal(&RekeyTrigger{
+		PublicKey: pubKey.Bytes(),
+		SessionID: session.GetSource().Marshal(),
+	})
+
+	//If the payload cannot be marshaled, panic
+	if err != nil {
+		jww.FATAL.Printf("Failed to marshal payload for Key "+
+			"Negotation Trigger with %s", session.GetPartner())
+	}
+
+	//send session
+	m := message.Send{
+		Recipient:   session.GetPartner(),
+		Payload:     payload,
+		MessageType: message.KeyExchangeTrigger,
+	}
+
+	//send the message under the key exchange
+	e2eParams := params.GetDefaultE2E()
+	e2eParams.Type = params.KeyExchange
+
+	rounds, _, err := sendE2E(m, e2eParams)
+	// If the send fails, returns the error so it can be handled. The caller
+	// should ensure the calling session is in a state where the Rekey will
+	// be triggered next time a key is used
+	if err != nil {
+		return errors.Errorf("Failed to send the key negotation message "+
+			"for %s: %s", session, err)
+	}
+
+	//create the runner which will handle the result of sending the messages
+	sendResults := make(chan ds.EventReturn, len(rounds))
+
+	//Register the event for all rounds
+	roundEvents := instance.GetRoundEvents()
+	for _, r := range rounds {
+		roundEvents.AddRoundEventChan(r, sendResults, sendTimeout,
+			states.COMPLETED, states.FAILED)
+	}
+
+	//Wait until the result tracking responds
+	success, numTimeOut, numRoundFail := utility.TrackResults(sendResults, len(rounds))
+
+	// If a single partition of the Key Negotiation request does not
+	// transmit, the partner cannot read the result. Log the error and set
+	// the session as unconfirmed so it will re-trigger the negotiation
+	if !success {
+		session.SetNegotiationStatus(e2e.Unconfirmed)
+		return errors.Errorf("Key Negotiation for %s failed to "+
+			"transmit %v/%v paritions: %v round failures, %v timeouts",
+			session, numRoundFail+numTimeOut, len(rounds), numRoundFail,
+			numTimeOut)
+	}
+
+	// otherwise, the transmission is a success and this should be denoted
+	// in the session and the log
+	jww.INFO.Printf("Key Negotiation transmission for %s sucesful",
+		session)
+	session.SetNegotiationStatus(e2e.Sent)
+
+	return nil
+}
diff --git a/keyExchange/rekey_test.go b/keyExchange/rekey_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e7d585328135b54a50366257a1b72891c425fbf
--- /dev/null
+++ b/keyExchange/rekey_test.go
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+/*
+func TestRekey(t *testing.T) {
+	// Generate alice and bob's session
+	aliceSession, networkManager := InitTestingContextGeneric(t)
+	bobSession, _ := InitTestingContextGeneric(t)
+
+	// Pull the keys for Alice and Bob
+	alicePrivKey := aliceSession.E2e().GetDHPrivateKey()
+	bobPubKey := bobSession.E2e().GetDHPublicKey()
+
+	// Maintain an ID for bob
+	bobID := id.NewIdFromBytes([]byte("test"), t)
+
+	// Generate a session ID, bypassing some business logic here
+	sessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup)
+
+	// Add bob as a partner
+	aliceSession.E2e().AddPartner(bobID, bobPubKey, alicePrivKey,
+		e2e.GetDefaultSessionParams(), e2e.GetDefaultSessionParams())
+
+	// Get Alice's manager for Bob
+	bobManager, err := aliceSession.E2e().GetPartner(bobID)
+	if err != nil {
+		t.Errorf("Bob is not recognized as Alice's partner: %v", err)
+	}
+	//// Trigger negotiations, so that negotiation statuses
+	//// can be transitioned
+	bobManager.TriggerNegotiations()
+
+	bobE2ESession := bobManager.GetSendSession(sessionID)
+
+	err = negotiate(networkManager.GetInstance(), networkManager.SendE2E, aliceSession, bobE2ESession, 1*time.Second)
+	if err != nil {
+		t.Errorf("Negotiate resulted in error: %v", err)
+	}
+
+	if bobE2ESession.NegotiationStatus() != e2e.Sent {
+		t.Errorf("Session not in expected state after negotiation."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", e2e.Sent, bobE2ESession.NegotiationStatus())
+	}
+}*/
diff --git a/keyExchange/trigger.go b/keyExchange/trigger.go
new file mode 100644
index 0000000000000000000000000000000000000000..544cde8eee82c1979568f639cc0d454f6d205d06
--- /dev/null
+++ b/keyExchange/trigger.go
@@ -0,0 +1,182 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"fmt"
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/e2e"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/states"
+)
+
+const (
+	errBadTrigger = "non-e2e trigger from partner %s"
+	errUnknown    = "unknown trigger from partner %s"
+)
+
+func startTrigger(sess *storage.Session, net interfaces.NetworkManager,
+	c chan message.Receive, quitCh <-chan struct{}, params params.Rekey) {
+	for true {
+		select {
+		case <-quitCh:
+			return
+		case request := <-c:
+			err := handleTrigger(sess, net, request, params)
+			if err != nil {
+				jww.ERROR.Printf("Failed to handle rekey trigger: %s",
+					err)
+			}
+		}
+	}
+}
+
+func handleTrigger(sess *storage.Session, net interfaces.NetworkManager,
+	request message.Receive, param params.Rekey) error {
+	//ensure the message was encrypted properly
+	if request.Encryption != message.E2E {
+		errMsg := fmt.Sprintf(errBadTrigger, request.Sender)
+		jww.ERROR.Printf(errMsg)
+		return errors.New(errMsg)
+	}
+
+	//Get the partner
+	partner, err := sess.E2e().GetPartner(request.Sender)
+	if err != nil {
+		errMsg := fmt.Sprintf(errUnknown, request.Sender)
+		jww.ERROR.Printf(errMsg)
+		return errors.New(errMsg)
+	}
+
+	//unmarshal the message
+	oldSessionID, PartnerPublicKey, err := unmarshalSource(
+		sess.E2e().GetGroup(), request.Payload)
+	if err != nil {
+		jww.ERROR.Printf("could not unmarshal partner %s: %s",
+			request.Sender, err)
+		return err
+	}
+
+	//get the old session which triggered the exchange
+	oldSession := partner.GetSendSession(oldSessionID)
+	if oldSession == nil {
+		err := errors.Errorf("no session %s for partner %s: %s",
+			oldSession, request.Sender, err)
+		jww.ERROR.Printf(err.Error())
+		return err
+	}
+
+	//create the new session
+	session, duplicate := partner.NewReceiveSession(PartnerPublicKey,
+		sess.E2e().GetE2ESessionParams(), oldSession)
+	// new session being nil means the session was a duplicate. This is possible
+	// in edge cases where the partner crashes during operation. The session
+	// creation in this case ignores the new session, but the confirmation
+	// message is still sent so the partner will know the session is confirmed
+	if duplicate {
+		jww.INFO.Printf("New session from Key Exchange Trigger to "+
+			"create session %s for partner %s is a duplicate, request ignored",
+			session.GetID(), request.Sender)
+	} else {
+		// if the session is new, attempt to trigger garbled message processing
+		// automatically skips if there is contention
+		net.CheckGarbledMessages()
+	}
+
+	//Send the Confirmation Message
+	//build the payload
+	payload, err := proto.Marshal(&RekeyConfirm{
+		SessionID: session.GetSource().Marshal(),
+	})
+
+	//If the payload cannot be marshaled, panic
+	if err != nil {
+		jww.FATAL.Panicf("Failed to marshal payload for Key "+
+			"Negotation Confirmation with %s", session.GetPartner())
+	}
+
+	//build the message
+	m := message.Send{
+		Recipient:   session.GetPartner(),
+		Payload:     payload,
+		MessageType: message.KeyExchangeConfirm,
+	}
+
+	//send the message under the key exchange
+	e2eParams := params.GetDefaultE2E()
+
+	// store in critical messages buffer first to ensure it is resent if the
+	// send fails
+	sess.GetCriticalMessages().AddProcessing(m, e2eParams)
+
+	rounds, _, err := net.SendE2E(m, e2eParams)
+
+	//Register the event for all rounds
+	sendResults := make(chan ds.EventReturn, len(rounds))
+	roundEvents := net.GetInstance().GetRoundEvents()
+	for _, r := range rounds {
+		roundEvents.AddRoundEventChan(r, sendResults, param.RoundTimeout,
+			states.COMPLETED, states.FAILED)
+	}
+
+	//Wait until the result tracking responds
+	success, numTimeOut, numRoundFail := utility.TrackResults(sendResults, len(rounds))
+	// If a single partition of the Key Negotiation request does not
+	// transmit, the partner will not be able to read the confirmation. If
+	// such a failure occurs
+	if !success {
+		jww.ERROR.Printf("Key Negotiation for %s failed to "+
+			"transmit %v/%v paritions: %v round failures, %v timeouts",
+			session, numRoundFail+numTimeOut, len(rounds), numRoundFail,
+			numTimeOut)
+		sess.GetCriticalMessages().Failed(m)
+		return nil
+	}
+
+	// otherwise, the transmission is a success and this should be denoted
+	// in the session and the log
+	sess.GetCriticalMessages().Succeeded(m)
+	jww.INFO.Printf("Key Negotiation transmission for %s sucesfull",
+		session)
+
+	return nil
+}
+
+func unmarshalSource(grp *cyclic.Group, payload []byte) (e2e.SessionID,
+	*cyclic.Int, error) {
+
+	msg := &RekeyTrigger{}
+	if err := proto.Unmarshal(payload, msg); err != nil {
+		return e2e.SessionID{}, nil, errors.Errorf("Failed to "+
+			"unmarshal payload: %s", err)
+	}
+
+	oldSessionID := e2e.SessionID{}
+
+	if err := oldSessionID.Unmarshal(msg.SessionID); err != nil {
+		return e2e.SessionID{}, nil, errors.Errorf("Failed to unmarshal"+
+			" sessionID: %s", err)
+	}
+
+	// checking it is inside the group is necessary because otherwise the
+	// creation of the cyclic int will crash below
+	if !grp.BytesInside(msg.PublicKey) {
+		return e2e.SessionID{}, nil, errors.Errorf("Public key not in e2e group; PublicKey %v",
+			msg.PublicKey)
+	}
+
+	return oldSessionID, grp.NewIntFromBytes(msg.PublicKey), nil
+}
diff --git a/keyExchange/trigger_test.go b/keyExchange/trigger_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c69e09562bd10bbe998855c33f3c765b7f0ed085
--- /dev/null
+++ b/keyExchange/trigger_test.go
@@ -0,0 +1,99 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/e2e"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
+	"google.golang.org/protobuf/proto"
+	"testing"
+	"time"
+)
+
+// Smoke test for handleTrigger
+func TestHandleTrigger(t *testing.T) {
+	// Generate alice and bob's session
+	aliceSession, aliceManager := InitTestingContextGeneric(t)
+	bobSession, _ := InitTestingContextGeneric(t)
+
+	// Pull the keys for Alice and Bob
+	alicePrivKey := aliceSession.E2e().GetDHPrivateKey()
+	bobPubKey := bobSession.E2e().GetDHPublicKey()
+
+	// Generate bob's new keypair
+	newBobPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, genericGroup, csprng.NewSystemRNG())
+	newBobPubKey := dh.GeneratePublicKey(newBobPrivKey, genericGroup)
+
+	// Maintain an ID for bob
+	bobID := id.NewIdFromBytes([]byte("test"), t)
+
+	// Add bob as a partner
+	aliceSession.E2e().AddPartner(bobID, bobSession.E2e().GetDHPublicKey(),
+		alicePrivKey, params.GetDefaultE2ESessionParams(),
+		params.GetDefaultE2ESessionParams())
+
+	// Generate a session ID, bypassing some business logic here
+	oldSessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup)
+
+	// Generate the message
+	rekey, _ := proto.Marshal(&RekeyTrigger{
+		SessionID: oldSessionID.Marshal(),
+		PublicKey: newBobPubKey.Bytes(),
+	})
+
+	receiveMsg := message.Receive{
+		Payload:     rekey,
+		MessageType: message.NoType,
+		Sender:      bobID,
+		Timestamp:   time.Now(),
+		Encryption:  message.E2E,
+	}
+
+	// Handle the trigger and check for an error
+	rekeyParams := params.GetDefaultRekey()
+	rekeyParams.RoundTimeout = 0 * time.Second
+	err := handleTrigger(aliceSession, aliceManager, receiveMsg, rekeyParams)
+	if err != nil {
+		t.Errorf("Handle trigger error: %v", err)
+	}
+
+	// Get Alice's manager for reception from Bob
+	receivedManager, err := aliceSession.E2e().GetPartner(bobID)
+	if err != nil {
+		t.Errorf("Failed to get bob's manager: %v", err)
+	}
+
+	// Generate the new session ID based off of Bob's new keys
+	baseKey := dh.GenerateSessionKey(alicePrivKey, newBobPubKey, genericGroup)
+	newSessionID := e2e.GetSessionIDFromBaseKeyForTesting(baseKey, t)
+
+	// Check that this new session ID is now in the manager
+	newSession := receivedManager.GetReceiveSession(newSessionID)
+	if newSession == nil {
+		t.Errorf("Did not get expected session")
+	}
+
+	// Generate a keypair alice will not recognize
+	unknownPrivateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, genericGroup, csprng.NewSystemRNG())
+	unknownPubliceKey := dh.GeneratePublicKey(unknownPrivateKey, genericGroup)
+
+	// Generate a new session ID based off of these unrecognized keys
+	badSessionID := e2e.GetSessionIDFromBaseKeyForTesting(unknownPubliceKey, t)
+
+	// Check that this session with unrecognized keys is not valid
+	badSession := receivedManager.GetReceiveSession(badSessionID)
+	if badSession != nil {
+		t.Errorf("Alice found a session from an unknown keypair. "+
+			"\nSession: %v", badSession)
+	}
+
+}
diff --git a/keyExchange/utils_test.go b/keyExchange/utils_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..162db4728cec9ddfa0b91d0f0ae3acc6e0c7c522
--- /dev/null
+++ b/keyExchange/utils_test.go
@@ -0,0 +1,300 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package keyExchange
+
+import (
+	"testing"
+	"time"
+
+	"github.com/golang/protobuf/proto"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/e2e"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/cyclic"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	cE2e "gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"gitlab.com/xx_network/primitives/ndf"
+)
+
+// Generate partner ID for two people, used for smoke tests
+func GeneratePartnerID(aliceKey, bobKey *cyclic.Int,
+	group *cyclic.Group) e2e.SessionID {
+	baseKey := dh.GenerateSessionKey(aliceKey, bobKey, group)
+
+	h, _ := hash.NewCMixHash()
+	h.Write(baseKey.Bytes())
+
+	sid := e2e.SessionID{}
+
+	copy(sid[:], h.Sum(nil))
+
+	return sid
+}
+
+// Contains a test implementation of the networkManager interface. Used to bypass actual sending
+// between test clients in testing key exchange
+type testNetworkManagerGeneric struct {
+	instance *network.Instance
+}
+
+func (t *testNetworkManagerGeneric) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+
+func (t *testNetworkManagerGeneric) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManagerGeneric) CheckGarbledMessages() {
+	return
+}
+
+func (t *testNetworkManagerGeneric) SendE2E(m message.Send, p params.E2E) (
+	[]id.Round, cE2e.MessageID, error) {
+	rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
+	return rounds, cE2e.MessageID{}, nil
+
+}
+
+func (t *testNetworkManagerGeneric) SendUnsafe(m message.Send, p params.Unsafe) ([]id.Round, error) {
+
+	return nil, nil
+}
+
+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
+
+}
+
+func (t *testNetworkManagerGeneric) GetInstance() *network.Instance {
+	return t.instance
+
+}
+
+func (t *testNetworkManagerGeneric) RegisterWithPermissioning(string) ([]byte, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManagerGeneric) GetRemoteVersion() (string, error) {
+	return "test", nil
+}
+func (t *testNetworkManagerGeneric) GetStoppable() stoppable.Stoppable {
+	return &stoppable.Multi{}
+}
+
+func (t *testNetworkManagerGeneric) InProgressRegistrations() int {
+	return 0
+}
+
+func InitTestingContextGeneric(i interface{}) (*storage.Session, interfaces.NetworkManager) {
+	switch i.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", i)
+	}
+
+	thisSession := storage.InitTestingSession(i)
+	commsManager := connect.NewManagerTesting(i)
+	instanceComms := &connect.ProtoComms{
+
+		Manager: commsManager,
+	}
+
+	thisInstance, err := network.NewInstanceTesting(instanceComms, def, def, nil, nil, i)
+	if err != nil {
+		return nil, nil
+	}
+
+	thisManager := &testNetworkManagerGeneric{instance: thisInstance}
+
+	return thisSession, thisManager
+
+}
+
+// Contains a test implementation of the networkManager interface. Used to bypass actual sending
+// between test clients in testing key exchange
+// Separated from Generic to allow for a full stack test that doesn't impact the generic one used in smoke tests
+type testNetworkManagerFullExchange struct {
+	instance *network.Instance
+}
+
+func (t *testNetworkManagerFullExchange) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+
+func (t *testNetworkManagerFullExchange) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManagerFullExchange) CheckGarbledMessages() {
+	return
+}
+
+// Intended for alice to send to bob. Trigger's Bob's confirmation, chaining the operation
+// together
+func (t *testNetworkManagerFullExchange) SendE2E(m message.Send, p params.E2E) (
+	[]id.Round, cE2e.MessageID, error) {
+
+	rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
+	alicePrivKey := aliceSession.E2e().GetDHPrivateKey()
+	bobPubKey := bobSession.E2e().GetDHPublicKey()
+
+	sessionID := GeneratePartnerID(alicePrivKey, bobPubKey, genericGroup)
+
+	rekeyConfirm, _ := proto.Marshal(&RekeyConfirm{
+		SessionID: sessionID.Marshal(),
+	})
+	payload := make([]byte, 0)
+	payload = append(payload, rekeyConfirm...)
+
+	confirmMessage := message.Receive{
+		Payload:     payload,
+		MessageType: message.KeyExchangeConfirm,
+		Sender:      exchangeAliceId,
+		Timestamp:   time.Now(),
+		Encryption:  message.E2E,
+	}
+
+	bobSwitchboard.Speak(confirmMessage)
+
+	return rounds, cE2e.MessageID{}, nil
+
+}
+
+func (t *testNetworkManagerFullExchange) SendUnsafe(m message.Send, p params.Unsafe) ([]id.Round, error) {
+
+	return nil, nil
+}
+
+func (t *testNetworkManagerFullExchange) SendCMIX(message format.Message, eid *id.ID, p params.CMIX) (id.Round, ephemeral.Id, error) {
+
+	return id.Round(0), ephemeral.Id{}, nil
+
+}
+
+func (t *testNetworkManagerFullExchange) GetInstance() *network.Instance {
+	return t.instance
+
+}
+
+func (t *testNetworkManagerFullExchange) RegisterWithPermissioning(string) ([]byte, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManagerFullExchange) GetRemoteVersion() (string, error) {
+	return "test", nil
+}
+func (t *testNetworkManagerFullExchange) GetStoppable() stoppable.Stoppable {
+	return &stoppable.Multi{}
+}
+
+func (t *testNetworkManagerFullExchange) InProgressRegistrations() int {
+	return 0
+}
+
+func InitTestingContextFullExchange(i interface{}) (*storage.Session, *switchboard.Switchboard, interfaces.NetworkManager) {
+	switch i.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", i)
+	}
+
+	thisSession := storage.InitTestingSession(i)
+	commsManager := connect.NewManagerTesting(i)
+	instanceComms := &connect.ProtoComms{
+		Manager: commsManager,
+	}
+
+	_, err := instanceComms.AddHost(&id.Permissioning, "0.0.0.0:420", []byte(pub), connect.GetDefaultHostParams())
+	if err != nil {
+		return nil, nil, nil
+	}
+	thisInstance, err := network.NewInstanceTesting(instanceComms, def, def, nil, nil, i)
+	if err != nil {
+		return nil, nil, nil
+	}
+
+	thisManager := &testNetworkManagerFullExchange{instance: thisInstance}
+
+	return thisSession, switchboard.New(), thisManager
+
+}
+
+var pub = "-----BEGIN CERTIFICATE-----\nMIIGHTCCBAWgAwIBAgIUOcAn9cpH+hyRH8/UfqtbFDoSxYswDQYJKoZIhvcNAQEL\nBQAwgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJQ2xhcmVt\nb250MRAwDgYDVQQKDAdFbGl4eGlyMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEZMBcG\nA1UEAwwQZ2F0ZXdheS5jbWl4LnJpcDEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZWxp\neHhpci5pbzAeFw0xOTA4MTYwMDQ4MTNaFw0yMDA4MTUwMDQ4MTNaMIGSMQswCQYD\nVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUNsYXJlbW9udDEQMA4GA1UE\nCgwHRWxpeHhpcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGTAXBgNVBAMMEGdhdGV3\nYXkuY21peC5yaXAxHzAdBgkqhkiG9w0BCQEWEGFkbWluQGVsaXh4aXIuaW8wggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7Dkb6VXFn4cdpU0xh6ji0nTDQ\nUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZrtzujFPBRFp9O\n14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfITVCv8CLE0t1i\nbiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGeskWEFa2VttHqF\n910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq6/OAXCU1JLi3\nkW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzfrarmsGM0LZh6\nJY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYICqldpt79gaET\n9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8VMKbrCaOkzD5z\ngnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4So9AppDQB41SH\n3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenPel2ApMXp+LVR\ndDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/uSALsU2v9UHBz\nprdrLSZk2YpozJb+CQIDAQABo2kwZzAdBgNVHQ4EFgQUDaTvG7SwgRQ3wcYx4l+W\nMcZjX7owHwYDVR0jBBgwFoAUDaTvG7SwgRQ3wcYx4l+WMcZjX7owDwYDVR0TAQH/\nBAUwAwEB/zAUBgNVHREEDTALgglmb28uY28udWswDQYJKoZIhvcNAQELBQADggIB\nADKz0ST0uS57oC4rT9zWhFqVZkEGh1x1XJ28bYtNUhozS8GmnttV9SnJpq0EBCm/\nr6Ub6+Wmf60b85vCN5WDYdoZqGJEBjGGsFzl4jkYEE1eeMfF17xlNUSdt1qLCE8h\nU0glr32uX4a6nsEkvw1vo1Liuyt+y0cOU/w4lgWwCqyweu3VuwjZqDoD+3DShVzX\n8f1p7nfnXKitrVJt9/uE+AtAk2kDnjBFbRxCfO49EX4Cc5rADUVXMXm0itquGBYp\nMbzSgFmsMp40jREfLYRRzijSZj8tw14c2U9z0svvK9vrLCrx9+CZQt7cONGHpr/C\n/GIrP/qvlg0DoLAtjea73WxjSCbdL3Nc0uNX/ymXVHdQ5husMCZbczc9LYdoT2VP\nD+GhkAuZV9g09COtRX4VP09zRdXiiBvweiq3K78ML7fISsY7kmc8KgVH22vcXvMX\nCgGwbrxi6QbQ80rWjGOzW5OxNFvjhvJ3vlbOT6r9cKZGIPY8IdN/zIyQxHiim0Jz\noavr9CPDdQefu9onizsmjsXFridjG/ctsJxcUEqK7R12zvaTxu/CVYZbYEUFjsCe\nq6ZAACiEJGvGeKbb/mSPvGs2P1kS70/cGp+P5kBCKqrm586FB7BcafHmGFrWhT3E\nLOUYkOV/gADT2hVDCrkPosg7Wb6ND9/mhCVVhf4hLGRh\n-----END CERTIFICATE-----\n"
+var def = getNDF()
+var genericGroup = cyclic.NewGroup(
+	large.NewIntFromString("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48"+
+		"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F"+
+		"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5"+
+		"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2"+
+		"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41"+
+		"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE"+
+		"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15"+
+		"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", 16),
+	large.NewIntFromString("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613"+
+		"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4"+
+		"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472"+
+		"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5"+
+		"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA"+
+		"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
+		"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
+		"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
+
+func getNDF() *ndf.NetworkDefinition {
+	return &ndf.NetworkDefinition{
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}
diff --git a/keyExchange/xchange.pb.go b/keyExchange/xchange.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..27a276b4c1a9538e767adb1994b684bb65369d1b
--- /dev/null
+++ b/keyExchange/xchange.pb.go
@@ -0,0 +1,232 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Call ./generate.sh to generate the protocol buffer code
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.25.0
+// 	protoc        (unknown)
+// source: xchange.proto
+
+package keyExchange
+
+import (
+	proto "github.com/golang/protobuf/proto"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// This is a compile-time assertion that a sufficiently up-to-date version
+// of the legacy proto package is being used.
+const _ = proto.ProtoPackageIsVersion4
+
+type RekeyTrigger struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// PublicKey used in the registration
+	PublicKey []byte `protobuf:"bytes,1,opt,name=publicKey,proto3" json:"publicKey,omitempty"`
+	// ID of the session used to create this session
+	SessionID []byte `protobuf:"bytes,2,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+}
+
+func (x *RekeyTrigger) Reset() {
+	*x = RekeyTrigger{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_xchange_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RekeyTrigger) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RekeyTrigger) ProtoMessage() {}
+
+func (x *RekeyTrigger) ProtoReflect() protoreflect.Message {
+	mi := &file_xchange_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RekeyTrigger.ProtoReflect.Descriptor instead.
+func (*RekeyTrigger) Descriptor() ([]byte, []int) {
+	return file_xchange_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *RekeyTrigger) GetPublicKey() []byte {
+	if x != nil {
+		return x.PublicKey
+	}
+	return nil
+}
+
+func (x *RekeyTrigger) GetSessionID() []byte {
+	if x != nil {
+		return x.SessionID
+	}
+	return nil
+}
+
+type RekeyConfirm struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// ID of the session created
+	SessionID []byte `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
+}
+
+func (x *RekeyConfirm) Reset() {
+	*x = RekeyConfirm{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_xchange_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RekeyConfirm) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RekeyConfirm) ProtoMessage() {}
+
+func (x *RekeyConfirm) ProtoReflect() protoreflect.Message {
+	mi := &file_xchange_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RekeyConfirm.ProtoReflect.Descriptor instead.
+func (*RekeyConfirm) Descriptor() ([]byte, []int) {
+	return file_xchange_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *RekeyConfirm) GetSessionID() []byte {
+	if x != nil {
+		return x.SessionID
+	}
+	return nil
+}
+
+var File_xchange_proto protoreflect.FileDescriptor
+
+var file_xchange_proto_rawDesc = []byte{
+	0x0a, 0x0d, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+	0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x22, 0x4a, 0x0a, 0x0c, 0x52, 0x65, 0x6b, 0x65, 0x79, 0x54,
+	0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+	0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69,
+	0x63, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49,
+	0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+	0x49, 0x44, 0x22, 0x2c, 0x0a, 0x0c, 0x52, 0x65, 0x6b, 0x65, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x72, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44,
+	0x42, 0x0d, 0x5a, 0x0b, 0x6b, 0x65, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x62,
+	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_xchange_proto_rawDescOnce sync.Once
+	file_xchange_proto_rawDescData = file_xchange_proto_rawDesc
+)
+
+func file_xchange_proto_rawDescGZIP() []byte {
+	file_xchange_proto_rawDescOnce.Do(func() {
+		file_xchange_proto_rawDescData = protoimpl.X.CompressGZIP(file_xchange_proto_rawDescData)
+	})
+	return file_xchange_proto_rawDescData
+}
+
+var file_xchange_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_xchange_proto_goTypes = []interface{}{
+	(*RekeyTrigger)(nil), // 0: parse.RekeyTrigger
+	(*RekeyConfirm)(nil), // 1: parse.RekeyConfirm
+}
+var file_xchange_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_xchange_proto_init() }
+func file_xchange_proto_init() {
+	if File_xchange_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_xchange_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RekeyTrigger); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_xchange_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RekeyConfirm); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_xchange_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_xchange_proto_goTypes,
+		DependencyIndexes: file_xchange_proto_depIdxs,
+		MessageInfos:      file_xchange_proto_msgTypes,
+	}.Build()
+	File_xchange_proto = out.File
+	file_xchange_proto_rawDesc = nil
+	file_xchange_proto_goTypes = nil
+	file_xchange_proto_depIdxs = nil
+}
diff --git a/keyExchange/xchange.proto b/keyExchange/xchange.proto
new file mode 100644
index 0000000000000000000000000000000000000000..633c79224c267de1a8325c208897a60fcb2428be
--- /dev/null
+++ b/keyExchange/xchange.proto
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Call ./generate.sh to generate the protocol buffer code
+
+syntax = "proto3";
+
+package parse;
+option go_package = "keyExchange";
+
+message RekeyTrigger {
+    // PublicKey used in the registration
+    bytes publicKey = 1;
+    // ID of the session used to create this session
+    bytes sessionID = 2;
+}
+
+message RekeyConfirm {
+    // ID of the session created
+    bytes sessionID = 1;
+}
diff --git a/keyStore/action.go b/keyStore/action.go
deleted file mode 100644
index 52cdf7e389bf22821c4ca8ba6981f0cee82c7987..0000000000000000000000000000000000000000
--- a/keyStore/action.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package keyStore
-
-type Action uint8
-
-const (
-	None Action = iota
-	Rekey
-	Purge
-	Deleted
-)
-
-func (a Action) String() string {
-	var ret string
-	switch a {
-	case None:
-		ret = "None"
-	case Rekey:
-		ret = "Rekey"
-	case Purge:
-		ret = "Purge"
-	case Deleted:
-		ret = "Deleted"
-	}
-	return ret
-}
diff --git a/keyStore/action_test.go b/keyStore/action_test.go
deleted file mode 100644
index 76463d97e5281e3654b4f5be8834a045e5bec2fa..0000000000000000000000000000000000000000
--- a/keyStore/action_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package keyStore
-
-import "testing"
-
-// Test all outputs of String for coverage
-func TestAction_String(t *testing.T) {
-	expectedStr := "None"
-	action := None
-	if action.String() != expectedStr {
-		t.Errorf("String returned %s, expected %s",
-			action.String(),
-			expectedStr)
-	}
-
-	expectedStr = "Rekey"
-	action = Rekey
-	if action.String() != expectedStr {
-		t.Errorf("String returned %s, expected %s",
-			action.String(),
-			expectedStr)
-	}
-
-	expectedStr = "Purge"
-	action = Purge
-	if action.String() != expectedStr {
-		t.Errorf("String returned %s, expected %s",
-			action.String(),
-			expectedStr)
-	}
-
-	expectedStr = "Deleted"
-	action = Deleted
-	if action.String() != expectedStr {
-		t.Errorf("String returned %s, expected %s",
-			action.String(),
-			expectedStr)
-	}
-}
diff --git a/keyStore/e2eKey.go b/keyStore/e2eKey.go
deleted file mode 100644
index 3c6226477ab86bd3bc34d30786eea663abe00e29..0000000000000000000000000000000000000000
--- a/keyStore/e2eKey.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package keyStore
-
-import (
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/primitives/format"
-)
-
-type E2EKey struct {
-	// Link to Manager
-	manager *KeyManager
-
-	// Key to be used
-	key *cyclic.Int
-
-	// Designation of crypto type
-	outer parse.CryptoType
-
-	// keyNum is needed by Key Manager
-	// to keep track of which receiving keys
-	// have been used
-	keyNum uint32
-}
-
-// Get key manager
-func (e2ekey *E2EKey) GetManager() *KeyManager {
-	return e2ekey.manager
-}
-
-// Get key value (cyclic.Int)
-func (e2ekey *E2EKey) GetKey() *cyclic.Int {
-	return e2ekey.key
-}
-
-// Get key type, E2E or Rekey
-func (e2ekey *E2EKey) GetOuterType() parse.CryptoType {
-	return e2ekey.outer
-}
-
-// Generate key fingerprint
-// NOTE: This function is not a getter,
-// it returns a new byte array on each call
-func (e2ekey *E2EKey) KeyFingerprint() format.Fingerprint {
-	h, _ := hash.NewCMixHash()
-	h.Write(e2ekey.key.Bytes())
-	fp := format.Fingerprint{}
-	copy(fp[:], h.Sum(nil))
-	return fp
-}
diff --git a/keyStore/e2eKey_test.go b/keyStore/e2eKey_test.go
deleted file mode 100644
index 19fa0b083b1a321eebe8428d56915d5913b883c7..0000000000000000000000000000000000000000
--- a/keyStore/e2eKey_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/hex"
-	"testing"
-)
-
-// Test key fingerprint for consistency
-func TestE2EKey_KeyFingerprint(t *testing.T) {
-	grp := initGroup()
-	key := new(E2EKey)
-	key.key = grp.NewInt(42)
-	keyFP := key.KeyFingerprint()
-	expectedFP, _ := hex.DecodeString(
-		"395a122eb1402bf256d86e3fa44764cf" +
-			"9acc559017a00b2b9ee12498e73ef2b5")
-
-	if !bytes.Equal(keyFP[:], expectedFP) {
-		t.Errorf("Key Fingerprint value is wrong. Expected %x"+
-			", got %x", expectedFP, keyFP[:])
-	}
-}
diff --git a/keyStore/keyManager.go b/keyStore/keyManager.go
deleted file mode 100644
index a6938702fa624f69145c159f74a883ae46da1e7e..0000000000000000000000000000000000000000
--- a/keyStore/keyManager.go
+++ /dev/null
@@ -1,592 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/binary"
-	"encoding/gob"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"sync/atomic"
-)
-
-// The KeyManager keeps track of all keys used in a single E2E
-// uni-directional relationship between the user and a partner
-// It tracks usage of send Keys and ReKeys in an atomic sendState
-// OR
-// It tracks usage of receiving Keys and ReKeys in lists of
-// atomic "dirty bit" states
-// It also owns the send Keys and ReKeys stacks of keys
-// OR lists of receiving Keys and ReKeys fingerprints
-// All Key Managers can be stored in the session object, and
-// can be GOB encoded/decoded, preserving the state
-// When the GOB Decode is successful, GenerateKeys can be called
-// on the KeyManager to generate all keys that have not been used
-type KeyManager struct {
-	// Underlying key
-	baseKey *cyclic.Int
-	// Own Private Key
-	privKey *cyclic.Int
-	// Partner Public Key
-	pubKey *cyclic.Int
-
-	// Designates end-to-end partner
-	partner *id.ID
-
-	// True if key manager tracks send keys, false if receive keys
-	sendOrRecv bool
-
-	// State of Sending Keys and Rekeys, formatted as follows:
-	//                      Bits
-	// |    63   | 62 - 56 |   55 - 40   | 39 - 32 |  31 - 0   |
-	// | deleted |  empty  | rekey count |  empty  | key count |
-	// |  1 bit  |  7 bits |   16 bits   |  8 bits |   32 bits |
-	sendState *uint64
-
-	// Value of the counter at which a rekey is triggered
-	ttl uint16
-
-	// Total number of Keys
-	numKeys uint32
-	// Total number of Rekey keys
-	numReKeys uint16
-
-	// Received Keys dirty bits
-	// Each bit represents a single Receiving Key
-	recvKeysState [numStates]*uint64
-	// Received ReKeys dirty bits
-	// Each bit represents a single Receiving ReKey
-	recvReKeysState [numReStates]*uint64
-
-	// Send Keys Stack
-	sendKeys *KeyStack
-	// Send ReKeys Stack
-	sendReKeys *KeyStack
-	// Receive Keys fingerprint list
-	recvKeysFingerprint []format.Fingerprint
-	// Receive ReKeys fingerprint list
-	recvReKeysFingerprint []format.Fingerprint
-}
-
-// Creates a new KeyManager to manage E2E Keys between user and partner
-// Receives the baseKey, privKey, pubKey, partner userID, numKeys, ttl and numReKeys
-// All internal states are forced to 0 for safety purposes
-func NewManager(baseKey *cyclic.Int,
-	privKey *cyclic.Int, pubKey *cyclic.Int,
-	partner *id.ID, sendOrRecv bool,
-	numKeys uint32, ttl uint16, numReKeys uint16) *KeyManager {
-
-	km := new(KeyManager)
-	km.baseKey = baseKey
-	km.privKey = privKey
-	km.pubKey = pubKey
-	km.partner = partner
-	km.sendOrRecv = sendOrRecv
-	km.sendState = new(uint64)
-	*km.sendState = 0
-	km.ttl = ttl
-	km.numKeys = numKeys
-	km.numReKeys = numReKeys
-	for i := range km.recvKeysState {
-		km.recvKeysState[i] = new(uint64)
-		*km.recvKeysState[i] = 0
-	}
-	for i := range km.recvReKeysState {
-		km.recvReKeysState[i] = new(uint64)
-		*km.recvReKeysState[i] = 0
-	}
-	return km
-}
-
-// Get the base key from the Key Manager
-func (km *KeyManager) GetBaseKey() *cyclic.Int {
-	return km.baseKey
-}
-
-// Get the private key from the Key Manager
-func (km *KeyManager) GetPrivKey() *cyclic.Int {
-	return km.privKey
-}
-
-// Get the public key from the Key Manager
-func (km *KeyManager) GetPubKey() *cyclic.Int {
-	return km.pubKey
-}
-
-// Get the partner ID from the Key Manager
-func (km *KeyManager) GetPartner() *id.ID {
-	return km.partner
-}
-
-// Constants needed for access to sendState
-//                      Bits
-// |    63   | 62 - 56 |   55 - 40   | 39 - 32 |  31 - 0   |
-// | deleted |  empty  | rekey count |  empty  | key count |
-// |  1 bit  |  7 bits |   16 bits   |  8 bits |   32 bits |
-const (
-	// Delete is most significant bit
-	stateDeleteMask uint64 = 0x8000000000000000
-	// Key Counter is lowest 32 bits
-	stateKeyMask uint64 = 0x00000000FFFFFFFF
-	// ReKey Counter is bits 55 to 40 (0 indexed)
-	stateReKeyMask uint64 = 0x00FFFF0000000000
-	// ReKey Counter shift value is 40
-	stateReKeyShift uint64 = 40
-	// Delete Increment is 1 shifted by 63 bits
-	stateDeleteIncr uint64 = 1 << 63
-	// Key Counter increment is 1
-	stateKeyIncr uint64 = 1
-	// ReKey Counter increment is 1 << 40
-	stateReKeyIncr uint64 = 1 << stateReKeyShift
-)
-
-// Check if a Rekey should be triggered
-// Extract the Key counter from state and then
-// compare to passed val
-func checkRekey(state uint64, val uint16) bool {
-	keyCounter := uint32(state & stateKeyMask)
-	return keyCounter >= uint32(val)
-}
-
-// Check if a Purge should be triggered
-// Extract the ReKey counter from state and then
-// compare to passed val
-func checkPurge(state uint64, val uint16) bool {
-	reKeyCounter := uint16((state & stateReKeyMask) >> stateReKeyShift)
-	return reKeyCounter >= val
-}
-
-// UpdateState atomically updates internal state
-// of key manager for send Keys or ReKeys
-// Once the number of used keys reaches the TTL value
-// a Rekey Action is returned
-// Once the number of used ReKeys reaches the the NumReKeys
-// value, a Purge Action is returned, and the Key Manager
-// can be destroyed
-// When a Purge is returned, the state topmost bit is set,
-// indicating that the KeyManager is now Deleted
-// This means that if the caller doesn't destroy it
-// right away, any further send Keys obtained from the
-// global key map will have the action set to Deleted
-// which can be used to trigger an error
-func (km *KeyManager) updateState(rekey bool) Action {
-	var stateIncr uint64
-	// Choose the correct increment according to key type
-	if rekey {
-		stateIncr = stateReKeyIncr
-	} else {
-		stateIncr = stateKeyIncr
-	}
-
-	// Atomically increment the state and save result
-	result := atomic.AddUint64(km.sendState, stateIncr)
-
-	// Check if KeyManager is in Deleted state
-	if result&stateDeleteMask != 0 {
-		return Deleted
-	}
-
-	// Check if result should trigger a Purge
-	if rekey && checkPurge(result, km.numReKeys) {
-		// set delete bit
-		atomic.AddUint64(km.sendState, stateDeleteIncr)
-		return Purge
-		// Check if result should trigger a Rekey
-	} else if !rekey && checkRekey(result, km.ttl) {
-		return Rekey
-	}
-	return None
-}
-
-// UpdateRecvState atomically updates internal
-// receiving state of key manager
-// It sets the correct bit of state index based on keyNum
-// and rekey
-// The keyNum is used to select the correct state from the array
-// Since each state is an uint64, keyNum / 64 determines the index
-// and keyNum % 64 determines the bit that needs to be set
-// Rekey is used to select which state array to update:
-// recvReKeysState or recvKeysState
-// The state is atomically updated by adding a value of 1 shifted
-// to the determined bit
-func (km *KeyManager) updateRecvState(rekey bool, keyNum uint32) {
-	stateIdx := keyNum / 64
-	stateBit := uint64(1 << (keyNum % 64))
-
-	if rekey {
-		atomic.AddUint64(km.recvReKeysState[stateIdx], stateBit)
-	} else {
-		atomic.AddUint64(km.recvKeysState[stateIdx], stateBit)
-	}
-}
-
-// Return true if bit specified by keyNum is set, meaning
-// that a particular key or reKey has been used
-// The keyNum is used to select the correct state from the array
-// Since each state is an uint64, keyNum / 64 determines the index
-// and keyNum % 64 determines the bit that needs to be read
-// Rekey is used to select which state array to update:
-// recvReKeysState or recvKeysState
-// The state is atomically loaded and then the bit mask is applied
-// to check if the value is 0 or different
-func (km *KeyManager) checkRecvStateBit(rekey bool, keyNum uint32) bool {
-	stateIdx := keyNum / 64
-	stateBit := uint64(1 << (keyNum % 64))
-
-	var state uint64
-	if rekey {
-		state = atomic.LoadUint64(km.recvReKeysState[stateIdx])
-	} else {
-		state = atomic.LoadUint64(km.recvKeysState[stateIdx])
-	}
-
-	return (state & stateBit) != 0
-}
-
-// GenerateKeys will generate all previously unused keys based on
-// KeyManager states
-// Sending Keys and ReKeys are generated and then pushed to a stack,
-// meaning that they are used in a LIFO manner.
-// This makes it easier to generate all send keys from a pre-existing state
-// as the number of unused keys will be simply numKeys - usedKeys
-// where usedKeys is extracted from the KeyManager state
-// Receiving Keys and ReKeys are generated in order, but there is no
-// guarantee that they will be used in order, this is why KeyManager
-// keeps a list of fingerprint for all receiving keys
-// When generating receiving keys from pre-existing state, all bits
-// from receiving states are checked, and if the bit is set ("dirty")
-// the key is not added to the Reception Keys map and fingerprint list
-// This way, this function can be used to generate all keys when a new
-// E2E relationship is established, and also to generate all previously
-// unused keys based on KeyManager state, when reloading an user session
-// The function returns modifications that need to be independently made to the keystore.
-func (km *KeyManager) GenerateKeys(grp *cyclic.Group, userID *id.ID) []*E2EKey {
-	var recE2EKeys []*E2EKey
-
-	if km.sendOrRecv {
-		// Calculate how many unused send keys are needed
-		usedSendKeys := uint32(*km.sendState & stateKeyMask)
-		numGenSendKeys := uint(km.numKeys - usedSendKeys)
-		usedSendReKeys := uint16((*km.sendState & stateReKeyMask) >> stateReKeyShift)
-		numGenSendReKeys := uint(km.numReKeys - usedSendReKeys)
-
-		// Generate numGenSendKeys send keys
-		sendKeys := e2e.DeriveKeys(grp, km.baseKey, userID, numGenSendKeys)
-		// Generate numGenSendReKeys send reKeys
-		sendReKeys := e2e.DeriveEmergencyKeys(grp, km.baseKey, userID, numGenSendReKeys)
-
-		// Create Send Keys Stack on keyManager
-		km.sendKeys = NewKeyStack()
-
-		// Create send E2E Keys and add to stack
-		for _, key := range sendKeys {
-			e2ekey := new(E2EKey)
-			e2ekey.key = key
-			e2ekey.manager = km
-			e2ekey.outer = parse.E2E
-			km.sendKeys.Push(e2ekey)
-		}
-
-		// Create Send ReKeys Stack on keyManager
-		km.sendReKeys = NewKeyStack()
-
-		// Create send E2E ReKeys and add to stack
-		for _, key := range sendReKeys {
-			e2ekey := new(E2EKey)
-			e2ekey.key = key
-			e2ekey.manager = km
-			e2ekey.outer = parse.Rekey
-			km.sendReKeys.Push(e2ekey)
-		}
-
-	} else {
-		// For receiving keys, generate all, and then only add to the map
-		// the unused ones based on recvStates
-		// Generate numKeys recv keys
-		recvKeys := e2e.DeriveKeys(grp, km.baseKey, km.partner, uint(km.numKeys))
-		// Generate numReKeys recv reKeys
-		recvReKeys := e2e.DeriveEmergencyKeys(grp, km.baseKey, km.partner, uint(km.numReKeys))
-
-		// Create Receive E2E Keys and put them into the E2eKeys obbj to return into the parent
-		// Skip keys that were already used as per recvStates
-		km.recvKeysFingerprint = make([]format.Fingerprint, 0)
-		for i, key := range recvKeys {
-			if !km.checkRecvStateBit(false, uint32(i)) {
-				e2ekey := new(E2EKey)
-				e2ekey.key = key
-				e2ekey.manager = km
-				e2ekey.outer = parse.E2E
-				e2ekey.keyNum = uint32(i)
-				recE2EKeys = append(recE2EKeys, e2ekey)
-				keyFP := e2ekey.KeyFingerprint()
-				km.recvKeysFingerprint = append(km.recvKeysFingerprint, keyFP)
-			}
-		}
-
-		// Create Receive E2E reKeys and add them into the E2ERekeys variable to return back to parent
-		// while keeping a list of the fingerprints
-		km.recvReKeysFingerprint = make([]format.Fingerprint, 0)
-		for i, key := range recvReKeys {
-			if !km.checkRecvStateBit(true, uint32(i)) {
-				e2ekey := new(E2EKey)
-				e2ekey.key = key
-				e2ekey.manager = km
-				e2ekey.outer = parse.Rekey
-				e2ekey.keyNum = uint32(i)
-				recE2EKeys = append(recE2EKeys, e2ekey)
-				keyFP := e2ekey.KeyFingerprint()
-				km.recvReKeysFingerprint = append(km.recvReKeysFingerprint, keyFP)
-			}
-		}
-	}
-	return recE2EKeys
-}
-
-// Pops first key from Send KeyStack of KeyManager
-// Atomically updates Key Manager Sending state
-// Returns *E2EKey and KeyAction
-func (km *KeyManager) PopKey() (*E2EKey, Action) {
-	// Pop key
-	e2eKey := km.sendKeys.Pop()
-	// Update Key Manager State
-	action := km.updateState(false)
-	return e2eKey, action
-}
-
-// Pops first rekey from Send ReKeyStack of KeyManager
-// Atomically updates Key Manager Sending state
-// Returns *E2EKey and KeyAction
-func (km *KeyManager) PopRekey() (*E2EKey, Action) {
-	// Pop key
-	e2eKey := km.sendReKeys.Pop()
-	// Update Key Manager State
-	action := km.updateState(true)
-	return e2eKey, action
-}
-
-// If the KeyManager is a sending one, destroy
-// will remove it from KeyStore map and then destroy it's key stacks
-// If it is a receiving one, destroy will remove it
-// from KeyStore map and then remove all keys from receiving key
-// map
-func (km *KeyManager) Destroy(ks *KeyStore) {
-	if km.sendOrRecv {
-		// Remove KeyManager from KeyStore
-		ks.DeleteSendManager(km.partner)
-		// Delete KeyStacks
-		km.sendKeys.Delete()
-		km.sendReKeys.Delete()
-	} else {
-		globals.Log.WARN.Println("This function no longer handles deleting of reception keys.")
-	}
-
-	// Hopefully when the function returns there
-	// will be no keys referencing this Manager left,
-	// so it will be garbage collected
-}
-
-// GobEncode the KeyManager so that it can be saved in
-// the session file
-func (km *KeyManager) GobEncode() ([]byte, error) {
-	// Anonymous structure that flattens nested structures
-	s := struct {
-		Partner        []byte
-		SendOrRecv     []byte
-		State          []byte
-		TTL            []byte
-		NumKeys        []byte
-		NumReKeys      []byte
-		RecvKeyState   []byte
-		RecvReKeyState []byte
-		BaseKey        []byte
-		PrivKey        []byte
-		PubKey         []byte
-	}{
-		km.partner.Bytes(),
-		make([]byte, 1),
-		make([]byte, 8),
-		make([]byte, 2),
-		make([]byte, 4),
-		make([]byte, 2),
-		make([]byte, 8*numStates),
-		make([]byte, 8*numReStates),
-		make([]byte, 0),
-		make([]byte, 0),
-		make([]byte, 0),
-	}
-
-	// Set send or receive
-	if km.sendOrRecv {
-		s.SendOrRecv[0] = 0xFF
-	} else {
-		s.SendOrRecv[0] = 0x00
-	}
-
-	// Convert all internal uints to bytes
-	binary.BigEndian.PutUint64(s.State, *km.sendState)
-	binary.BigEndian.PutUint16(s.TTL, km.ttl)
-	binary.BigEndian.PutUint32(s.NumKeys, km.numKeys)
-	binary.BigEndian.PutUint16(s.NumReKeys, km.numReKeys)
-	for i := 0; i < int(numStates); i++ {
-		binary.BigEndian.PutUint64(
-			s.RecvKeyState[i*8:(i+1)*8],
-			*km.recvKeysState[i])
-	}
-	for i := 0; i < int(numReStates); i++ {
-		binary.BigEndian.PutUint64(
-			s.RecvReKeyState[i*8:(i+1)*8],
-			*km.recvReKeysState[i])
-	}
-
-	// GobEncode baseKey
-	keyBytes, err := km.baseKey.GobEncode()
-
-	if err != nil {
-		return nil, err
-	}
-
-	// Add baseKey to struct
-	s.BaseKey = append(s.BaseKey, keyBytes...)
-
-	// GobEncode privKey
-	if km.privKey != nil {
-		keyBytes, err = km.privKey.GobEncode()
-
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	// Add privKey to struct
-	s.PrivKey = append(s.BaseKey, keyBytes...)
-
-	// GobEncode pubKey
-	if km.pubKey != nil {
-		keyBytes, err = km.pubKey.GobEncode()
-
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	// Add pubKey to struct
-	s.PubKey = append(s.BaseKey, keyBytes...)
-
-	var buf bytes.Buffer
-
-	// Create new encoder that will transmit the buffer
-	enc := gob.NewEncoder(&buf)
-
-	// Transmit the data
-	err = enc.Encode(s)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
-}
-
-// GobDecode bytes into a new Key Manager
-// It can be used to get Key Managers from the
-// store session file
-// GenerateKeys should then be run so that all
-// key maps are restored properly
-func (km *KeyManager) GobDecode(in []byte) error {
-	// Anonymous structure that flattens nested structures
-	s := struct {
-		Partner        []byte
-		SendOrRecv     []byte
-		State          []byte
-		TTL            []byte
-		NumKeys        []byte
-		NumReKeys      []byte
-		RecvKeyState   []byte
-		RecvReKeyState []byte
-		BaseKey        []byte
-		PrivKey        []byte
-		PubKey         []byte
-	}{
-		make([]byte, 32),
-		make([]byte, 1),
-		make([]byte, 8),
-		make([]byte, 2),
-		make([]byte, 4),
-		make([]byte, 2),
-		make([]byte, 8*numStates),
-		make([]byte, 8*numReStates),
-		[]byte{},
-		[]byte{},
-		[]byte{},
-	}
-
-	var buf bytes.Buffer
-
-	// Write bytes to the buffer
-	buf.Write(in)
-
-	// Create new decoder that reads from the buffer
-	dec := gob.NewDecoder(&buf)
-
-	// Receive and decode data
-	err := dec.Decode(&s)
-
-	if err != nil {
-		return err
-	}
-
-	partner, err := id.Unmarshal(s.Partner)
-	if err != nil {
-		return err
-	}
-	km.partner = partner
-
-	// Convert decoded bytes and put into key manager structure
-	km.baseKey = new(cyclic.Int)
-	err = km.baseKey.GobDecode(s.BaseKey)
-
-	if err != nil {
-		return err
-	}
-
-	km.privKey = new(cyclic.Int)
-	err = km.privKey.GobDecode(s.PrivKey)
-
-	if err != nil {
-		return err
-	}
-
-	km.pubKey = new(cyclic.Int)
-	err = km.pubKey.GobDecode(s.PubKey)
-
-	if err != nil {
-		return err
-	}
-
-	if s.SendOrRecv[0] == 0xFF {
-		km.sendOrRecv = true
-	} else {
-		km.sendOrRecv = false
-	}
-
-	km.sendState = new(uint64)
-	*km.sendState = binary.BigEndian.Uint64(s.State)
-	km.ttl = binary.BigEndian.Uint16(s.TTL)
-	km.numKeys = binary.BigEndian.Uint32(s.NumKeys)
-	km.numReKeys = binary.BigEndian.Uint16(s.NumReKeys)
-	for i := 0; i < int(numStates); i++ {
-		km.recvKeysState[i] = new(uint64)
-		*km.recvKeysState[i] = binary.BigEndian.Uint64(
-			s.RecvKeyState[i*8 : (i+1)*8])
-	}
-	for i := 0; i < int(numReStates); i++ {
-		km.recvReKeysState[i] = new(uint64)
-		*km.recvReKeysState[i] = binary.BigEndian.Uint64(
-			s.RecvReKeyState[i*8 : (i+1)*8])
-	}
-
-	return nil
-}
diff --git a/keyStore/keyManager_test.go b/keyStore/keyManager_test.go
deleted file mode 100644
index 3851e03e3e978539768f1d759ada70c3017b5ca0..0000000000000000000000000000000000000000
--- a/keyStore/keyManager_test.go
+++ /dev/null
@@ -1,671 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/base64"
-	"encoding/gob"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/primitives/id"
-	"testing"
-)
-
-// initGroup sets up the cryptographic constants for cMix
-func initGroup() *cyclic.Group {
-
-	base := 16
-
-	pString := "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
-		"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
-		"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
-		"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
-		"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
-		"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
-		"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
-		"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B"
-
-	gString := "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
-		"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
-		"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
-		"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
-		"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
-		"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
-		"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
-		"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7"
-
-	p := large.NewIntFromString(pString, base)
-	g := large.NewIntFromString(gString, base)
-
-	grp := cyclic.NewGroup(p, g)
-
-	return grp
-}
-
-// Test creation of KeyManager
-func TestKeyManager_New(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, nil, nil,
-		partner, true, 12, 10, 10)
-
-	if km == nil {
-		t.Errorf("NewManager returned nil")
-	}
-}
-
-// Test KeyManager base key getter
-func TestKeyManager_GetBaseKey(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	result := km.GetBaseKey()
-
-	if result.Cmp(baseKey) != 0 {
-		t.Errorf("GetBaseKey returned wrong value, "+
-			"expected: %s, got: %s",
-			privKey.Text(10), result.Text(10))
-	}
-}
-
-// Test KeyManager private key getter
-func TestKeyManager_GetPrivKey(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	result := km.GetPrivKey()
-
-	if result.Cmp(privKey) != 0 {
-		t.Errorf("GetPrivKey returned wrong value, "+
-			"expected: %s, got: %s",
-			privKey.Text(10), result.Text(10))
-	}
-}
-
-// Test KeyManager public key getter
-func TestKeyManager_GetPubKey(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	result := km.GetPubKey()
-
-	if result.Cmp(pubKey) != 0 {
-		t.Errorf("GetPubKey returned wrong value, "+
-			"expected: %s, got: %s",
-			pubKey.Text(10), result.Text(10))
-	}
-}
-
-// Test KeyManager partner getter
-func TestKeyManager_GetPartner(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	result := km.GetPartner()
-
-	if *result != *partner {
-		t.Errorf("GetPartner returned wrong value, "+
-			"expected: %s, got: %s",
-			*partner, *result)
-	}
-}
-
-// Test rekey trigger
-func TestKeyManager_Rekey(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, nil, nil,
-		partner, true, 12, 10, 10)
-
-	var action Action
-	for i := 0; i < 9; i++ {
-		action = km.updateState(false)
-		if action != None {
-			t.Errorf("Expected 'None' action, got %s instead",
-				action)
-		}
-	}
-
-	action = km.updateState(false)
-	if action != Rekey {
-		t.Errorf("Expected 'Rekey' action, got %s instead",
-			action)
-	}
-}
-
-// Test purge trigger
-func TestKeyManager_Purge(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, nil, nil,
-		partner, true, 12, 10, 10)
-
-	var action Action
-	for i := 0; i < 9; i++ {
-		action = km.updateState(true)
-		if action != None {
-			t.Errorf("Expected 'None' action, got %s instead",
-				action)
-		}
-	}
-
-	action = km.updateState(true)
-	if action != Purge {
-		t.Errorf("Expected 'Purge' action, got %s instead",
-			action)
-	}
-
-	// Confirm that state is now deleted
-	action = km.updateState(false)
-	if action != Deleted {
-		t.Errorf("Expected 'Deleted' action, got %s instead",
-			action)
-	}
-}
-
-// Test receive state update
-func TestKeyManager_UpdateRecvState(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	km := NewManager(baseKey, nil, nil,
-		partner, false, 12, 10, 10)
-
-	expectedVal := uint64(0x0010000001000008)
-	// Mark some keys as used and confirm expected value
-	km.updateRecvState(false, 3)
-	km.updateRecvState(false, 24)
-	km.updateRecvState(false, 52)
-
-	if *km.recvKeysState[0] != expectedVal {
-		t.Errorf("UpdateRecvState failed for Key, expected"+
-			" %d, got %d", expectedVal, *km.recvKeysState[0])
-	}
-
-	expectedVal = uint64(0x0000080000040020)
-	// Mark some Rekeys as used and confirm expected value
-	km.updateRecvState(true, 5)
-	km.updateRecvState(true, 18)
-	km.updateRecvState(true, 43)
-
-	if *km.recvReKeysState[0] != expectedVal {
-		t.Errorf("UpdateRecvState failed for ReKey, expected"+
-			" %d, got %d", expectedVal, *km.recvReKeysState[0])
-	}
-}
-
-// Test KeyManager Key Generation
-func TestKeyManager_GenerateKeys(t *testing.T) {
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	ks := NewStore()
-	kmSend := NewManager(baseKey, nil, nil,
-		partner, true, 12, 10, 10)
-
-	// Generate Send Keys
-	kmSend.GenerateKeys(grp, userID)
-	ks.AddSendManager(kmSend)
-
-	kmRecv := NewManager(baseKey, nil, nil,
-		partner, false, 12, 10, 10)
-
-	// Generate Receive Keys
-	e2ekeys := kmRecv.GenerateKeys(grp, userID)
-	ks.AddRecvManager(kmRecv)
-	ks.AddReceiveKeysByFingerprint(e2ekeys)
-
-	// Confirm Send KeyManager is stored correctly in KeyStore map
-	retKM := ks.GetSendManager(partner)
-	if retKM != kmSend {
-		t.Errorf("KeyManager stored in KeyStore is not the same")
-	}
-
-	// Confirm keys can be correctly pop'ed from KeyManager
-	actual, action := retKM.PopKey()
-
-	if actual == nil {
-		t.Errorf("KeyManager returned nil when poping key")
-	} else if action != None {
-		t.Errorf("Expected 'None' action, got %s instead",
-			action)
-	}
-
-	actual, action = retKM.PopRekey()
-
-	if actual == nil {
-		t.Errorf("KeyManager returned nil when poping rekey")
-	} else if action != None {
-		t.Errorf("Expected 'None' action, got %s instead",
-			action)
-	}
-
-	// Confirm Receive Keys can be obtained from KeyStore
-	actual = ks.GetRecvKey(kmRecv.recvKeysFingerprint[4])
-	if actual == nil {
-		t.Errorf("ReceptionKeys Map returned nil for Key")
-	}
-
-	actual = ks.GetRecvKey(e2ekeys[8].KeyFingerprint())
-
-	if actual == nil {
-		t.Errorf("ReceptionKeys Map returned nil for ReKey")
-	}
-}
-
-// Test KeyManager destroy
-func TestKeyManager_Destroy(t *testing.T) {
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	ks := NewStore()
-	km := NewManager(baseKey, nil, nil,
-		partner, true, 12, 10, 10)
-
-	// Generate Send Keys
-	km.GenerateKeys(grp, userID)
-	ks.AddSendManager(km)
-
-	km2 := NewManager(baseKey, nil, nil,
-		partner, false, 12, 10, 10)
-
-	// Generate Receive Keys
-	e2ekeys := km2.GenerateKeys(grp, userID)
-	// TODO add ks keys here
-	ks.AddRecvManager(km2)
-	ks.AddReceiveKeysByFingerprint(e2ekeys)
-
-	// Confirm Send KeyManager is stored correctly in KeyStore map
-	retKM := ks.GetSendManager(partner)
-	if retKM != km {
-		t.Errorf("KeyManager stored in KeyStore is not the same")
-	}
-
-	// Confirm keys can be correctly pop'ed from KeyManager
-	actual, action := retKM.PopKey()
-
-	if actual == nil {
-		t.Errorf("KeyManager returned nil when poping key")
-	} else if action != None {
-		t.Errorf("Expected 'None' action, got %s instead",
-			action)
-	}
-
-	actual, action = retKM.PopRekey()
-
-	if actual == nil {
-		t.Errorf("KeyManager returned nil when poping rekey")
-	} else if action != None {
-		t.Errorf("Expected 'None' action, got %s instead",
-			action)
-	}
-
-	// Confirm Receive Keys can be obtained from KeyStore
-	actual = ks.GetRecvKey(km2.recvKeysFingerprint[4])
-
-	if actual == nil {
-		t.Errorf("ReceptionKeys Map returned nil for Key")
-	}
-
-	actual = ks.GetRecvKey(km2.recvReKeysFingerprint[8])
-	if actual == nil {
-		t.Errorf("ReceptionKeys Map returned nil for ReKey")
-	}
-
-	// Destroy KeyManager and confirm KeyManager is gone from map
-	km.Destroy(ks)
-
-	retKM = ks.GetSendManager(partner)
-	if retKM != nil {
-		t.Errorf("KeyManager was not properly removed from KeyStore")
-	}
-
-}
-
-// Test GOB Encode/Decode of KeyManager
-// and do a simple comparison after
-func TestKeyManager_GobSimple(t *testing.T) {
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-
-	var byteBuf bytes.Buffer
-
-	enc := gob.NewEncoder(&byteBuf)
-	dec := gob.NewDecoder(&byteBuf)
-
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	err := enc.Encode(km)
-
-	if err != nil {
-		t.Errorf("Error GOB Encoding KeyManager: %s", err)
-	}
-
-	outKm := &KeyManager{}
-
-	err = dec.Decode(&outKm)
-
-	if err != nil {
-		t.Errorf("Error GOB Decoding KeyManager: %s", err)
-	}
-
-	if km.baseKey.Cmp(outKm.baseKey) != 0 {
-		t.Errorf("GobEncoder/GobDecoder failed on BaseKey, "+
-			"Expected: %v; Recieved: %v ",
-			km.baseKey.TextVerbose(10, 12),
-			outKm.baseKey.TextVerbose(10, 12))
-	}
-
-	if *km.partner != *outKm.partner {
-		t.Errorf("GobEncoder/GobDecoder failed on Partner, "+
-			"Expected: %v; Recieved: %v ",
-			*km.partner,
-			*outKm.partner)
-	}
-
-	if *km.sendState != *outKm.sendState {
-		t.Errorf("GobEncoder/GobDecoder failed on State, "+
-			"Expected: %v; Recieved: %v ",
-			*km.sendState,
-			*outKm.sendState)
-	}
-
-	if km.ttl != outKm.ttl {
-		t.Errorf("GobEncoder/GobDecoder failed on TTL, "+
-			"Expected: %v; Recieved: %v ",
-			km.ttl,
-			outKm.ttl)
-	}
-
-	if km.numKeys != outKm.numKeys {
-		t.Errorf("GobEncoder/GobDecoder failed on NumKeys, "+
-			"Expected: %v; Recieved: %v ",
-			km.numKeys,
-			outKm.numKeys)
-	}
-
-	if km.numReKeys != outKm.numReKeys {
-		t.Errorf("GobEncoder/GobDecoder failed on NumReKeys, "+
-			"Expected: %v; Recieved: %v ",
-			km.numReKeys,
-			outKm.numReKeys)
-	}
-
-	for i := 0; i < int(numStates); i++ {
-		if *km.recvKeysState[i] != *outKm.recvKeysState[i] {
-			t.Errorf("GobEncoder/GobDecoder failed on RecvKeysState[%d], "+
-				"Expected: %v; Recieved: %v ",
-				i,
-				*km.recvKeysState[i],
-				*outKm.recvKeysState[i])
-		}
-	}
-
-	for i := 0; i < int(numReStates); i++ {
-		if *km.recvReKeysState[i] != *outKm.recvReKeysState[i] {
-			t.Errorf("GobEncoder/GobDecoder failed on RecvReKeysState[%d], "+
-				"Expected: %v; Recieved: %v ",
-				i,
-				*km.recvReKeysState[i],
-				*outKm.recvReKeysState[i])
-		}
-	}
-}
-
-// Tests that GobDecode() for Key Manager throws an error for a
-// malformed byte array
-func TestKeyManager_GobDecodeError(t *testing.T) {
-	km := KeyManager{}
-	err := km.GobDecode([]byte{})
-
-	if err.Error() != "EOF" {
-		t.Errorf("GobDecode() did not produce the expected error\n\treceived: %v"+
-			"\n\texpected: %v", err, errors.New("EOF"))
-	}
-}
-
-// Test that key maps are reconstructed correctly after
-// Key Manager GOB Encode/Decode
-func TestKeyManager_Gob(t *testing.T) {
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	ks := NewStore()
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	// Generate Send Keys
-	km.GenerateKeys(grp, userID)
-	ks.AddSendManager(km)
-
-	km2 := NewManager(baseKey, privKey, pubKey,
-		partner, false, 12, 10, 10)
-
-	// Generate Receive Keys
-	e2ekeys := km2.GenerateKeys(grp, userID)
-	ks.AddRecvManager(km2)
-	ks.AddReceiveKeysByFingerprint(e2ekeys)
-
-	// Generate keys here to have a way to compare after
-	sendKeys := e2e.DeriveKeys(grp, baseKey, userID, uint(km.numKeys))
-	sendReKeys := e2e.DeriveEmergencyKeys(grp, baseKey, userID, uint(km.numReKeys))
-	recvKeys := e2e.DeriveKeys(grp, baseKey, partner, uint(km.numKeys))
-	recvReKeys := e2e.DeriveEmergencyKeys(grp, baseKey, partner, uint(km.numReKeys))
-
-	var expectedKeyMap = make(map[string]bool)
-
-	for _, key := range sendKeys {
-		expectedKeyMap[base64.StdEncoding.EncodeToString(key.Bytes())] = true
-	}
-
-	for _, key := range sendReKeys {
-		expectedKeyMap[base64.StdEncoding.EncodeToString(key.Bytes())] = true
-	}
-
-	for _, key := range recvKeys {
-		expectedKeyMap[base64.StdEncoding.EncodeToString(key.Bytes())] = true
-	}
-
-	for _, key := range recvReKeys {
-		expectedKeyMap[base64.StdEncoding.EncodeToString(key.Bytes())] = true
-	}
-
-	// Use some send keys and mark on expected map as used
-	retKM := ks.GetSendManager(partner)
-	if retKM != km {
-		t.Errorf("KeyManager stored in KeyStore is not the same")
-	}
-	key, _ := retKM.PopKey()
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	key, _ = retKM.PopKey()
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	key, _ = retKM.PopKey()
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	usedSendKeys := 3
-
-	key, _ = retKM.PopRekey()
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	key, _ = retKM.PopRekey()
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	usedSendReKeys := 2
-
-	// Use some receive keys and mark on expected map as used
-	key = ks.GetRecvKey(km2.recvKeysFingerprint[3])
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	key = ks.GetRecvKey(km2.recvKeysFingerprint[8])
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	key = ks.GetRecvKey(km2.recvKeysFingerprint[6])
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	key = ks.GetRecvKey(km2.recvKeysFingerprint[1])
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	usedRecvKeys := 4
-
-	key = ks.GetRecvKey(km2.recvReKeysFingerprint[4])
-	expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] = false
-	usedRecvReKeys := 1
-
-	// Now GOB Encode Key Manager
-	var byteBuf bytes.Buffer
-
-	enc := gob.NewEncoder(&byteBuf)
-	dec := gob.NewDecoder(&byteBuf)
-
-	err := enc.Encode(km)
-
-	if err != nil {
-		t.Errorf("Error GOB Encoding KeyManager: %s", err)
-	}
-
-	// Destroy KeyManager and confirm KeyManager is gone from map
-	km.Destroy(ks)
-
-	retKM = ks.GetSendManager(partner)
-	if retKM != nil {
-		t.Errorf("KeyManager was not properly removed from KeyStore")
-	}
-
-	// GOB Decode Key Manager
-	sendKm := &KeyManager{}
-	err = dec.Decode(&sendKm)
-
-	if err != nil {
-		t.Errorf("Error GOB Decoding KeyManager: %s", err)
-	}
-
-	err = enc.Encode(km2)
-
-	if err != nil {
-		t.Errorf("Error GOB Encoding KeyManager2: %s", err)
-	}
-
-	// Destroy Key Manager (and maps) and confirm no more receive keys exist
-	km2.Destroy(ks)
-
-	// GOB Decode Key Manager2
-	outKm2 := &KeyManager{}
-	err = dec.Decode(&outKm2)
-
-	if err != nil {
-		t.Errorf("Error GOB Decoding KeyManager2: %s", err)
-	}
-
-	// Generate Keys from decoded Key Managers
-	e2ekeys = sendKm.GenerateKeys(grp, userID)
-	ks.AddSendManager(sendKm)
-	//ks.AddReceiveKeysByFingerprint(e2ekeys)
-
-	e2ekeys = outKm2.GenerateKeys(grp, userID)
-	ks.AddRecvManager(km)
-	ks.AddReceiveKeysByFingerprint(e2ekeys)
-
-	// Confirm maps are the same as before delete
-
-	// First, check that len of send Stacks matches expected
-	if sendKm.sendKeys.keys.Len() != int(sendKm.numKeys)-usedSendKeys {
-		t.Errorf("SendKeys Stack contains more keys than expected after decode."+
-			" Expected: %d, Got: %d",
-			int(sendKm.numKeys)-usedSendKeys,
-			sendKm.sendKeys.keys.Len())
-	}
-
-	if sendKm.sendReKeys.keys.Len() != int(sendKm.numReKeys)-usedSendReKeys {
-		t.Errorf("SendReKeys Stack contains more keys than expected after decode."+
-			" Expected: %d, Got: %d",
-			int(sendKm.numReKeys)-usedSendReKeys,
-			sendKm.sendReKeys.keys.Len())
-	}
-
-	// Now confirm that all send keys are in the expected map
-	retKM = ks.GetSendManager(partner)
-	for i := 0; i < int(sendKm.numKeys)-usedSendKeys; i++ {
-		key, _ := retKM.PopKey()
-		if expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] != true {
-			t.Errorf("SendKey %v was used or didn't exist before",
-				key.KeyFingerprint())
-		}
-	}
-
-	for i := 0; i < int(sendKm.numReKeys)-usedSendReKeys; i++ {
-		key, _ := retKM.PopRekey()
-		if expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] != true {
-			t.Errorf("SendReKey %v was used or didn't exist before",
-				key.KeyFingerprint())
-		}
-	}
-
-	// Check that len of fingerprint lists matches expected
-	if len(outKm2.recvKeysFingerprint) != int(outKm2.numKeys)-usedRecvKeys {
-		t.Errorf("ReceiveKeys list contains more keys than expected after decode."+
-			" Expected: %d, Got: %d",
-			int(outKm2.numKeys)-usedRecvKeys,
-			len(outKm2.recvKeysFingerprint))
-	}
-
-	if len(outKm2.recvReKeysFingerprint) != int(outKm2.numReKeys)-usedRecvReKeys {
-		t.Errorf("ReceiveReKeys list contains more keys than expected after decode."+
-			" Expected: %d, Got: %d",
-			int(outKm2.numReKeys)-usedRecvReKeys,
-			len(outKm2.recvReKeysFingerprint))
-	}
-
-	// Now confirm that all receiving keys are in the expected map
-	for i := 0; i < int(outKm2.numKeys)-usedRecvKeys; i++ {
-		key := ks.GetRecvKey(outKm2.recvKeysFingerprint[i])
-		if expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] != true {
-			t.Errorf("ReceiveKey %v was used or didn't exist before",
-				key.KeyFingerprint())
-		}
-	}
-
-	for i := 0; i < int(outKm2.numReKeys)-usedRecvReKeys; i++ {
-		key := ks.GetRecvKey(outKm2.recvReKeysFingerprint[i])
-		if expectedKeyMap[base64.StdEncoding.EncodeToString(key.key.Bytes())] != true {
-			t.Errorf("ReceiveReKey %v was used or didn't exist before",
-				key.KeyFingerprint())
-		}
-	}
-}
diff --git a/keyStore/keyParams.go b/keyStore/keyParams.go
deleted file mode 100644
index 7af8bae62e12c51a7ca573d99fc39dfd3081551b..0000000000000000000000000000000000000000
--- a/keyStore/keyParams.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package keyStore
-
-import "gitlab.com/elixxir/crypto/e2e"
-
-// DEFAULT KEY GENERATION PARAMETERS
-// Hardcoded limits for keys
-// With 16 receiving states we can hold
-// 16*64=1024 dirty bits for receiving keys
-// With that limit, and setting maxKeys to 800,
-// we need a Threshold of 224, and a scalar
-// smaller than 1.28 to ensure we never generate
-// more than 1024 keys
-// With 1 receiving states for ReKeys we can hold
-// 64 Rekeys
-const (
-	numStates   uint16  = 16
-	numReStates uint16  = 1
-	minKeys     uint16  = 500
-	maxKeys     uint16  = 800
-	ttlScalar   float64 = 1.2 // generate 20% extra keys
-	threshold   uint16  = 224
-	numReKeys   uint16  = 64
-)
-
-type KeyParams struct {
-	MinKeys   uint16
-	MaxKeys   uint16
-	NumRekeys uint16
-	e2e.TTLParams
-}
diff --git a/keyStore/keyStack.go b/keyStore/keyStack.go
deleted file mode 100644
index 43bf3ae0d3f67dc5d6b96e8a63e74dc6fc17fc7f..0000000000000000000000000000000000000000
--- a/keyStore/keyStack.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package keyStore
-
-import (
-	"github.com/golang-collections/collections/stack"
-	"gitlab.com/elixxir/client/globals"
-	"sync"
-)
-
-// KeyStack contains a stack of E2E keys (or rekeys)
-// Also has a mutex for access control
-type KeyStack struct {
-	// List of Keys used for sending
-	// When a key is used it is deleted (pop'ed)
-	keys *stack.Stack
-	// Lock
-	sync.Mutex
-}
-
-// Create a new KeyStack
-// It creates the internal stack.Stack object
-func NewKeyStack() *KeyStack {
-	ks := new(KeyStack)
-	ks.keys = stack.New()
-	return ks
-}
-
-// Push an E2EKey into the stack
-func (ks *KeyStack) Push(key *E2EKey) {
-	ks.keys.Push(key)
-}
-
-// Returns the top key on the stack
-// Internally holds the lock when
-// running Pop on the internal stack.Stack object
-func (ks *KeyStack) Pop() *E2EKey {
-	var key *E2EKey
-
-	// Get the key
-	ks.Lock()
-	keyFace := ks.keys.Pop()
-	ks.Unlock()
-
-	// Check if the key exists and panic otherwise
-	if keyFace == nil {
-		globals.Log.WARN.Printf("E2E key stack is empty!")
-		key = nil
-	} else {
-		key = keyFace.(*E2EKey)
-	}
-
-	return key
-}
-
-// Deletes all keys from stack, i.e., pops all
-// Internally holds the lock
-func (ks *KeyStack) Delete() {
-	ks.Lock()
-	defer ks.Unlock()
-	length := ks.keys.Len()
-	for i := 0; i < length; i++ {
-		ks.keys.Pop()
-	}
-}
diff --git a/keyStore/keyStack_test.go b/keyStore/keyStack_test.go
deleted file mode 100644
index caecf8635b716de4ae7bf6639d8ba8264f34b35c..0000000000000000000000000000000000000000
--- a/keyStore/keyStack_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package keyStore
-
-import (
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/large"
-	"testing"
-	"time"
-)
-
-// Helper function to compare E2E Keys
-func E2EKeyCmp(a, b *E2EKey) bool {
-	if a.GetManager() != b.GetManager() {
-		return false
-	}
-	if a.GetOuterType() != b.GetOuterType() {
-		return false
-	}
-	if a.GetKey().Cmp(b.GetKey()) != 0 {
-		return false
-	}
-	return true
-}
-
-// Test KeyStack creation and push/pop
-func TestKeyStack(t *testing.T) {
-	ks := NewKeyStack()
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	expectedKeys := make([]*E2EKey, 100)
-
-	for i := 0; i < 100; i++ {
-		key := new(E2EKey)
-		key.outer = parse.E2E
-		key.key = grp.NewInt(int64(i + 2))
-		key.manager = nil
-		expectedKeys[99-i] = key
-		ks.Push(key)
-	}
-
-	for i := 0; i < 100; i++ {
-		actual := ks.Pop()
-		if !E2EKeyCmp(actual, expectedKeys[i]) {
-			t.Errorf("Pop'd key doesn't match with expected")
-		}
-	}
-}
-
-// Test that KeyStack panics on pop if empty
-func TestKeyStack_Panic(t *testing.T) {
-	ks := NewKeyStack()
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	expectedKeys := make([]*E2EKey, 10)
-
-	for i := 0; i < 10; i++ {
-		key := new(E2EKey)
-		key.outer = parse.E2E
-		key.key = grp.NewInt(int64(i + 2))
-		key.manager = nil
-		expectedKeys[9-i] = key
-		ks.Push(key)
-	}
-
-	defer func() {
-		if r := recover(); r == nil {
-			t.Errorf("Pop should panic when stack is empty")
-		}
-	}()
-
-	for i := 0; i < 11; i++ {
-		actual := ks.Pop()
-		if !E2EKeyCmp(actual, expectedKeys[i]) {
-			t.Errorf("Pop'd key doesn't match with expected")
-		}
-	}
-}
-
-// Test that delete correctly empties stack
-func TestKeyStack_Delete(t *testing.T) {
-	ks := NewKeyStack()
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	expectedKeys := make([]*E2EKey, 100)
-
-	for i := 0; i < 100; i++ {
-		key := new(E2EKey)
-		key.outer = parse.E2E
-		key.key = grp.NewInt(int64(i + 2))
-		key.manager = nil
-		expectedKeys[99-i] = key
-		ks.Push(key)
-	}
-
-	for i := 0; i < 50; i++ {
-		actual := ks.Pop()
-		if !E2EKeyCmp(actual, expectedKeys[i]) {
-			t.Errorf("Pop'd key doesn't match with expected")
-		}
-	}
-
-	ks.Delete()
-
-	k4 := ks.Pop()
-	if k4 != nil {
-		t.Errorf("Pop should return nil when stack is empty")
-	}
-}
-
-// Test concurrent access
-func TestKeyStack_Concurrent(t *testing.T) {
-	ks := NewKeyStack()
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	expectedKeys := make([]*E2EKey, 100)
-
-	for i := 0; i < 100; i++ {
-		key := new(E2EKey)
-		key.outer = parse.E2E
-		key.key = grp.NewInt(int64(i + 2))
-		key.manager = nil
-		expectedKeys[99-i] = key
-		ks.Push(key)
-	}
-
-	for i := 0; i < 100; i++ {
-		go func() {
-			ks.Pop()
-		}()
-	}
-
-	// wait for goroutines
-	time.Sleep(500 * time.Millisecond)
-
-	k4 := ks.Pop()
-	if k4 != nil {
-		t.Errorf("Pop should return nil when stack is empty")
-	}
-}
diff --git a/keyStore/keyStore.go b/keyStore/keyStore.go
deleted file mode 100644
index 8256cbde527b7025ea1842974b6c62df2b9de427..0000000000000000000000000000000000000000
--- a/keyStore/keyStore.go
+++ /dev/null
@@ -1,353 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/gob"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"sync"
-)
-
-// Local types in order to implement functions that
-// return real types instead of interfaces
-type keyManMap sync.Map
-type inKeyMap sync.Map
-
-// Stores a KeyManager entry for given user
-func (m *keyManMap) Store(user *id.ID, km *KeyManager) {
-	(*sync.Map)(m).Store(*user, km)
-}
-
-// Loads a KeyManager entry for given user
-func (m *keyManMap) Load(user *id.ID) *KeyManager {
-	val, ok := (*sync.Map)(m).Load(*user)
-	if !ok {
-		return nil
-	} else {
-		return val.(*KeyManager)
-	}
-}
-
-// Deletes a KeyManager entry for given user
-func (m *keyManMap) Delete(user *id.ID) {
-	(*sync.Map)(m).Delete(*user)
-}
-
-// Internal helper function to get a list of all values
-// contained in a KeyManMap
-func (m *keyManMap) values() []*KeyManager {
-	valueList := make([]*KeyManager, 0)
-	(*sync.Map)(m).Range(func(key, value interface{}) bool {
-		valueList = append(valueList, value.(*KeyManager))
-		return true
-	})
-	return valueList
-}
-
-// Internal helper function to get a list of all keys
-// contained in a KeyManMap
-func (m *keyManMap) keys() []id.ID {
-	keyList := make([]id.ID, 0)
-	(*sync.Map)(m).Range(func(key, value interface{}) bool {
-		keyList = append(keyList, key.(id.ID))
-		return true
-	})
-	return keyList
-}
-
-// Stores an *E2EKey for given fingerprint
-func (m *inKeyMap) Store(fingerprint format.Fingerprint, key *E2EKey) {
-	(*sync.Map)(m).Store(fingerprint, key)
-}
-
-// Pops key for given fingerprint, i.e,
-// returns and deletes it from the map
-// Atomically updates Key Manager Receiving state
-// Returns nil if not found
-func (m *inKeyMap) Pop(fingerprint format.Fingerprint) *E2EKey {
-	val, ok := (*sync.Map)(m).Load(fingerprint)
-
-	var key *E2EKey
-	if !ok {
-		return nil
-	} else {
-		key = val.(*E2EKey)
-	}
-	// Delete key from map
-	m.Delete(fingerprint)
-	// Update Key Manager Receiving State
-	key.GetManager().updateRecvState(
-		key.GetOuterType() == parse.Rekey,
-		key.keyNum)
-	return key
-}
-
-// Deletes a key for given fingerprint
-func (m *inKeyMap) Delete(fingerprint format.Fingerprint) {
-	(*sync.Map)(m).Delete(fingerprint)
-}
-
-// Deletes keys from a given list of fingerprints
-func (m *inKeyMap) DeleteList(fingerprints []format.Fingerprint) {
-	for _, fp := range fingerprints {
-		m.Delete(fp)
-	}
-}
-
-// KeyStore contains the E2E key
-// and Key Managers maps
-// Send keys are obtained directly from the Key Manager
-// which is looked up in the sendKeyManagers map
-// Receiving keys are lookup up by fingerprint on
-// receptionKeys map
-// RecvKeyManagers map is needed in order to maintain
-// active Key Managers when the session is stored/loaded
-// It is not a sync.map since it won't be accessed
-// very often
-// It still contains a lock for multithreaded access
-type KeyStore struct {
-	// Key generation parameters
-	params *KeyParams
-
-	// Transmission Keys map
-	// Maps id.ID to *KeyManager
-	sendKeyManagers *keyManMap
-
-	// Reception Keys map
-	// Maps format.Fingerprint to *E2EKey
-	receptionKeys *inKeyMap
-
-	// Reception Key Managers map
-	recvKeyManagers map[id.ID]*ReceptionKeyManagerBuffer
-
-	lock sync.Mutex
-}
-
-func NewStore() *KeyStore {
-	ks := new(KeyStore)
-	ks.params = &KeyParams{
-		MinKeys:   minKeys,
-		MaxKeys:   maxKeys,
-		NumRekeys: numReKeys,
-		TTLParams: e2e.TTLParams{
-			TTLScalar:  ttlScalar,
-			MinNumKeys: threshold,
-		},
-	}
-	ks.sendKeyManagers = new(keyManMap)
-	ks.receptionKeys = new(inKeyMap)
-	ks.recvKeyManagers = make(map[id.ID]*ReceptionKeyManagerBuffer)
-	return ks
-}
-
-func (ks *KeyStore) DeleteContactKeys(id *id.ID) error {
-	ks.lock.Lock()
-	defer ks.lock.Unlock()
-
-	rkmb, ok := ks.recvKeyManagers[*id]
-	if ok {
-		for _, manager := range rkmb.managers {
-			if manager != nil {
-				keys := manager.recvKeysFingerprint
-				rekeys := manager.recvReKeysFingerprint
-				ks.receptionKeys.DeleteList(append(keys, rekeys...))
-			}
-		}
-	} else {
-		return errors.Errorf("User with id %+v not in map of key managers", id)
-	}
-	delete(ks.recvKeyManagers, *id)
-	ks.sendKeyManagers.Delete(id)
-	return nil
-}
-
-// Get Key generation parameters from KeyStore
-func (ks *KeyStore) GetKeyParams() *KeyParams {
-	return ks.params
-}
-
-// Add a Send KeyManager to respective map in KeyStore
-func (ks *KeyStore) AddSendManager(km *KeyManager) {
-	ks.sendKeyManagers.Store(km.GetPartner(), km)
-}
-
-// Get a Send KeyManager from respective map in KeyStore
-// based on partner ID
-func (ks *KeyStore) GetSendManager(partner *id.ID) *KeyManager {
-	return ks.sendKeyManagers.Load(partner)
-}
-
-// GetPartners returns the list of partners we have keys for
-func (ks *KeyStore) GetPartners() []id.ID {
-	return ks.sendKeyManagers.keys()
-}
-
-// Delete a Send KeyManager from respective map in KeyStore
-// based on partner ID
-func (ks *KeyStore) DeleteSendManager(partner *id.ID) {
-	ks.sendKeyManagers.Delete(partner)
-}
-
-// Add a Receiving E2EKey to the correct KeyStore map
-// based on its fingerprint
-func (ks *KeyStore) AddRecvKey(fingerprint format.Fingerprint,
-	key *E2EKey) {
-	ks.receptionKeys.Store(fingerprint, key)
-}
-
-// Get the Receiving Key stored in correct KeyStore map
-// based on the given fingerprint
-func (ks *KeyStore) GetRecvKey(fingerprint format.Fingerprint) *E2EKey {
-	return ks.receptionKeys.Pop(fingerprint)
-}
-
-// Add a Receive KeyManager to respective map in KeyStore
-func (ks *KeyStore) AddRecvManager(km *KeyManager) {
-	ks.lock.Lock()
-	defer ks.lock.Unlock()
-
-	//ks.recvKeyManagers = km
-	keys, ok := ks.recvKeyManagers[*km.partner]
-
-	if ok {
-		toBeDeleted := keys.push(km)
-		ks.DeleteReceiveKeysByFingerprint(toBeDeleted)
-	} else {
-		newBuffer := NewReceptionKeyManagerBuffer()
-		newBuffer.push(km)
-		ks.recvKeyManagers[*km.partner] = newBuffer
-	}
-}
-
-// Gets the Key manager at the current location on the ReceptionKeyManagerBuffer
-// based on partner ID
-func (ks *KeyStore) GetRecvManager(partner *id.ID) *KeyManager {
-	ks.lock.Lock()
-	defer ks.lock.Unlock()
-	return ks.recvKeyManagers[*partner].getCurrentReceptionKeyManager()
-}
-
-// Delete a Receive KeyManager based on partner ID from respective map in KeyStore
-func (ks *KeyStore) DeleteRecvManager(partner *id.ID) {
-	ks.lock.Lock()
-	defer ks.lock.Unlock()
-	delete(ks.recvKeyManagers, *partner)
-}
-
-// GobEncode the KeyStore
-func (ks *KeyStore) GobEncode() ([]byte, error) {
-	var buf bytes.Buffer
-
-	// Create new encoder that will transmit the buffer
-	enc := gob.NewEncoder(&buf)
-
-	// Transmit the Key Parameters
-	err := enc.Encode(ks.params)
-
-	if err != nil {
-		return nil, err
-	}
-
-	// Transmit the Send Key Managers
-	kmList := ks.sendKeyManagers.values()
-	err = enc.Encode(kmList)
-
-	if err != nil {
-		return nil, err
-	}
-
-	// Transmit the Receive Key Managers
-	err = enc.Encode(ks.recvKeyManagers)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
-}
-
-// GobDecode the KeyStore from bytes
-// NOTE: ReconstructKeys must be called after GobDecoding a KeyStore
-func (ks *KeyStore) GobDecode(in []byte) error {
-	var buf bytes.Buffer
-
-	// Write bytes to the buffer
-	buf.Write(in)
-
-	// Create new decoder that reads from the buffer
-	dec := gob.NewDecoder(&buf)
-
-	// Decode Key Parameters
-	err := dec.Decode(&ks.params)
-
-	if err != nil {
-		return err
-	}
-
-	// Decode Key Managers List
-	var kmList []*KeyManager
-	err = dec.Decode(&kmList)
-
-	if err != nil {
-		return err
-	}
-
-	// Decode Recv Key Managers map
-	err = dec.Decode(&ks.recvKeyManagers)
-
-	if err != nil {
-		return err
-	}
-
-	// Reconstruct Send Key Manager map
-	ks.sendKeyManagers = new(keyManMap)
-	ks.receptionKeys = new(inKeyMap)
-	for _, km := range kmList {
-		ks.AddSendManager(km)
-	}
-
-	return nil
-}
-
-// ReconstructKeys loops through all key managers and
-// calls GenerateKeys on each of them, in order to rebuild
-// the key maps
-func (ks *KeyStore) ReconstructKeys(grp *cyclic.Group, userID *id.ID) {
-
-	kmList := ks.sendKeyManagers.values()
-	for _, km := range kmList {
-		km.GenerateKeys(grp, userID)
-		ks.AddSendManager(km)
-	}
-
-	for _, kmb := range ks.recvKeyManagers {
-		for _, km := range kmb.managers {
-			if km != nil {
-				e2eKeys := km.GenerateKeys(grp, userID)
-				ks.AddReceiveKeysByFingerprint(e2eKeys)
-			}
-		}
-	}
-}
-
-func (ks *KeyStore) DeleteReceiveKeysByFingerprint(toBeDeleted []format.Fingerprint) {
-	if len(toBeDeleted) != 0 {
-		ks.receptionKeys.DeleteList(toBeDeleted)
-	}
-}
-
-func (ks *KeyStore) AddReceiveKeysByFingerprint(newKeys []*E2EKey) {
-	for _, key := range newKeys {
-		ks.AddRecvKey(key.KeyFingerprint(), key)
-	}
-}
-
-// Delete multiple Receiving E2EKeys from the correct KeyStore map
-// based on a list of fingerprints
-func (ks *KeyStore) DeleteRecvKeyList(fingerprints []format.Fingerprint) {
-	ks.receptionKeys.DeleteList(fingerprints)
-}
diff --git a/keyStore/keyStore_test.go b/keyStore/keyStore_test.go
deleted file mode 100644
index ee334ff3ad7147cb1d6ba2f02423f4f5f1705b4a..0000000000000000000000000000000000000000
--- a/keyStore/keyStore_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/gob"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"testing"
-)
-
-// Test GetKeyParams and confirm default params are correct
-func TestKeyStore_GetKeyParams(t *testing.T) {
-	ks := NewStore()
-
-	params := ks.GetKeyParams()
-
-	if params.MinKeys != minKeys {
-		t.Errorf("KeyParams: MinKeys mismatch, expected %d, "+
-			"got %d", minKeys, params.MinKeys)
-	} else if params.MaxKeys != maxKeys {
-		t.Errorf("KeyParams: MaxKeys mismatch, expected %d, "+
-			"got %d", maxKeys, params.MaxKeys)
-	} else if params.NumRekeys != numReKeys {
-		t.Errorf("KeyParams: NumRekeys mismatch, expected %d, "+
-			"got %d", numReKeys, params.NumRekeys)
-	} else if params.TTLScalar != ttlScalar {
-		t.Errorf("KeyParams: TTLScalar mismatch, expected %f, "+
-			"got %f", ttlScalar, params.TTLScalar)
-	} else if params.MinNumKeys != threshold {
-		t.Errorf("KeyParams: MinNumKeys mismatch, expected %d, "+
-			"got %d", threshold, params.MinNumKeys)
-	}
-}
-
-// Test GOB Encode/Decode of KeyStore
-// and compare if all keys match originals
-func TestKeyStore_Gob(t *testing.T) {
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	ks := NewStore()
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	// Generate Send Keys
-	e2ekeys := km.GenerateKeys(grp, userID)
-	ks.AddSendManager(km)
-
-	km2 := NewManager(baseKey, privKey, pubKey,
-		partner, false, 12, 10, 10)
-
-	// Generate Receive Keys
-	e2ekeys = km2.GenerateKeys(grp, userID)
-	ks.AddReceiveKeysByFingerprint(e2ekeys)
-	ks.AddRecvManager(km2)
-
-	// Now that some KeyManagers are in the keystore, Gob Encode it
-	var byteBuf bytes.Buffer
-
-	enc := gob.NewEncoder(&byteBuf)
-	dec := gob.NewDecoder(&byteBuf)
-
-	err := enc.Encode(ks)
-
-	if err != nil {
-		t.Errorf("Error GOB Encoding KeyStore: %s", err)
-	}
-
-	outKs := &KeyStore{}
-
-	err = dec.Decode(&outKs)
-
-	if err != nil {
-		t.Errorf("Error GOB Decoding KeyStore: %s", err)
-	}
-
-	// Need to reconstruct keys after decoding
-	outKs.ReconstructKeys(grp, userID)
-
-	// Get KeyManagers and compare keys
-	outKm := outKs.GetSendManager(partner)
-
-	for i := 0; i < 12; i++ {
-		origKey, _ := km.PopKey()
-		actualKey, _ := outKm.PopKey()
-
-		if origKey.GetOuterType() != actualKey.GetOuterType() {
-			t.Errorf("Send Key type mistmatch after GOB Encode/Decode")
-		} else if origKey.key.Cmp(actualKey.key) != 0 {
-			t.Errorf("Send Key mistmatch after GOB Encode/Decode")
-		}
-	}
-
-	for i := 0; i < 10; i++ {
-		origKey, _ := km.PopRekey()
-		actualKey, _ := outKm.PopRekey()
-
-		if origKey.GetOuterType() != actualKey.GetOuterType() {
-			t.Errorf("Send Key type mistmatch after GOB Encode/Decode")
-		} else if origKey.key.Cmp(actualKey.key) != 0 {
-			t.Errorf("Send Key mistmatch after GOB Encode/Decode")
-		}
-	}
-}
-
-// Tests that GobDecode() for Key Store throws an error for a
-// malformed byte array
-func TestKeyStore_GobDecodeErrors(t *testing.T) {
-	ksTest := KeyStore{}
-	err := ksTest.GobDecode([]byte{})
-
-	if err.Error() != "EOF" {
-		//if !reflect.DeepEqual(err, errors.New("EOF")) {
-		t.Errorf("GobDecode() did not produce the expected error\n\treceived: %v"+
-			"\n\texpected: %v", err, errors.New("EOF"))
-	}
-}
-
-func TestKeyStore_DeleteContactKeys(t *testing.T) {
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	ks := NewStore()
-	km := NewManager(baseKey, privKey, pubKey,
-		partner, true, 12, 10, 10)
-
-	// Generate Send Keys
-	e2ekeys := km.GenerateKeys(grp, userID)
-	km.recvReKeysFingerprint = []format.Fingerprint{*format.NewFingerprint([]byte("testtesttesttesttesttesttesttest"))}
-	km.recvKeysFingerprint = []format.Fingerprint{*format.NewFingerprint([]byte("testtesttesttesttesttesttesttest"))}
-	ks.AddSendManager(km)
-	rkmb := NewReceptionKeyManagerBuffer()
-
-	km2 := NewManager(baseKey, privKey, pubKey,
-		partner, false, 12, 10, 10)
-
-	// Generate Receive Keys
-	e2ekeys = km2.GenerateKeys(grp, userID)
-	ks.AddReceiveKeysByFingerprint(e2ekeys)
-	km2.recvReKeysFingerprint = []format.Fingerprint{*format.NewFingerprint([]byte("testtesttesttesttesttesttesttest"))}
-	km2.recvKeysFingerprint = []format.Fingerprint{*format.NewFingerprint([]byte("testtesttesttesttesttesttesttest"))}
-	ks.AddRecvManager(km2)
-
-	rkmb.managers[0] = km
-	rkmb.managers[1] = km2
-	rkmb.managers[2] = km2
-	rkmb.managers[3] = km2
-	rkmb.managers[4] = km2
-	ks.recvKeyManagers[*partner] = rkmb
-
-	err := ks.DeleteContactKeys(partner)
-	if err != nil {
-		t.Errorf("Failed to delete contact keys: %+v", err)
-	}
-}
diff --git a/keyStore/recieveKeyManagerBuffer.go b/keyStore/recieveKeyManagerBuffer.go
deleted file mode 100644
index 334b5c795b7dc48e8583a31676918a0353a56cef..0000000000000000000000000000000000000000
--- a/keyStore/recieveKeyManagerBuffer.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/gob"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/primitives/format"
-)
-
-const ReceptionKeyManagerBufferLength = 5
-
-//This creates a circular buffer and initializes all the keymanagers to be nil at location zero.
-func NewReceptionKeyManagerBuffer() *ReceptionKeyManagerBuffer {
-	newBuffer := ReceptionKeyManagerBuffer{}
-	newBuffer.loc = 0
-	return &newBuffer
-}
-
-type ReceptionKeyManagerBuffer struct {
-	managers [ReceptionKeyManagerBufferLength]*KeyManager
-	loc      int
-}
-
-// Push takes in a new keymanager obj, and adds it into our circular buffer of keymanagers,
-// the keymanager obj passed in overwrites the keymanager in the buffer, and we have to return the existing
-// keymanager if there is one back ot the parent so that the deletion can be handled.
-func (rkmb *ReceptionKeyManagerBuffer) push(km *KeyManager) []format.Fingerprint {
-	deadkm := &KeyManager{}
-	deadkm = nil
-	if rkmb.managers[0] != nil {
-		//Don't increment location if location 0 is empty first time around
-		rkmb.loc = (rkmb.loc + 1) % ReceptionKeyManagerBufferLength
-		deadkm = rkmb.managers[rkmb.loc]
-	} else {
-
-	}
-
-	rkmb.managers[rkmb.loc] = km
-
-	if deadkm == nil {
-		return []format.Fingerprint{}
-	} else {
-
-		return append(deadkm.recvKeysFingerprint, deadkm.recvReKeysFingerprint...)
-
-	}
-}
-
-func (rkmb *ReceptionKeyManagerBuffer) getCurrentReceptionKeyManager() *KeyManager {
-	return rkmb.managers[rkmb.loc]
-}
-
-func (rkmb *ReceptionKeyManagerBuffer) getCurrentLoc() int {
-	return rkmb.loc
-}
-
-func (rkmb *ReceptionKeyManagerBuffer) getReceptionKeyManagerAtLoc(n int) *KeyManager {
-	return rkmb.managers[n%ReceptionKeyManagerBufferLength]
-}
-
-func (rkmb *ReceptionKeyManagerBuffer) GobEncode() ([]byte, error) {
-
-	//get rid of nils for encoding
-	var bufferSlice []*KeyManager
-
-	for i := 0; i < len(rkmb.managers); i++ {
-		j := (rkmb.loc + i) % len(rkmb.managers)
-		if rkmb.managers[j] != nil {
-			bufferSlice = append(bufferSlice, rkmb.managers[j])
-		}
-
-	}
-
-	anon := struct {
-		Managers []*KeyManager
-		Loc      int
-	}{
-		bufferSlice,
-		rkmb.loc,
-	}
-
-	var encodeBytes bytes.Buffer
-
-	enc := gob.NewEncoder(&encodeBytes)
-
-	err := enc.Encode(anon)
-
-	if err != nil {
-		err = errors.New(fmt.Sprintf("Could not encode Reception Keymanager Buffer: %s",
-			err.Error()))
-		return nil, err
-	}
-	return encodeBytes.Bytes(), nil
-
-}
-
-func (rkmb *ReceptionKeyManagerBuffer) GobDecode(in []byte) error {
-
-	anon := struct {
-		Managers []*KeyManager
-		Loc      int
-	}{}
-
-	var buf bytes.Buffer
-
-	// Write bytes to the buffer
-	buf.Write(in)
-
-	dec := gob.NewDecoder(&buf)
-
-	err := dec.Decode(&anon)
-
-	if err != nil {
-		err = errors.New(fmt.Sprintf("Could not Decode Reception Keymanager Buffer: %s", err.Error()))
-		return err
-	}
-
-	rkmb.loc = anon.Loc
-
-	for i := 0; i < len(anon.Managers); i++ {
-		j := (anon.Loc + i) % len(rkmb.managers)
-		rkmb.managers[j] = anon.Managers[i]
-	}
-
-	return nil
-}
diff --git a/keyStore/recieveKeyManagerBuffer_test.go b/keyStore/recieveKeyManagerBuffer_test.go
deleted file mode 100644
index a9caec7d1dba38af6a53253a26b64b518df0688b..0000000000000000000000000000000000000000
--- a/keyStore/recieveKeyManagerBuffer_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package keyStore
-
-import (
-	"bytes"
-	"encoding/gob"
-	"gitlab.com/elixxir/primitives/id"
-	"testing"
-)
-
-// Test that the buffer is recieving objects and that it is in fact circular
-func TestPush(t *testing.T) {
-	aBuffer := NewReceptionKeyManagerBuffer()
-
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	//Generate twice the amount of keymanagers so we can test the circularness of the buffer as well
-	kmArray := []KeyManager{}
-	for i := 0; i < ReceptionKeyManagerBufferLength*2; i++ {
-		newKm := *NewManager(baseKey, nil, nil,
-			partner, false, 12, 10, 10)
-
-		newKm.GenerateKeys(grp, userID)
-		kmArray = append(kmArray, newKm)
-
-		toDelete := aBuffer.push(&newKm)
-		println("delete %v", toDelete)
-		if i < ReceptionKeyManagerBufferLength {
-			if len(toDelete) != 0 {
-				//ERROR should have something
-				t.Errorf("Error Nothing Should Be Returned to be deleted since" +
-					" keybuffer should be filling up from empty state")
-			}
-
-			if &newKm != aBuffer.getCurrentReceptionKeyManager() {
-				t.Errorf("Error incorrect Keymanager receieved from buffer.")
-			}
-
-		} else {
-			if len(toDelete) == 0 {
-				t.Errorf("Error not returning old keymanager to properly be disposed of")
-			}
-
-			if &newKm != aBuffer.getCurrentReceptionKeyManager() {
-				t.Errorf("Error incorrect Keymanager receieved from buffer after its been filled up.")
-			}
-		}
-
-	}
-
-	if &kmArray[0] == &kmArray[1] {
-		t.Errorf("Error tests fail because we are not creating a new Keymanager")
-	}
-
-}
-
-//test that loc is always circular and outputted value is what is expected
-func TestReceptionKeyManagerBuffer_getCurrentLoc(t *testing.T) {
-	aBuffer := NewReceptionKeyManagerBuffer()
-
-	if aBuffer.getCurrentLoc() != 0 {
-		// Error location is not initialized as zero
-		t.Errorf("Error ReceptionKeyManagerBuffer Loc not initialized to zero")
-	}
-
-	for i := 0; i < ReceptionKeyManagerBufferLength*2; i++ {
-
-		aBuffer.push(&KeyManager{})
-
-		if aBuffer.getCurrentLoc() != aBuffer.loc {
-			//error mismatch between actual loc and returned loc
-			t.Errorf("Error ReceptionKeyManagerBuffer Loc mismatch with Getfunction")
-		}
-
-		if aBuffer.loc > ReceptionKeyManagerBufferLength || aBuffer.loc < 0 {
-			//Error Buffer Out of bounds
-			t.Errorf("Error ReceptionKeyManagerBuffer Loc out of bounds error")
-		}
-
-		if aBuffer.loc != (i % ReceptionKeyManagerBufferLength) {
-			//Error location is not circular
-
-			t.Errorf("Error ReceptionKeyManagerBuffer Loc is not circular")
-		}
-	}
-
-}
-
-func TestReceptionKeyManagerBuffer_getCurrentReceptionKeyManager(t *testing.T) {
-	aBuffer := NewReceptionKeyManagerBuffer()
-	testManager := &KeyManager{}
-	aBuffer.push(testManager)
-
-	if aBuffer.getCurrentReceptionKeyManager() != testManager {
-		t.Errorf("Error this is not the same manager pushed in.")
-	}
-}
-
-func TestNewReceptionKeyManagerBuffer(t *testing.T) {
-	aBuffer := NewReceptionKeyManagerBuffer()
-
-	if aBuffer == nil {
-		t.Errorf("Error creating new reception keymanager buffer returning nil")
-	}
-}
-
-func TestReceptionKeyManagerBuffer_Gob(t *testing.T) {
-	aBuffer := NewReceptionKeyManagerBuffer()
-	grp := initGroup()
-	baseKey := grp.NewInt(57)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-
-	newKm := *NewManager(baseKey, nil,
-		nil, partner,
-		false, 12, 10, 10)
-
-	newKm.GenerateKeys(grp, userID)
-
-	aBuffer.push(&newKm)
-
-	var byteBuf bytes.Buffer
-
-	enc := gob.NewEncoder(&byteBuf)
-	dec := gob.NewDecoder(&byteBuf)
-
-	err := enc.Encode(aBuffer)
-
-	if err != nil {
-		t.Errorf("Failed to encode GOB KeyManagerBuffer: %s", err)
-	}
-
-	newBuffer := NewReceptionKeyManagerBuffer()
-	err = dec.Decode(&newBuffer)
-	if err != nil {
-		t.Errorf("Failed to decode GOB KeyManagerBuffer: %s", err)
-	}
-}
diff --git a/keyStore/rekeyManager.go b/keyStore/rekeyManager.go
deleted file mode 100644
index cfb0442141a2ac3f93c871d1d35decb953e4afb0..0000000000000000000000000000000000000000
--- a/keyStore/rekeyManager.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package keyStore
-
-import (
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/primitives/id"
-	"sync"
-)
-
-type RekeyContext struct {
-	BaseKey *cyclic.Int
-	PrivKey *cyclic.Int
-	PubKey  *cyclic.Int
-}
-
-type RekeyKeys struct {
-	CurrPrivKey *cyclic.Int
-	CurrPubKey  *cyclic.Int
-	NewPrivKey  *cyclic.Int
-	NewPubKey   *cyclic.Int
-}
-
-func (k *RekeyKeys) RotateKeysIfReady() {
-	if k.NewPrivKey != nil && k.NewPubKey != nil {
-		k.CurrPrivKey = k.NewPrivKey
-		k.CurrPubKey = k.NewPubKey
-		k.NewPrivKey = nil
-		k.NewPubKey = nil
-	}
-}
-
-type RekeyManager struct {
-	Ctxs map[id.ID]*RekeyContext
-	Keys map[id.ID]*RekeyKeys
-	lock sync.Mutex
-}
-
-func NewRekeyManager() *RekeyManager {
-	return &RekeyManager{
-		Ctxs: make(map[id.ID]*RekeyContext),
-		Keys: make(map[id.ID]*RekeyKeys),
-	}
-}
-
-func (rkm *RekeyManager) AddCtx(partner *id.ID,
-	ctx *RekeyContext) {
-	rkm.lock.Lock()
-	defer rkm.lock.Unlock()
-	rkm.Ctxs[*partner] = ctx
-}
-
-func (rkm *RekeyManager) GetCtx(partner *id.ID) *RekeyContext {
-	rkm.lock.Lock()
-	defer rkm.lock.Unlock()
-	return rkm.Ctxs[*partner]
-}
-
-func (rkm *RekeyManager) DeleteCtx(partner *id.ID) {
-	rkm.lock.Lock()
-	defer rkm.lock.Unlock()
-	delete(rkm.Ctxs, *partner)
-}
-
-func (rkm *RekeyManager) AddKeys(partner *id.ID,
-	keys *RekeyKeys) {
-	rkm.lock.Lock()
-	defer rkm.lock.Unlock()
-	rkm.Keys[*partner] = keys
-}
-
-func (rkm *RekeyManager) GetKeys(partner *id.ID) *RekeyKeys {
-	rkm.lock.Lock()
-	defer rkm.lock.Unlock()
-	return rkm.Keys[*partner]
-}
-
-func (rkm *RekeyManager) DeleteKeys(partner *id.ID) {
-	rkm.lock.Lock()
-	defer rkm.lock.Unlock()
-	delete(rkm.Keys, *partner)
-}
diff --git a/keyStore/rekeyManager_test.go b/keyStore/rekeyManager_test.go
deleted file mode 100644
index 87072da6f788bbc5c7e6aae38fea5e7e0c8d6b96..0000000000000000000000000000000000000000
--- a/keyStore/rekeyManager_test.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package keyStore
-
-import (
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/primitives/id"
-	"testing"
-)
-
-// Test creation of RekeyManager
-func TestRekeyManager_New(t *testing.T) {
-	rkm := NewRekeyManager()
-
-	if rkm == nil {
-		t.Errorf("NewRekeyManager returned nil")
-	}
-}
-
-// Test all Ctx related functions of RekeyManager
-func TestRekeyManager_Ctx(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	baseKey := grp.NewInt(57)
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-	rkm := NewRekeyManager()
-
-	val := &RekeyContext{
-		BaseKey: baseKey,
-		PrivKey: privKey,
-		PubKey:  pubKey,
-	}
-
-	// Add RekeyContext to map
-	rkm.AddCtx(partner, val)
-
-	// Confirm different partner returns nil
-	actual := rkm.GetCtx(userID)
-
-	if actual != nil {
-		t.Errorf("GetCtx returned something but expected nil")
-	}
-
-	// Get added value and compare
-	actual = rkm.GetCtx(partner)
-
-	if actual == nil {
-		t.Errorf("GetCtx returned nil")
-	} else if actual.BaseKey.Cmp(baseKey) != 0 {
-		t.Errorf("BaseKey doesn't match for RekeyContext added to Contexts map")
-	} else if actual.PrivKey.Cmp(privKey) != 0 {
-		t.Errorf("PrivKey doesn't match for RekeyContext added to Contexts map")
-	} else if actual.PubKey.Cmp(pubKey) != 0 {
-		t.Errorf("PubKey doesn't match for RekeyContext added to Contexts map")
-	}
-
-	// Delete value and confirm it's gone
-	rkm.DeleteCtx(partner)
-
-	actual = rkm.GetCtx(partner)
-
-	if actual != nil {
-		t.Errorf("GetCtx returned something but expected nil after deletion")
-	}
-}
-
-// Test all Keys related functions of RekeyManager
-func TestRekeyManager_Keys(t *testing.T) {
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-	privKey := grp.NewInt(5)
-	pubKey := grp.NewInt(42)
-	partner := id.NewIdFromUInt(14, id.User, t)
-	userID := id.NewIdFromUInt(18, id.User, t)
-	rkm := NewRekeyManager()
-
-	val := &RekeyKeys{
-		CurrPrivKey: privKey,
-		CurrPubKey:  pubKey,
-	}
-
-	// Add RekeyKeys to map
-	rkm.AddKeys(partner, val)
-
-	// Confirm different partner returns nil
-	actual := rkm.GetKeys(userID)
-
-	if actual != nil {
-		t.Errorf("GetNodeKeys returned something but expected nil")
-	}
-
-	// Get added value and compare
-	actual = rkm.GetKeys(partner)
-
-	if actual == nil {
-		t.Errorf("GetNodeKeys returned nil")
-	} else if actual.CurrPrivKey.Cmp(privKey) != 0 {
-		t.Errorf("CurrPrivKey doesn't match for RekeyKeys added to Keys map")
-	} else if actual.CurrPubKey.Cmp(pubKey) != 0 {
-		t.Errorf("CurrPubKey doesn't match for RekeyKeys added to Keys map")
-	}
-
-	// Delete value and confirm it's gone
-	rkm.DeleteKeys(partner)
-
-	actual = rkm.GetKeys(partner)
-
-	if actual != nil {
-		t.Errorf("GetNodeKeys returned something but expected nil after deletion")
-	}
-
-	// Confirm RekeyKeys behavior of key rotation
-	newPrivKey := grp.NewInt(7)
-	newPubKey := grp.NewInt(91)
-
-	// Add new PrivKey
-	val.NewPrivKey = newPrivKey
-
-	// Call rotate and confirm nothing changes
-	val.RotateKeysIfReady()
-
-	if val.CurrPrivKey.Cmp(privKey) != 0 {
-		t.Errorf("CurrPrivKey doesn't match for RekeyKeys after adding new PrivateKey")
-	} else if val.CurrPubKey.Cmp(pubKey) != 0 {
-		t.Errorf("CurrPubKey doesn't match for RekeyKeys after adding new PrivateKey")
-	}
-
-	// Add new PubKey, rotate, and confirm keys change
-	val.NewPubKey = newPubKey
-	val.RotateKeysIfReady()
-
-	if val.CurrPrivKey.Cmp(newPrivKey) != 0 {
-		t.Errorf("CurrPrivKey doesn't match for RekeyKeys after key rotation")
-	} else if val.CurrPubKey.Cmp(newPubKey) != 0 {
-		t.Errorf("CurrPubKey doesn't match for RekeyKeys after key rotation")
-	}
-}
diff --git a/main.go b/main.go
index a39e9f5c9e113859986a2102f4e9f043698085b4..47a38ce28d8f06cb2c16d1aba035b3a9c393caad 100644
--- a/main.go
+++ b/main.go
@@ -1,8 +1,9 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
 
 package main
 
diff --git a/network/ephemeral/testutil.go b/network/ephemeral/testutil.go
new file mode 100644
index 0000000000000000000000000000000000000000..fbcc4e3e8d031097f4d9447c0904f537451b3d40
--- /dev/null
+++ b/network/ephemeral/testutil.go
@@ -0,0 +1,150 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package ephemeral
+
+import (
+	"testing"
+
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/comms/testkeys"
+	"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"
+	"gitlab.com/xx_network/primitives/utils"
+)
+
+// testNetworkManager is a test implementation of NetworkManager interface.
+type testNetworkManager struct {
+	instance *network.Instance
+	msg      message.Send
+}
+
+func (t *testNetworkManager) SendE2E(m message.Send, _ params.E2E) ([]id.Round,
+	e2e.MessageID, error) {
+	rounds := []id.Round{
+		id.Round(0),
+		id.Round(1),
+		id.Round(2),
+	}
+
+	t.msg = m
+
+	return rounds, e2e.MessageID{}, nil
+}
+
+func (t *testNetworkManager) SendUnsafe(m message.Send, _ params.Unsafe) ([]id.Round, error) {
+	rounds := []id.Round{
+		id.Round(0),
+		id.Round(1),
+		id.Round(2),
+	}
+
+	t.msg = m
+
+	return rounds, nil
+}
+
+func (t *testNetworkManager) SendCMIX(format.Message, *id.ID, params.CMIX) (id.Round, ephemeral.Id, error) {
+	return 0, ephemeral.Id{}, nil
+}
+
+func (t *testNetworkManager) GetInstance() *network.Instance {
+	return t.instance
+}
+
+func (t *testNetworkManager) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+
+func (t *testNetworkManager) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	return nil, nil
+}
+
+func (t *testNetworkManager) CheckGarbledMessages() {}
+
+func (t *testNetworkManager) InProgressRegistrations() int {
+	return 0
+}
+
+func NewTestNetworkManager(i interface{}) interfaces.NetworkManager {
+	switch i.(type) {
+	case *testing.T, *testing.M, *testing.B:
+		break
+	default:
+		jww.FATAL.Panicf("initTesting is restricted to testing only."+
+			"Got %T", i)
+	}
+
+	commsManager := connect.NewManagerTesting(i)
+
+	cert, err := utils.ReadFile(testkeys.GetNodeCertPath())
+	if err != nil {
+		jww.FATAL.Panicf("Failed to create new test Instance: %v", err)
+	}
+
+	commsManager.AddHost(&id.Permissioning, "", cert, connect.GetDefaultHostParams())
+	instanceComms := &connect.ProtoComms{
+		Manager: commsManager,
+	}
+
+	thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), getNDF(), nil, nil, i)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to create new test Instance: %v", err)
+	}
+
+	thisManager := &testNetworkManager{instance: thisInstance}
+
+	return thisManager
+}
+
+func getNDF() *ndf.NetworkDefinition {
+	return &ndf.NetworkDefinition{
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}
diff --git a/network/ephemeral/tracker.go b/network/ephemeral/tracker.go
new file mode 100644
index 0000000000000000000000000000000000000000..f158e337521ffddb5bcd6267e69e0e048e89f9de
--- /dev/null
+++ b/network/ephemeral/tracker.go
@@ -0,0 +1,196 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package ephemeral
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"time"
+)
+
+const validityGracePeriod = 5 * time.Minute
+const TimestampKey = "IDTrackingTimestamp"
+const ephemeralStoppable = "EphemeralCheck"
+
+// Track runs a thread which checks for past and present ephemeral ids
+func Track(session *storage.Session, ourId *id.ID) stoppable.Stoppable {
+	stop := stoppable.NewSingle(ephemeralStoppable)
+
+	go track(session, ourId, stop)
+
+	return stop
+}
+
+// track is a thread which continuously processes ephemeral ids.
+// If any error occurs, the thread crashes
+func track(session *storage.Session, ourId *id.ID, stop *stoppable.Single) {
+
+	// Check that there is a timestamp in store at all
+	err := checkTimestampStore(session)
+	if err != nil {
+		jww.FATAL.Panicf("Could not store timestamp "+
+			"for ephemeral ID tracking: %v", err)
+	}
+
+	// Get the latest timestamp from store
+	lastTimestampObj, err := session.Get(TimestampKey)
+	if err != nil {
+		jww.FATAL.Panicf("Could not get timestamp: %v", err)
+	}
+
+	lastCheck, err := unmarshalTimestamp(lastTimestampObj)
+	if err != nil {
+		jww.FATAL.Panicf("Could not parse stored timestamp: %v", err)
+	}
+
+	// Wait until we get the id size from the network
+	receptionStore := session.Reception()
+	receptionStore.WaitForIdSizeUpdate()
+
+	for true {
+		now := time.Now()
+		// Generates the IDs since the last track
+		protoIds, err := ephemeral.GetIdsByRange(ourId, receptionStore.GetIDSize(),
+			now, now.Sub(lastCheck))
+
+		jww.DEBUG.Printf("Now: %s, LastCheck: %s, Different: %s",
+			now, lastCheck, now.Sub(lastCheck))
+
+		jww.DEBUG.Printf("protoIds Count: %d", len(protoIds))
+
+		if err != nil {
+			jww.FATAL.Panicf("Could not generate "+
+				"upcoming IDs: %v", err)
+		}
+
+		// Generate identities off of that list
+		identities := generateIdentities(protoIds, ourId)
+
+		jww.INFO.Printf("Number of Identities Generated: %d",
+			len(identities))
+
+		jww.INFO.Printf("Current Identity: %d (source: %s), Start: %s, End: %s",
+			identities[len(identities)-1].EphId.Int64(), identities[len(identities)-1].Source,
+			identities[len(identities)-1].StartValid, identities[len(identities)-1].EndValid)
+
+		// Add identities to storage if unique
+		for _, identity := range identities {
+			if err = receptionStore.AddIdentity(identity); err != nil {
+				jww.FATAL.Panicf("Could not insert "+
+					"identity: %v", err)
+			}
+		}
+
+		// Generate the time stamp for storage
+		vo, err := marshalTimestamp(now)
+		if err != nil {
+			jww.FATAL.Panicf("Could not marshal "+
+				"timestamp for storage: %v", err)
+
+		}
+
+		// Store the timestamp
+		if err = session.Set(TimestampKey, vo); err != nil {
+			jww.FATAL.Panicf("Could not store timestamp: %v", err)
+		}
+
+		// Sleep until the last Id has expired
+		timeToSleep := calculateTickerTime(protoIds)
+		t := time.NewTimer(timeToSleep)
+		select {
+		case <-t.C:
+		case <-stop.Quit():
+			return
+		}
+	}
+}
+
+// generateIdentities is a constructor which generates a list of
+// identities off of the list of protoIdentities passed in
+func generateIdentities(protoIds []ephemeral.ProtoIdentity,
+	ourId *id.ID) []reception.Identity {
+
+	identities := make([]reception.Identity, 0)
+
+	// Add identities for every ephemeral id
+	for _, eid := range protoIds {
+		// Expand the grace period for both start and end
+		eid.End.Add(validityGracePeriod)
+		eid.Start.Add(-validityGracePeriod)
+		identities = append(identities, reception.Identity{
+			EphId:      eid.Id,
+			Source:     ourId,
+			End:        eid.End,
+			StartValid: eid.Start,
+			EndValid:   eid.End,
+			Ephemeral:  false,
+		})
+
+	}
+
+	return identities
+}
+
+// Sanitation check of timestamp store. If a value has not been stored yet
+// then the current time is stored
+func checkTimestampStore(session *storage.Session) error {
+	if _, err := session.Get(TimestampKey); err != nil {
+		// only generate from the last hour because this is a new id, it
+		// couldn't receive messages yet
+		now, err := marshalTimestamp(time.Now().Add(-1 * time.Hour))
+		if err != nil {
+			return errors.Errorf("Could not marshal new timestamp for storage: %v", err)
+		}
+		return session.Set(TimestampKey, now)
+	}
+
+	return nil
+}
+
+// Takes the stored timestamp and unmarshal into a time object
+func unmarshalTimestamp(lastTimestampObj *versioned.Object) (time.Time, error) {
+	if lastTimestampObj == nil || lastTimestampObj.Data == nil {
+		return time.Now(), nil
+	}
+
+	lastTimestamp := time.Time{}
+	err := lastTimestamp.UnmarshalBinary(lastTimestampObj.Data)
+	return lastTimestamp, err
+}
+
+// Marshals the timestamp for ekv storage. Generates a storable object
+func marshalTimestamp(timeToStore time.Time) (*versioned.Object, error) {
+	data, err := timeToStore.MarshalBinary()
+
+	return &versioned.Object{
+		Version:   0,
+		Timestamp: time.Now(),
+		Data:      data,
+	}, err
+}
+
+// Helper function which calculates the time for the ticker based
+// off of the last ephemeral ID to expire
+func calculateTickerTime(baseIDs []ephemeral.ProtoIdentity) time.Duration {
+	if len(baseIDs) == 0 {
+		return time.Duration(0)
+	}
+	// Get the last identity in the list
+	lastIdentity := baseIDs[len(baseIDs)-1]
+
+	// Factor out the grace period previously expanded upon.
+	// Calculate and return that duration
+	gracePeriod := lastIdentity.End.Add(-validityGracePeriod)
+	return lastIdentity.End.Sub(gracePeriod)
+}
diff --git a/network/ephemeral/tracker_test.go b/network/ephemeral/tracker_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d01511f5b03be3b08a2084b92548cae62f84ff7
--- /dev/null
+++ b/network/ephemeral/tracker_test.go
@@ -0,0 +1,125 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package ephemeral
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/testkeys"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
+	"testing"
+	"time"
+)
+
+// Smoke test for Track function
+func TestCheck(t *testing.T) {
+	session := storage.InitTestingSession(t)
+	instance := NewTestNetworkManager(t)
+	if err := setupInstance(instance); err != nil {
+		t.Errorf("Could not set up instance: %v", err)
+	}
+
+	/// Store a mock initial timestamp the store
+	now := time.Now()
+	twoDaysAgo := now.Add(-2 * 24 * time.Hour)
+	twoDaysTimestamp, err := marshalTimestamp(twoDaysAgo)
+	if err != nil {
+		t.Errorf("Could not marshal timestamp for test setup: %v", err)
+	}
+	err = session.Set(TimestampKey, twoDaysTimestamp)
+	if err != nil {
+		t.Errorf("Could not set mock timestamp for test setup: %v", err)
+	}
+
+	ourId := id.NewIdFromBytes([]byte("Sauron"), t)
+	stop := Track(session, ourId)
+	session.Reception().MarkIdSizeAsSet()
+
+	err = stop.Close(3 * time.Second)
+	if err != nil {
+		t.Errorf("Could not close thread: %v", err)
+	}
+
+}
+
+// Unit test for track
+func TestCheck_Thread(t *testing.T) {
+
+	session := storage.InitTestingSession(t)
+	instance := NewTestNetworkManager(t)
+	if err := setupInstance(instance); err != nil {
+		t.Errorf("Could not set up instance: %v", err)
+	}
+	ourId := id.NewIdFromBytes([]byte("Sauron"), t)
+	stop := stoppable.NewSingle(ephemeralStoppable)
+
+	/// Store a mock initial timestamp the store
+	now := time.Now()
+	yesterday := now.Add(-24 * time.Hour)
+	yesterdayTimestamp, err := marshalTimestamp(yesterday)
+	if err != nil {
+		t.Errorf("Could not marshal timestamp for test setup: %v", err)
+	}
+	err = session.Set(TimestampKey, yesterdayTimestamp)
+	if err != nil {
+		t.Errorf("Could not set mock timestamp for test setup: %v", err)
+	}
+
+	// Run the tracker
+	go func() {
+		track(session, ourId, stop)
+	}()
+	time.Sleep(3 * time.Second)
+
+	session.Reception().MarkIdSizeAsSet()
+
+	err = stop.Close(3 * time.Second)
+	if err != nil {
+		t.Errorf("Could not close thread: %v", err)
+	}
+
+}
+
+func setupInstance(instance interfaces.NetworkManager) error {
+	cert, err := utils.ReadFile(testkeys.GetNodeKeyPath())
+	if err != nil {
+		return errors.Errorf("Failed to read cert from from file: %v", err)
+	}
+	ri := &mixmessages.RoundInfo{
+		ID: 1,
+	}
+
+	testCert, err := rsa.LoadPrivateKeyFromPem(cert)
+	if err != nil {
+		return errors.Errorf("Failed to load cert from from file: %v", err)
+	}
+	if err = signature.Sign(ri, testCert); err != nil {
+		return errors.Errorf("Failed to sign round info: %v", err)
+	}
+	if err = instance.GetInstance().RoundUpdate(ri); err != nil {
+		return errors.Errorf("Failed to RoundUpdate from from file: %v", err)
+	}
+
+	ri = &mixmessages.RoundInfo{
+		ID: 2,
+	}
+	if err = signature.Sign(ri, testCert); err != nil {
+		return errors.Errorf("Failed to sign round info: %v", err)
+	}
+	if err = instance.GetInstance().RoundUpdate(ri); err != nil {
+		return errors.Errorf("Failed to RoundUpdate from from file: %v", err)
+	}
+
+	return nil
+}
diff --git a/network/follow.go b/network/follow.go
new file mode 100644
index 0000000000000000000000000000000000000000..7ac859229780c4c80396eb28b0e18d4352e2bf48
--- /dev/null
+++ b/network/follow.go
@@ -0,0 +1,265 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package network
+
+// follow.go tracks the network for:
+//   1. The status of the network and its accessibility
+//   2. New/Active/Complete rounds and their contact gateways
+//   3. Node addition and removal
+// This information is tracked by polling a gateway for the network definition
+// file (NDF). Once it detects an event it sends it off to the proper channel
+// for a worker to update the client state (add/remove a node, check for
+// messages at a gateway, etc). See:
+//   - /node/register.go for add/remove node events
+//   - /rounds/historical.go for old round retrieval
+//   - /rounds/retrieve.go for message retrieval
+//   - /message/handle.go decryption, partitioning, and signaling of messages
+//   - /health/tracker.go - tracks the state of the network through the network
+//		instance
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+	"time"
+
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/network/gateway"
+	"gitlab.com/elixxir/client/network/rounds"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/knownRounds"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+const debugTrackPeriod = 1 * time.Minute
+const maxChecked = 100000
+
+//comms interface makes testing easier
+type followNetworkComms interface {
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+	SendPoll(host *connect.Host, message *pb.GatewayPoll) (*pb.GatewayPollResponse, error)
+}
+
+// followNetwork polls the network to get updated on the state of nodes, the
+// round status, and informs the client when messages can be retrieved.
+func (m *manager) followNetwork(report interfaces.ClientErrorReport, quitCh <-chan struct{}) {
+	ticker := time.NewTicker(m.param.TrackNetworkPeriod)
+	TrackTicker := time.NewTicker(debugTrackPeriod)
+	rng := m.Rng.GetStream()
+
+	done := false
+	for !done {
+		select {
+		case <-quitCh:
+			rng.Close()
+			done = true
+		case <-ticker.C:
+			m.follow(report, rng, m.Comms)
+		case <-TrackTicker.C:
+			jww.INFO.Println(m.tracker.Report())
+			m.tracker = newPollTracker()
+		}
+	}
+}
+
+// executes each iteration of the follower
+func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, comms followNetworkComms) {
+
+	//get the identity we will poll for
+	identity, err := m.Session.Reception().GetIdentity(rng)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to get an identity, this should be "+
+			"impossible: %+v", err)
+	}
+
+	m.tracker.Track(identity.EphId, identity.Source)
+
+	//randomly select a gateway to poll
+	//TODO: make this more intelligent
+	gwHost, err := gateway.Get(m.Instance.GetPartialNdf().Get(), comms, rng)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to follow network, NDF has corrupt "+
+			"data: %s", err)
+	}
+
+	// Get client version for poll
+	version := m.Session.GetClientVersion()
+
+	// Poll network updates
+	pollReq := pb.GatewayPoll{
+		Partial: &pb.NDFHash{
+			Hash: m.Instance.GetPartialNdf().GetHash(),
+		},
+		LastUpdate:     uint64(m.Instance.GetLastUpdateID()),
+		ReceptionID:    identity.EphId[:],
+		StartTimestamp: identity.StartRequest.UnixNano(),
+		EndTimestamp:   identity.EndRequest.UnixNano(),
+		ClientVersion:  []byte(version.String()),
+	}
+	jww.TRACE.Printf("Executing poll for %v(%s) range: %s-%s(%s) from %s",
+		identity.EphId.Int64(), identity.Source, identity.StartRequest,
+		identity.EndRequest, identity.EndRequest.Sub(identity.StartRequest), gwHost.GetId())
+
+	pollResp, err := comms.SendPoll(gwHost, &pollReq)
+	if err != nil {
+		report(
+			"NetworkFollower",
+			fmt.Sprintf("Failed to poll network, \"%s\", Gateway: %s", err.Error(), gwHost.String()),
+			fmt.Sprintf("%+v", err),
+		)
+		jww.ERROR.Printf("Unable to poll %s for NDF: %+v", gwHost, err)
+		return
+	}
+
+	// ---- Process Network State Update Data ----
+	gwRoundsState := &knownRounds.KnownRounds{}
+	err = gwRoundsState.Unmarshal(pollResp.KnownRounds)
+	if err != nil {
+		jww.ERROR.Printf("Failed to unmarshal: %+v", err)
+		return
+	}
+
+	// ---- Node Events ----
+	// NOTE: this updates the structure, AND sends events over the node
+	//       update channels about new and removed nodes
+	if pollResp.PartialNDF != nil {
+		err = m.Instance.UpdatePartialNdf(pollResp.PartialNDF)
+		if err != nil {
+			jww.ERROR.Printf("Unable to update partial NDF: %+v", err)
+			return
+		}
+
+		err = m.Instance.UpdateGatewayConnections()
+		if err != nil {
+			jww.ERROR.Printf("Unable to update gateway connections: %+v", err)
+			return
+		}
+	}
+
+	//check that the stored address space is correct
+	m.Session.Reception().UpdateIdSize(uint(m.Instance.GetPartialNdf().Get().AddressSpaceSize))
+	// Updates any id size readers of a network compliant id size
+	m.Session.Reception().MarkIdSizeAsSet()
+	// NOTE: this updates rounds and updates the tracking of the health of the
+	// network
+	if pollResp.Updates != nil {
+		err = m.Instance.RoundUpdates(pollResp.Updates)
+		if err != nil {
+			jww.ERROR.Printf("%+v", err)
+			return
+		}
+
+		// Iterate over ClientErrors for each RoundUpdate
+		for _, update := range pollResp.Updates {
+
+			// Ignore irrelevant updates
+			if update.State != uint32(states.COMPLETED) && update.State != uint32(states.FAILED) {
+				continue
+			}
+
+			for _, clientErr := range update.ClientErrors {
+
+				// If this Client appears in the ClientError
+				if bytes.Equal(clientErr.ClientId, m.Session.GetUser().TransmissionID.Marshal()) {
+
+					// Obtain relevant NodeGateway information
+					nGw, err := m.Instance.GetNodeAndGateway(gwHost.GetId())
+					if err != nil {
+						jww.ERROR.Printf("Unable to get NodeGateway: %+v", err)
+						return
+					}
+					nid, err := nGw.Node.GetNodeId()
+					if err != nil {
+						jww.ERROR.Printf("Unable to get NodeID: %+v", err)
+						return
+					}
+
+					// FIXME: Should be able to trigger proper type of round event
+					// FIXME: without mutating the RoundInfo. Signature also needs verified
+					// FIXME: before keys are deleted
+					update.State = uint32(states.FAILED)
+					m.Instance.GetRoundEvents().TriggerRoundEvent(update)
+
+					// delete all existing keys and trigger a re-registration with the relevant Node
+					m.Session.Cmix().Remove(nid)
+					m.Instance.GetAddGatewayChan() <- nGw
+				}
+			}
+		}
+	}
+
+	// ---- Identity Specific Round Processing -----
+	if identity.Fake {
+		jww.DEBUG.Printf("not processing result, identity.Fake == true")
+		return
+	}
+
+	if len(pollResp.Filters.Filters) == 0 {
+		jww.TRACE.Printf("No filters found for the passed ID %d (%s), "+
+			"skipping processing.", identity.EphId.Int64(), identity.Source)
+		return
+	}
+
+	//get the range fo filters which are valid for the identity
+	filtersStart, filtersEnd, outOfBounds := rounds.ValidFilterRange(identity, pollResp.Filters)
+
+	//check if there are any valid filters returned
+	if outOfBounds {
+		return
+	}
+
+	firstRound := id.Round(math.MaxUint64)
+	lastRound := id.Round(0)
+
+	//prepare the filter objects for processing
+	filterList := make([]*rounds.RemoteFilter, filtersEnd-filtersStart)
+	for i := filtersStart; i < filtersEnd; i++ {
+		if len(pollResp.Filters.Filters[i].Filter) != 0 {
+			filterList[i-filtersStart] = rounds.NewRemoteFilter(pollResp.Filters.Filters[i])
+			if filterList[i-filtersStart].FirstRound() < firstRound {
+				firstRound = filterList[i-filtersStart].FirstRound()
+			}
+			if filterList[i-filtersStart].LastRound() > lastRound {
+				lastRound = filterList[i-filtersStart].LastRound()
+			}
+		}
+	}
+
+	// check rounds using the round checker function which determines if there
+	// are messages waiting in rounds and then sends signals to the appropriate
+	// handling threads
+	roundChecker := func(rid id.Round) bool {
+		return m.round.Checker(rid, filterList, identity)
+	}
+
+	// move the earliest unknown round tracker forward to the earliest
+	// tracked round if it is behind
+	earliestTrackedRound := id.Round(pollResp.EarliestRound)
+	updated := identity.UR.Set(earliestTrackedRound)
+
+
+	// 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
+	// messages for the user (bloom not implemented yet)
+	earliestRemaining := gwRoundsState.RangeUnchecked(updated,
+		maxChecked, roundChecker)
+	identity.UR.Set(earliestRemaining)
+	jww.INFO.Printf("Earliest Remaining: %d", earliestRemaining)
+
+
+	//delete any old rounds from processing
+	if earliestRemaining>updated{
+		for i:=updated;i<=earliestRemaining;i++{
+			m.round.DeleteProcessingRoundDelete(i, identity.EphId, identity.Source)
+		}
+	}
+}
diff --git a/network/gateway/gateway.go b/network/gateway/gateway.go
new file mode 100644
index 0000000000000000000000000000000000000000..f4a16cf2e78a5be6fe9cadbcd496b2cd7bb95b79
--- /dev/null
+++ b/network/gateway/gateway.go
@@ -0,0 +1,112 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package gateway
+
+import (
+	"encoding/binary"
+	"fmt"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/crypto/shuffle"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"io"
+	"math"
+)
+
+type HostGetter interface {
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+}
+
+// Get the Host of a random gateway in the NDF
+func Get(ndf *ndf.NetworkDefinition, hg HostGetter, rng io.Reader) (*connect.Host, error) {
+	gwLen := uint32(len(ndf.Gateways))
+	if gwLen == 0 {
+		return nil, errors.Errorf("no gateways available")
+	}
+
+	gwIdx := ReadRangeUint32(0, gwLen, rng)
+	gwID, err := id.Unmarshal(ndf.Nodes[gwIdx].ID)
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed to get Gateway")
+	}
+	gwID.SetType(id.Gateway)
+	gwHost, ok := hg.GetHost(gwID)
+	if !ok {
+		return nil, errors.Errorf("host for gateway %s could not be "+
+			"retrieved", gwID)
+	}
+	return gwHost, nil
+}
+
+// GetAllShuffled returns a shufled list of gateway hosts from the specified round
+func GetAllShuffled(hg HostGetter, ri *mixmessages.RoundInfo) ([]*connect.Host, error) {
+	roundTop := ri.GetTopology()
+	hosts := make([]*connect.Host, 0)
+	shuffledList := make([]uint64, 0)
+
+	// Collect all host information from the round
+	for index, _ := range roundTop {
+		selectedId, err := id.Unmarshal(roundTop[index])
+		if err != nil {
+			return nil, err
+		}
+
+		selectedId.SetType(id.Gateway)
+
+		gwHost, ok := hg.GetHost(selectedId)
+		if !ok {
+			return nil, errors.Errorf("Could not find host for gateway %s", selectedId)
+		}
+		hosts = append(hosts, gwHost)
+		shuffledList = append(shuffledList, uint64(index))
+	}
+
+	returnHosts := make([]*connect.Host, len(hosts))
+
+	// Shuffle a list corresponding to the valid gateway hosts
+	shuffle.Shuffle(&shuffledList)
+
+	// Index through the shuffled list, building a list
+	// of shuffled gateways from the round
+	for index, shuffledIndex := range shuffledList {
+		returnHosts[index] = hosts[shuffledIndex]
+	}
+
+	return returnHosts, nil
+
+}
+
+// ReadUint32 reads an integer from an io.Reader (which should be a CSPRNG)
+func ReadUint32(rng io.Reader) uint32 {
+	var rndBytes [4]byte
+	i, err := rng.Read(rndBytes[:])
+	if i != 4 || err != nil {
+		panic(fmt.Sprintf("cannot read from rng: %+v", err))
+	}
+	return binary.BigEndian.Uint32(rndBytes[:])
+}
+
+// ReadRangeUint32 reduces an integer from 0, MaxUint32 to the range start, end
+func ReadRangeUint32(start, end uint32, rng io.Reader) uint32 {
+	size := end - start
+	// note we could just do the part inside the () here, but then extra
+	// can == size which means a little bit of range is wastes, either
+	// choice seems negligible so we went with the "more correct"
+	extra := (math.MaxUint32%size + 1) % size
+	limit := math.MaxUint32 - extra
+	// Loop until we read something inside the limit
+	for {
+		res := ReadUint32(rng)
+		if res > limit {
+			continue
+		}
+		return (res % size) + start
+	}
+}
diff --git a/network/health/tracker.go b/network/health/tracker.go
new file mode 100644
index 0000000000000000000000000000000000000000..8e9837165a33982f6dcf7387e7c42388b4c276bd
--- /dev/null
+++ b/network/health/tracker.go
@@ -0,0 +1,178 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Contains functionality related to the event model driven network health tracker
+
+package health
+
+import (
+	"errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/comms/network"
+	"sync"
+	"time"
+)
+
+type Tracker struct {
+	timeout time.Duration
+
+	heartbeat chan network.Heartbeat
+
+	channels []chan bool
+	funcs    []func(isHealthy bool)
+
+	running bool
+
+	// Determines the current health status
+	isHealthy bool
+	// Denotes the past health status
+	// wasHealthy is true if isHealthy has ever been true
+	wasHealthy bool
+	mux        sync.RWMutex
+}
+
+// Creates a single HealthTracker thread, starts it, and returns a tracker and a stoppable
+func Init(instance *network.Instance, timeout time.Duration) *Tracker {
+
+	tracker := newTracker(timeout)
+	instance.SetNetworkHealthChan(tracker.heartbeat)
+
+	return tracker
+}
+
+// Builds and returns a new Tracker object given a Context
+func newTracker(timeout time.Duration) *Tracker {
+	return &Tracker{
+		timeout:   timeout,
+		channels:  make([]chan bool, 0),
+		heartbeat: make(chan network.Heartbeat, 100),
+		isHealthy: false,
+		running:   false,
+	}
+}
+
+// Add a channel to the list of Tracker channels
+// such that each channel can be notified of network changes
+func (t *Tracker) AddChannel(c chan bool) {
+	t.mux.Lock()
+	t.channels = append(t.channels, c)
+	t.mux.Unlock()
+	select {
+	case c <- t.IsHealthy():
+	default:
+	}
+}
+
+// Add a function to the list of Tracker function
+// such that each function can be run after network changes
+func (t *Tracker) AddFunc(f func(isHealthy bool)) {
+	t.mux.Lock()
+	t.funcs = append(t.funcs, f)
+	t.mux.Unlock()
+	go f(t.IsHealthy())
+}
+
+func (t *Tracker) IsHealthy() bool {
+	t.mux.RLock()
+	defer t.mux.RUnlock()
+	return t.isHealthy
+}
+
+// Returns true if isHealthy has ever been true
+func (t *Tracker) WasHealthy() bool {
+	t.mux.RLock()
+	defer t.mux.RUnlock()
+	return t.wasHealthy
+}
+
+func (t *Tracker) setHealth(h bool) {
+	t.mux.Lock()
+	// Only set wasHealthy to true if either
+	//  wasHealthy is true or
+	//  wasHealthy false but h value is true
+	t.wasHealthy = t.wasHealthy || h
+	t.isHealthy = h
+	t.mux.Unlock()
+	t.transmit(h)
+}
+
+func (t *Tracker) Start() (stoppable.Stoppable, error) {
+	t.mux.Lock()
+	defer t.mux.Unlock()
+	if t.running {
+		return nil, errors.New("cannot start Health tracker threads, " +
+			"they are already running")
+	}
+	t.running = true
+
+	t.isHealthy = false
+
+	stop := stoppable.NewSingle("Health Tracker")
+	stopCleanup := stoppable.NewCleanup(stop, func(duration time.Duration) error {
+		t.mux.Lock()
+		defer t.mux.Unlock()
+		t.isHealthy = false
+		t.transmit(false)
+		t.running = false
+		return nil
+	})
+
+	go t.start(stop.Quit())
+
+	return stopCleanup, nil
+}
+
+// Long-running thread used to monitor and report on network health
+func (t *Tracker) start(quitCh <-chan struct{}) {
+	timer := time.NewTimer(t.timeout)
+
+	for {
+		var heartbeat network.Heartbeat
+		select {
+		case <-quitCh:
+			// Handle thread kill
+			break
+		case heartbeat = <-t.heartbeat:
+			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)
+			}
+			break
+		case <-timer.C:
+			t.setHealth(false)
+			break
+		}
+	}
+}
+
+func (t *Tracker) transmit(health bool) {
+	for _, c := range t.channels {
+		select {
+		case c <- health:
+		default:
+			jww.WARN.Printf("Unable to send Health event")
+		}
+	}
+
+	// Run all listening functions
+	for _, f := range t.funcs {
+		go f(health)
+	}
+}
+
+func healthy(a network.Heartbeat) bool {
+	return a.IsRoundComplete
+}
diff --git a/network/health/tracker_test.go b/network/health/tracker_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a10843c36ef23bcd3dc8cfbb5699e71a9643e78
--- /dev/null
+++ b/network/health/tracker_test.go
@@ -0,0 +1,107 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package health
+
+import (
+	"gitlab.com/elixxir/comms/network"
+	//	"gitlab.com/elixxir/comms/network"
+	"testing"
+	"time"
+)
+
+// Happy path smoke test
+func TestNewTracker(t *testing.T) {
+	// Initialize required variables
+	timeout := 250 * time.Millisecond
+	tracker := newTracker(timeout)
+	counter := 2 // First signal is "false/unhealthy"
+	positiveHb := network.Heartbeat{
+		HasWaitingRound: true,
+		IsRoundComplete: true,
+	}
+
+	// Build listening channel and listening function
+	listenChan := make(chan bool, 10)
+	listenFunc := func(isHealthy bool) {
+		if isHealthy {
+			counter++
+		} else {
+			counter--
+		}
+	}
+	tracker.AddChannel(listenChan)
+	tracker.AddFunc(listenFunc)
+	go func() {
+		for isHealthy := range listenChan {
+			if isHealthy {
+				counter++
+			} else {
+				counter--
+			}
+		}
+	}()
+
+	// Begin the health tracker
+	_, err := tracker.Start()
+	if err != nil {
+		t.Errorf("Unable to start tracker: %+v", err)
+		return
+	}
+
+	// Send a positive health heartbeat
+	expectedCount := 2
+	tracker.heartbeat <- positiveHb
+
+	// Wait for the heartbeat to register
+	for i := 0; i < 4; i++ {
+		if tracker.IsHealthy() && counter == expectedCount {
+			break
+		} else {
+			time.Sleep(50 * time.Millisecond)
+		}
+	}
+
+	// Verify the network was marked as healthy
+	if !tracker.IsHealthy() {
+		t.Errorf("Tracker did not become healthy")
+		return
+	}
+
+	// Check if the tracker was ever healthy
+	if !tracker.WasHealthy() {
+		t.Errorf("Tracker did not become healthy")
+		return
+	}
+
+	// Verify the heartbeat triggered the listening chan/func
+	if counter != expectedCount {
+		t.Errorf("Expected counter to be %d, got %d", expectedCount, counter)
+	}
+
+	// Wait out the timeout
+	expectedCount = 0
+	time.Sleep(timeout)
+
+	// Verify the network was marked as NOT healthy
+	if tracker.IsHealthy() {
+		t.Errorf("Tracker should not report healthy")
+		return
+	}
+
+	// Check if the tracker was ever healthy,
+	// after setting healthy to false
+	if !tracker.WasHealthy() {
+		t.Errorf("Tracker was healthy previously but not reported healthy")
+		return
+	}
+
+	// Verify the timeout triggered the listening chan/func
+	if counter != expectedCount {
+		t.Errorf("Expected counter to be %d, got %d", expectedCount, counter)
+	}
+}
diff --git a/network/internal/internal.go b/network/internal/internal.go
new file mode 100644
index 0000000000000000000000000000000000000000..fc0d6aa429348b43469494b3136abc30c347e6da
--- /dev/null
+++ b/network/internal/internal.go
@@ -0,0 +1,40 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package internal
+
+import (
+	"gitlab.com/elixxir/client/network/health"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type Internal struct {
+	Session     *storage.Session
+	Switchboard *switchboard.Switchboard
+	//generic RNG for client
+	Rng *fastRNG.StreamGenerator
+
+	// Comms pointer to send/recv messages
+	Comms *client.Comms
+	//contains the health tracker which keeps track of if from the client's
+	//perspective, the network is in good condition
+	Health *health.Tracker
+	//ID which messages are sent as
+	TransmissionID *id.ID
+	//ID which messages are received as
+	ReceptionID *id.ID
+	//contains the network instance
+	Instance *network.Instance
+
+	//channels
+	NodeRegistration chan network.NodeGateway
+}
diff --git a/network/manager.go b/network/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..9cdb4bb33a063cc53168dbae553e81e506b8c7c7
--- /dev/null
+++ b/network/manager.go
@@ -0,0 +1,180 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package network
+
+// tracker.go controls access to network resources. Interprocess communications
+// and intraclient state are accessible through the context object.
+
+import (
+	"sync/atomic"
+
+	"time"
+
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/network/ephemeral"
+	"gitlab.com/elixxir/client/network/health"
+	"gitlab.com/elixxir/client/network/internal"
+	"gitlab.com/elixxir/client/network/message"
+	"gitlab.com/elixxir/client/network/node"
+	"gitlab.com/elixxir/client/network/rounds"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/primitives/ndf"
+)
+
+// Manager implements the NetworkManager interface inside context. It
+// controls access to network resources and implements all of the communications
+// functions used by the client.
+type manager struct {
+	// parameters of the network
+	param params.Network
+
+	//Shared data with all sub managers
+	internal.Internal
+
+	//sub-managers
+	round   *rounds.Manager
+	message *message.Manager
+	//atomic denotes if the network is running
+	running *uint32
+
+	//map of polls for debugging
+	tracker *pollTracker
+}
+
+// NewManager builds a new reception manager object using inputted key fields
+func NewManager(session *storage.Session, switchboard *switchboard.Switchboard,
+	rng *fastRNG.StreamGenerator, comms *client.Comms,
+	params params.Network, ndf *ndf.NetworkDefinition) (interfaces.NetworkManager, error) {
+
+	//start network instance
+	instance, err := network.NewInstance(comms.ProtoComms, ndf, nil, nil)
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed to create"+
+			" client network manager")
+	}
+
+	running := uint32(0)
+
+	// Note: These are not loaded/stored in E2E Store, but the
+	// E2E Session Params are a part of the network parameters, so we
+	// set them here when they are needed on startup
+	session.E2e().SetE2ESessionParams(params.E2EParams)
+
+	//create manager object
+	m := manager{
+		param:         params,
+		running:       &running,
+		tracker:       newPollTracker(),
+	}
+
+	m.Internal = internal.Internal{
+		Session:          session,
+		Switchboard:      switchboard,
+		Rng:              rng,
+		Comms:            comms,
+		Health:           health.Init(instance, params.NetworkHealthTimeout),
+		NodeRegistration: make(chan network.NodeGateway, params.RegNodesBufferLen),
+		Instance:         instance,
+		TransmissionID:   session.User().GetCryptographicIdentity().GetTransmissionID(),
+		ReceptionID:      session.User().GetCryptographicIdentity().GetReceptionID(),
+	}
+
+	// register the node registration channel early so login connection updates
+	// get triggered for registration if necessary
+	instance.SetAddGatewayChan(m.NodeRegistration)
+
+	//create sub managers
+	m.message = message.NewManager(m.Internal, m.param.Messages, m.NodeRegistration)
+	m.round = rounds.NewManager(m.Internal, m.param.Rounds, m.message.GetMessageReceptionChannel())
+
+	return &m, nil
+}
+
+// StartRunners kicks off all network reception goroutines ("threads").
+// Started Threads are:
+//   - Network Follower (/network/follow.go)
+//   - Historical Round Retrieval (/network/rounds/historical.go)
+//	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
+//	 - Message Handling Worker Group (/network/message/handle.go)
+//	 - Health Tracker (/network/health)
+//	 - Garbled Messages (/network/message/garbled.go)
+//	 - Critical Messages (/network/message/critical.go)
+//   - Ephemeral ID tracking (network/ephemeral/tracker.go)
+func (m *manager) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	if !atomic.CompareAndSwapUint32(m.running, 0, 1) {
+		return nil, errors.Errorf("network routines are already running")
+	}
+
+	multi := stoppable.NewMulti("networkManager")
+
+	// health tracker
+	healthStop, err := m.Health.Start()
+	if err != nil {
+		return nil, errors.Errorf("failed to follow")
+	}
+	multi.Add(healthStop)
+
+	// Node Updates
+	multi.Add(node.StartRegistration(m.Instance, m.Session, m.Rng,
+		m.Comms, m.NodeRegistration, m.param.ParallelNodeRegistrations)) // Adding/Keys
+	//TODO-remover
+	//m.runners.Add(StartNodeRemover(m.Context))        // Removing
+
+	// Start the Network Tracker
+	trackNetworkStopper := stoppable.NewSingle("TrackNetwork")
+	go m.followNetwork(report, trackNetworkStopper.Quit())
+	multi.Add(trackNetworkStopper)
+
+	// Message reception
+	multi.Add(m.message.StartProcessies())
+
+	// Round processing
+	multi.Add(m.round.StartProcessors())
+
+	multi.Add(ephemeral.Track(m.Session, m.ReceptionID))
+
+	//set the running status back to 0 so it can be started again
+	closer := stoppable.NewCleanup(multi, func(time.Duration) error {
+		if !atomic.CompareAndSwapUint32(m.running, 1, 0) {
+			return errors.Errorf("network routines are already stopped")
+		}
+		return nil
+	})
+
+	return closer, nil
+}
+
+// GetHealthTracker returns the health tracker
+func (m *manager) GetHealthTracker() interfaces.HealthTracker {
+	return m.Health
+}
+
+// GetInstance returns the network instance object (ndf state)
+func (m *manager) GetInstance() *network.Instance {
+	return m.Instance
+}
+
+// triggers a check on garbled messages to see if they can be decrypted
+// this should be done when a new e2e client is added in case messages were
+// received early or arrived out of order
+func (m *manager) CheckGarbledMessages() {
+	m.message.CheckGarbledMessages()
+}
+
+// InProgressRegistrations returns an approximation of the number of in progress
+// node registrations.
+func (m *manager) InProgressRegistrations() int {
+	return len(m.Internal.NodeRegistration)
+}
diff --git a/network/message/bundle.go b/network/message/bundle.go
new file mode 100644
index 0000000000000000000000000000000000000000..56f1618d643641da6e9c2550e998a37a1269344c
--- /dev/null
+++ b/network/message/bundle.go
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type Bundle struct {
+	Round    id.Round
+	Messages []format.Message
+	Finish   func()
+	Identity reception.IdentityUse
+}
diff --git a/network/message/critical.go b/network/message/critical.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ebaed5d32390c35f92a6a96557782be6336ff1e
--- /dev/null
+++ b/network/message/critical.go
@@ -0,0 +1,138 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// Critical Messages are protocol layer communications that must succeed. These
+// are added to the persistent critical messages store.  This thread waits for
+// network access to move from unhealthy to healthy and the sends all critical
+// messages.
+// Health is tracked by registering with the Health
+// Tracker (/network/Health/Tracker.g0)
+
+//Thread loop for processing critical messages
+func (m *Manager) processCriticalMessages(quitCh <-chan struct{}) {
+	done := false
+	for !done {
+		select {
+		case <-quitCh:
+			done = true
+		case isHealthy := <-m.networkIsHealthy:
+			if isHealthy {
+				m.criticalMessages()
+			}
+		}
+	}
+}
+
+// processes all critical messages
+func (m *Manager) criticalMessages() {
+	critMsgs := m.Session.GetCriticalMessages()
+	// try to send every message in the critical messages and the raw critical
+	// messages buffer in parallel
+
+	//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)
+			//send the message
+			rounds, _, err := m.SendE2E(msg, param)
+			//if the message fail to send, notify the buffer so it can be handled
+			//in the future and exit
+			if err != nil {
+				jww.ERROR.Printf("Failed to send critical message to %s "+
+					" on notification of healthy network: %+v", msg.Recipient,
+					err)
+				critMsgs.Failed(msg)
+				return
+			}
+			//wait on the results to make sure the rounds were successful
+			sendResults := make(chan ds.EventReturn, len(rounds))
+			roundEvents := m.Instance.GetRoundEvents()
+			for _, r := range rounds {
+				roundEvents.AddRoundEventChan(r, sendResults, 1*time.Minute,
+					states.COMPLETED, states.FAILED)
+			}
+			success, numTimeOut, numRoundFail := utility.TrackResults(sendResults, len(rounds))
+			if !success {
+				jww.ERROR.Printf("critical message send to %s failed "+
+					"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)
+				return
+			}
+
+			jww.INFO.Printf("Sucesfull resend of critical message "+
+				"to %s on rounds %d", msg.Recipient, rounds)
+			critMsgs.Succeeded(msg)
+		}(msg, param)
+	}
+
+	critRawMsgs := m.Session.GetCriticalRawMessages()
+	param := params.GetDefaultCMIX()
+	//raw critical messages
+	for msg, rid, has := critRawMsgs.Next(); has; msg, rid, has = critRawMsgs.Next() {
+		localRid := rid.DeepCopy()
+		go func(msg format.Message, rid *id.ID) {
+			jww.INFO.Printf("Resending critical raw message to %s "+
+				"(msgDigest: %s)", rid, msg.Digest())
+			//send the message
+			round, _, err := m.SendCMIX(msg, rid, param)
+			//if the message fail to send, notify the buffer so it can be handled
+			//in the future and exit
+			if err != nil {
+				jww.ERROR.Printf("Failed to send critical raw message on "+
+					"notification of healthy network: %+v", err)
+				critRawMsgs.Failed(msg, rid)
+				return
+			}
+
+			//wait on the results to make sure the rounds were successful
+			sendResults := make(chan ds.EventReturn, 1)
+			roundEvents := m.Instance.GetRoundEvents()
+
+			roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute,
+				states.COMPLETED, states.FAILED)
+
+			success, numTimeOut, _ := utility.TrackResults(sendResults, 1)
+			if !success {
+				if numTimeOut > 0 {
+					jww.ERROR.Printf("critical raw message resend to %s "+
+						"(msgDigest: %s) on round %d failed to transmit due to "+
+						"timeout", rid, msg.Digest(), round)
+				} else {
+					jww.ERROR.Printf("critical raw message resend to %s "+
+						"(msgDigest: %s) on round %d failed to transmit due to "+
+						"send failure", rid, msg.Digest(), round)
+				}
+
+				critRawMsgs.Failed(msg, rid)
+				return
+			}
+
+			jww.INFO.Printf("Sucesfull resend of critical raw message "+
+				"to %s (msgDigest: %s) on round %d", rid, msg.Digest(), round)
+
+			critRawMsgs.Succeeded(msg, rid)
+		}(msg, localRid)
+	}
+
+}
diff --git a/network/message/garbled.go b/network/message/garbled.go
new file mode 100644
index 0000000000000000000000000000000000000000..70bab27f42eab90b759494ce2912d3d13a864660
--- /dev/null
+++ b/network/message/garbled.go
@@ -0,0 +1,90 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/primitives/format"
+	"time"
+)
+
+// Messages can arrive in the network out of order. When message handling fails
+// to decrypt a message, it is added to the garbled message buffer (which is
+// stored on disk) and the message decryption is retried here whenever triggered.
+
+// This can be triggered through the CheckGarbledMessages on the network manager
+// and is used in the /keyExchange package on successful rekey triggering
+
+// Triggers Garbled message checking if the queue is not full
+// Exposed on the network manager
+func (m *Manager) CheckGarbledMessages() {
+	select {
+	case m.triggerGarbled <- struct{}{}:
+	default:
+	}
+}
+
+//long running thread which processes garbled messages
+func (m *Manager) processGarbledMessages(quitCh <-chan struct{}) {
+	done := false
+	for !done {
+		select {
+		case <-quitCh:
+			done = true
+		case <-m.triggerGarbled:
+			m.handleGarbledMessages()
+		}
+	}
+}
+
+//handler for a single run of garbled messages
+func (m *Manager) handleGarbledMessages() {
+	garbledMsgs := m.Session.GetGarbledMessages()
+	e2eKv := m.Session.E2e()
+	var failedMsgs []format.Message
+	//try to decrypt every garbled message, excising those who's counts are too high
+	for grbldMsg, count, timestamp, has := garbledMsgs.Next(); has; grbldMsg, count, timestamp, has = garbledMsgs.Next() {
+		fingerprint := grbldMsg.GetKeyFP()
+		// Check if the key is there, process it if it is
+		if key, isE2E := e2eKv.PopKey(fingerprint); isE2E {
+			// Decrypt encrypted message
+			msg, err := key.Decrypt(grbldMsg)
+			if err == nil {
+				// get the sender
+				sender := key.GetSession().GetPartner()
+				//remove from the buffer if decryption is successful
+				garbledMsgs.Remove(grbldMsg)
+
+				jww.INFO.Printf("Garbled message decoded as E2E from "+
+					"%s, msgDigest: %s", sender, grbldMsg.Digest())
+
+				//handle the successfully decrypted message
+				xxMsg, ok := m.partitioner.HandlePartition(sender, message.E2E,
+					msg.GetContents(),
+					key.GetSession().GetRelationshipFingerprint())
+				if ok {
+					m.Switchboard.Speak(xxMsg)
+					continue
+				}
+			}
+		}
+		// fail the message if any part of the decryption fails,
+		// unless it is the last attempts and has been in the buffer long
+		// enough, in which case remove it
+		if count == m.param.MaxChecksGarbledMessage &&
+			time.Since(timestamp) > m.param.GarbledMessageWait {
+			garbledMsgs.Remove(grbldMsg)
+		} else {
+			failedMsgs = append(failedMsgs, grbldMsg)
+		}
+	}
+	for _, grbldMsg := range failedMsgs {
+		garbledMsgs.Failed(grbldMsg)
+	}
+}
diff --git a/network/message/garbled_test.go b/network/message/garbled_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..21098b72bef9a6408c6131c275c021471e79565d
--- /dev/null
+++ b/network/message/garbled_test.go
@@ -0,0 +1,127 @@
+package message
+
+import (
+	"encoding/binary"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/network/internal"
+	"gitlab.com/elixxir/client/network/message/parse"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/csprng"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+type TestListener struct {
+	ch chan bool
+}
+
+// the Hear function is called to exercise the listener, passing in the
+// data as an item
+func (l TestListener) Hear(item message.Receive) {
+	l.ch <- true
+}
+
+// Returns a name, used for debugging
+func (l TestListener) Name() string {
+	return "TEST LISTENER FOR GARBLED MESSAGES"
+}
+
+func TestManager_CheckGarbledMessages(t *testing.T) {
+	sess1 := storage.InitTestingSession(t)
+
+	sess2 := storage.InitTestingSession(t)
+
+	sw := switchboard.New()
+	l := TestListener{
+		ch: make(chan bool),
+	}
+	sw.RegisterListener(sess2.GetUser().TransmissionID, message.Raw, l)
+	comms, err := client.NewClientComms(sess1.GetUser().TransmissionID, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+	i := internal.Internal{
+		Session:          sess1,
+		Switchboard:      sw,
+		Rng:              fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG),
+		Comms:            comms,
+		Health:           nil,
+		TransmissionID:   sess1.GetUser().TransmissionID,
+		Instance:         nil,
+		NodeRegistration: nil,
+	}
+	m := NewManager(i, params.Messages{
+		MessageReceptionBuffLen:        20,
+		MessageReceptionWorkerPoolSize: 20,
+		MaxChecksGarbledMessage:        20,
+		GarbledMessageWait:             time.Hour,
+	}, nil)
+
+	e2ekv := i.Session.E2e()
+	err = e2ekv.AddPartner(sess2.GetUser().TransmissionID, sess2.E2e().GetDHPublicKey(), e2ekv.GetDHPrivateKey(),
+		params.GetDefaultE2ESessionParams(),
+		params.GetDefaultE2ESessionParams())
+	if err != nil {
+		t.Errorf("Failed to add e2e partner: %+v", err)
+		t.FailNow()
+	}
+
+	err = sess2.E2e().AddPartner(sess1.GetUser().TransmissionID,
+		sess1.E2e().GetDHPublicKey(), sess2.E2e().GetDHPrivateKey(),
+		params.GetDefaultE2ESessionParams(),
+		params.GetDefaultE2ESessionParams())
+	if err != nil {
+		t.Errorf("Failed to add e2e partner: %+v", err)
+		t.FailNow()
+	}
+	partner1, err := sess2.E2e().GetPartner(sess1.GetUser().ReceptionID)
+	if err != nil {
+		t.Errorf("Failed to get partner: %+v", err)
+		t.FailNow()
+	}
+
+	msg := format.NewMessage(m.Session.Cmix().GetGroup().GetP().ByteLen())
+
+	key, err := partner1.GetKeyForSending(params.Standard)
+	if err != nil {
+		t.Errorf("failed to get key: %+v", err)
+		t.FailNow()
+	}
+
+	contents := make([]byte, msg.ContentsSize())
+	prng := rand.New(rand.NewSource(42))
+	prng.Read(contents)
+	fmp := parse.FirstMessagePartFromBytes(contents)
+	binary.BigEndian.PutUint32(fmp.Type, uint32(message.Raw))
+	fmp.NumParts[0] = uint8(1)
+	binary.BigEndian.PutUint16(fmp.Len, 256)
+	fmp.Part[0] = 0
+	ts, err := time.Now().MarshalBinary()
+	if err != nil {
+		t.Errorf("failed to martial ts: %+v", err)
+	}
+	copy(fmp.Timestamp, ts)
+	msg.SetContents(fmp.Bytes())
+	encryptedMsg := key.Encrypt(msg)
+	i.Session.GetGarbledMessages().Add(encryptedMsg)
+
+	quitch := make(chan struct{})
+	go m.processGarbledMessages(quitch)
+
+	m.CheckGarbledMessages()
+
+	ticker := time.NewTicker(time.Second)
+	select {
+	case <-ticker.C:
+		t.Error("Didn't hear anything")
+	case <-l.ch:
+		t.Log("Heard something")
+	}
+
+}
diff --git a/network/message/handler.go b/network/message/handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..95606c8a7a204afe27d67903af41e511a2b102a1
--- /dev/null
+++ b/network/message/handler.go
@@ -0,0 +1,137 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/crypto/e2e"
+	fingerprint2 "gitlab.com/elixxir/crypto/fingerprint"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+func (m *Manager) handleMessages(quitCh <-chan struct{}) {
+	done := false
+	for !done {
+		select {
+		case <-quitCh:
+			done = true
+		case bundle := <-m.messageReception:
+			for _, msg := range bundle.Messages {
+				m.handleMessage(msg, bundle.Identity)
+			}
+			bundle.Finish()
+		}
+	}
+
+}
+
+func (m *Manager) handleMessage(ecrMsg format.Message, identity reception.IdentityUse) {
+	// We've done all the networking, now process the message
+	fingerprint := ecrMsg.GetKeyFP()
+
+	e2eKv := m.Session.E2e()
+
+	var sender *id.ID
+	var msg format.Message
+	var encTy message.EncryptionType
+	var err error
+	var relationshipFingerprint []byte
+
+	//check if the identity fingerprint matches
+	forMe, err := fingerprint2.CheckIdentityFP(ecrMsg.GetIdentityFP(),
+		ecrMsg.GetContents(), identity.Source)
+	if err != nil {
+		jww.FATAL.Panicf("Could not check IdentityFingerprint: %+v", err)
+	}
+	if !forMe {
+		if jww.GetLogThreshold() == jww.LevelTrace {
+			expectedFP, _ := fingerprint2.IdentityFP(ecrMsg.GetContents(),
+				identity.Source)
+			jww.TRACE.Printf("Message for %d (%s) failed identity "+
+				"check: %v (expected) vs %v (received)", identity.EphId,
+				identity.Source, expectedFP, ecrMsg.GetIdentityFP())
+		}
+
+		return
+	}
+
+	// try to get the key fingerprint, process as e2e encryption if
+	// the fingerprint is found
+	if key, isE2E := e2eKv.PopKey(fingerprint); isE2E {
+		// Decrypt encrypted message
+		msg, err = key.Decrypt(ecrMsg)
+		// get the sender
+		sender = key.GetSession().GetPartner()
+		relationshipFingerprint = key.GetSession().GetRelationshipFingerprint()
+
+		//drop the message is decryption failed
+		if err != nil {
+			//if decryption failed, print an error
+			jww.WARN.Printf("Failed to decrypt message with fp %s "+
+				"from partner %s: %s", key.Fingerprint(), sender, err)
+			return
+		}
+		//set the type as E2E encrypted
+		encTy = message.E2E
+	} else if isUnencrypted, uSender := e2e.IsUnencrypted(ecrMsg); isUnencrypted {
+		// if the key fingerprint does not match, try to treat it as an
+		// unencrypted message
+		sender = uSender
+		msg = ecrMsg
+		encTy = message.None
+	} else {
+		// if it doesnt match any form of encrypted, hear it as a raw message
+		// and add it to garbled messages to be handled later
+		msg = ecrMsg
+		if err != nil {
+			jww.DEBUG.Printf("Failed to unmarshal ephemeral ID "+
+				"on unknown message: %+v", err)
+		}
+		raw := message.Receive{
+			Payload:     msg.Marshal(),
+			MessageType: message.Raw,
+			Sender:      &id.ID{},
+			EphemeralID: identity.EphId,
+			Timestamp:   time.Time{},
+			Encryption:  message.None,
+			RecipientID: identity.Source,
+		}
+		jww.INFO.Printf("Garbled/RAW Message: keyFP: %v, msgDigest: %s",
+			msg.GetKeyFP(), msg.Digest())
+		m.Session.GetGarbledMessages().Add(msg)
+		m.Switchboard.Speak(raw)
+		return
+	}
+
+	jww.INFO.Printf("Received message of type %s from %s,"+
+		" msgDigest: %s", encTy, sender, ecrMsg.Digest())
+
+	// Process the decrypted/unencrypted message partition, to see if
+	// we get a full message
+	xxMsg, ok := m.partitioner.HandlePartition(sender, encTy, msg.GetContents(),
+		relationshipFingerprint)
+
+	// If the reception completed a message, hear it on the switchboard
+	if ok {
+		//Set the identities
+		xxMsg.RecipientID = identity.Source
+		xxMsg.EphemeralID = identity.EphId
+		xxMsg.Encryption = encTy
+		if xxMsg.MessageType == message.Raw {
+			jww.WARN.Panicf("Recieved a message of type 'Raw' from %s."+
+				"Message Ignored, 'Raw' is a reserved type. Message supressed.",
+				xxMsg.ID)
+		} else {
+			m.Switchboard.Speak(xxMsg)
+		}
+	}
+}
diff --git a/network/message/manager.go b/network/message/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca4a1c0f40bbc57e54925b0c742063df59f34218
--- /dev/null
+++ b/network/message/manager.go
@@ -0,0 +1,74 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	"fmt"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/network/internal"
+	"gitlab.com/elixxir/client/network/message/parse"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/primitives/format"
+)
+
+type Manager struct {
+	param       params.Messages
+	partitioner parse.Partitioner
+	internal.Internal
+
+	messageReception chan Bundle
+	nodeRegistration chan network.NodeGateway
+	networkIsHealthy chan bool
+	triggerGarbled   chan struct{}
+}
+
+func NewManager(internal internal.Internal, param params.Messages,
+	nodeRegistration chan network.NodeGateway) *Manager {
+	dummyMessage := format.NewMessage(internal.Session.Cmix().GetGroup().GetP().ByteLen())
+	m := Manager{
+		param:            param,
+		partitioner:      parse.NewPartitioner(dummyMessage.ContentsSize(), internal.Session),
+		messageReception: make(chan Bundle, param.MessageReceptionBuffLen),
+		networkIsHealthy: make(chan bool, 1),
+		triggerGarbled:   make(chan struct{}, 1),
+		nodeRegistration: nodeRegistration,
+	}
+	m.Internal = internal
+	return &m
+}
+
+//Gets the channel to send received messages on
+func (m *Manager) GetMessageReceptionChannel() chan<- Bundle {
+	return m.messageReception
+}
+
+//Starts all worker pool
+func (m *Manager) StartProcessies() stoppable.Stoppable {
+	multi := stoppable.NewMulti("MessageReception")
+
+	//create the message handler workers
+	for i := uint(0); i < m.param.MessageReceptionWorkerPoolSize; i++ {
+		stop := stoppable.NewSingle(fmt.Sprintf("MessageReception Worker %v", i))
+		go m.handleMessages(stop.Quit())
+		multi.Add(stop)
+	}
+
+	//create the critical messages thread
+	critStop := stoppable.NewSingle("CriticalMessages")
+	go m.processCriticalMessages(critStop.Quit())
+	m.Health.AddChannel(m.networkIsHealthy)
+	multi.Add(critStop)
+
+	//create the garbled messages thread
+	garbledStop := stoppable.NewSingle("GarbledMessages")
+	go m.processGarbledMessages(garbledStop.Quit())
+	multi.Add(garbledStop)
+
+	return multi
+}
diff --git a/network/message/parse/firstMessagePart.go b/network/message/parse/firstMessagePart.go
new file mode 100644
index 0000000000000000000000000000000000000000..958a386992f161f007921476f52eb9749ea628bf
--- /dev/null
+++ b/network/message/parse/firstMessagePart.go
@@ -0,0 +1,100 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package parse
+
+import (
+	"encoding/binary"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"time"
+)
+
+const numPartsLen = 1
+const typeLen = message.TypeLen
+const timestampLen = 15
+const firstHeaderLen = headerLen + numPartsLen + typeLen + timestampLen
+
+type firstMessagePart struct {
+	messagePart
+	NumParts  []byte
+	Type      []byte
+	Timestamp []byte
+}
+
+//creates a new first message part for the passed in contents. Does no length checks
+func newFirstMessagePart(mt message.Type, id uint32, numParts uint8,
+	timestamp time.Time, contents []byte) firstMessagePart {
+	//create the message structure
+	data := make([]byte, len(contents)+firstHeaderLen)
+	m := FirstMessagePartFromBytes(data)
+
+	//Put the message type in the message
+	binary.BigEndian.PutUint32(m.Type, uint32(mt))
+
+	//Add the message ID
+	binary.BigEndian.PutUint32(m.Id, id)
+
+	// Add the part number to the message, its always zero because this is the
+	// first part. Because the default is zero this step could be skipped, but\
+	// keep it in the code for clarity
+	m.Part[0] = 0
+
+	// Add the number of parts to the message
+	m.NumParts[0] = numParts
+
+	//Serialize and add the timestamp to the payload
+	timestampBytes, err := timestamp.MarshalBinary()
+	if err != nil {
+		jww.FATAL.Panicf("Failed to create firstMessagePart: %s", err.Error())
+	}
+	copy(m.Timestamp, timestampBytes)
+
+	//set the contents length
+	binary.BigEndian.PutUint16(m.Len, uint16(len(contents)))
+
+	//add the contents to the payload
+	copy(m.Contents[:len(contents)], contents)
+
+	return m
+}
+
+// Builds a first message part mapped to the passed in data slice. Mapped by
+// reference, a copy is not made.
+func FirstMessagePartFromBytes(data []byte) firstMessagePart {
+	m := firstMessagePart{
+		messagePart: messagePart{
+			Data:     data,
+			Id:       data[:idLen],
+			Part:     data[idLen : idLen+partLen],
+			Len:      data[idLen+partLen : idLen+partLen+lenLen],
+			Contents: data[idLen+partLen+numPartsLen+typeLen+timestampLen+lenLen:],
+		},
+		NumParts:  data[idLen+partLen+lenLen : idLen+partLen+numPartsLen+lenLen],
+		Type:      data[idLen+partLen+numPartsLen+lenLen : idLen+partLen+numPartsLen+typeLen+lenLen],
+		Timestamp: data[idLen+partLen+numPartsLen+typeLen+lenLen : idLen+partLen+numPartsLen+typeLen+timestampLen+lenLen],
+	}
+	return m
+}
+
+func (m firstMessagePart) GetType() message.Type {
+	return message.Type(binary.BigEndian.Uint32(m.Type))
+}
+
+func (m firstMessagePart) GetNumParts() uint8 {
+	return m.NumParts[0]
+}
+
+func (m firstMessagePart) GetTimestamp() (time.Time, error) {
+	var t time.Time
+	err := t.UnmarshalBinary(m.Timestamp)
+	return t, err
+}
+
+func (m firstMessagePart) Bytes() []byte {
+	return m.Data
+}
diff --git a/network/message/parse/firstMessagePart_test.go b/network/message/parse/firstMessagePart_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..676723247fec562607d43f9a29dec827890c76a8
--- /dev/null
+++ b/network/message/parse/firstMessagePart_test.go
@@ -0,0 +1,100 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package parse
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Expected firstMessagePart for checking against, generated by fmp in TestNewFirstMessagePart
+var efmp = firstMessagePart{
+	messagePart: messagePart{
+		Data: []byte{0, 0, 4, 53, 0, 0, 13, 2, 0, 0, 0, 2, 1, 0, 0, 0, 14, 215, 133, 90, 117, 0, 0, 0, 0, 255, 255,
+			116, 101, 115, 116, 105, 110, 103, 115, 116, 114, 105, 110, 103},
+		Id:       []byte{0, 0, 4, 53},
+		Part:     []byte{0},
+		Len:      []byte{0, 13},
+		Contents: []byte{116, 101, 115, 116, 105, 110, 103, 115, 116, 114, 105, 110, 103},
+	},
+	NumParts:  []byte{2},
+	Type:      []byte{0, 0, 0, 2},
+	Timestamp: []byte{1, 0, 0, 0, 14, 215, 133, 90, 117, 0, 0, 0, 0, 255, 255},
+}
+
+// Test that newFirstMessagePart returns a correctly made firstMessagePart
+func TestNewFirstMessagePart(t *testing.T) {
+	fmp := newFirstMessagePart(
+		message.Text,
+		1077,
+		2,
+		time.Unix(1609786229, 0).UTC(),
+		[]byte{'t', 'e', 's', 't', 'i', 'n', 'g',
+			's', 't', 'r', 'i', 'n', 'g'},
+	)
+
+	gotTime, err := fmp.GetTimestamp()
+	if err != nil {
+		t.Error(err)
+	}
+	expectedTime, err := fmp.GetTimestamp()
+	if err != nil {
+		t.Error(err)
+	}
+	if !gotTime.Equal(expectedTime) {
+		t.Errorf("Got time: %v, expected time: %v", gotTime, expectedTime)
+	}
+
+	if !reflect.DeepEqual(fmp, efmp) {
+		t.Errorf("Expected and got firstMessagePart did not match.\n\tGot: %#v\n\tExpected: %#v", fmp, efmp)
+	}
+}
+
+// Test that FirstMessagePartFromBytes returns a correctly made firstMessagePart from the bytes of one
+func TestFirstMessagePartFromBytes(t *testing.T) {
+	fmp := FirstMessagePartFromBytes(efmp.Data)
+
+	if !reflect.DeepEqual(fmp, efmp) {
+		t.Error("Expected and got firstMessagePart did not match")
+	}
+}
+
+// Test that GetType returns the correct type for a firstMessagePart
+func TestFirstMessagePart_GetType(t *testing.T) {
+	if efmp.GetType() != message.Text {
+		t.Errorf("Got %v, expected %v", efmp.GetType(), message.Text)
+	}
+}
+
+// Test that GetNumParts returns the correct number of parts for a firstMessagePart
+func TestFirstMessagePart_GetNumParts(t *testing.T) {
+	if efmp.GetNumParts() != 2 {
+		t.Errorf("Got %v, expected %v", efmp.GetNumParts(), 2)
+	}
+}
+
+// Test that GetTimestamp returns the correct timestamp for a firstMessagePart
+func TestFirstMessagePart_GetTimestamp(t *testing.T) {
+	et, err := efmp.GetTimestamp()
+	if err != nil {
+		t.Error(err)
+	}
+	if !time.Unix(1609786229, 0).Equal(et) {
+		t.Errorf("Got %v, expected %v", et, time.Unix(1609786229, 0))
+	}
+}
+
+// Test that GetTimestamp returns the correct bytes for a firstMessagePart
+func TestFirstMessagePart_Bytes(t *testing.T) {
+	if bytes.Compare(efmp.Bytes(), efmp.Data) != 0 {
+		t.Errorf("Got %v, expected %v", efmp.Bytes(), efmp.Data)
+	}
+}
diff --git a/network/message/parse/messagePart.go b/network/message/parse/messagePart.go
new file mode 100644
index 0000000000000000000000000000000000000000..815544be80370e2c4f66482c903ae0cbe9e9ad92
--- /dev/null
+++ b/network/message/parse/messagePart.go
@@ -0,0 +1,83 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package parse
+
+import (
+	"encoding/binary"
+)
+
+const idLen = 4
+const partLen = 1
+const lenLen = 2
+const headerLen = idLen + partLen + lenLen
+
+type messagePart struct {
+	Data     []byte
+	Id       []byte
+	Part     []byte
+	Len      []byte
+	Contents []byte
+}
+
+//creates a new message part for the passed in contents. Does no length checks
+func newMessagePart(id uint32, part uint8, contents []byte) messagePart {
+	//create the message structure
+	data := make([]byte, len(contents)+headerLen)
+	m := MessagePartFromBytes(data)
+
+	//add the message ID to the message
+	binary.BigEndian.PutUint32(m.Id, id)
+
+	//set the message part number
+	m.Part[0] = part
+
+	//set the contents length
+	binary.BigEndian.PutUint16(m.Len, uint16(len(contents)))
+
+	//copy the contents into the message
+	copy(m.Contents[:len(contents)], contents)
+	return m
+}
+
+// Builds a Message part mapped to the passed in data slice. Mapped by
+// reference, a copy is not made.
+func MessagePartFromBytes(data []byte) messagePart {
+	m := messagePart{
+		Data:     data,
+		Id:       data[:idLen],
+		Part:     data[idLen : idLen+partLen],
+		Len:      data[idLen+partLen : idLen+partLen+lenLen],
+		Contents: data[idLen+partLen+lenLen:],
+	}
+	return m
+}
+
+func (m messagePart) GetID() uint32 {
+	return binary.BigEndian.Uint32(m.Id)
+}
+
+func (m messagePart) GetPart() uint8 {
+	return m.Part[0]
+}
+
+func (m messagePart) GetContents() []byte {
+	return m.Contents
+}
+
+func (m messagePart) GetSizedContents() []byte {
+	size := m.GetContentsLength()
+	return m.Contents[:size]
+}
+
+func (m messagePart) GetContentsLength() int {
+	return int(binary.BigEndian.Uint16(m.Len))
+}
+
+func (m messagePart) Bytes() []byte {
+	return m.Data
+}
diff --git a/network/message/parse/messagePart_test.go b/network/message/parse/messagePart_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ab1db0213c05a18f7615a836e627350a5879616
--- /dev/null
+++ b/network/message/parse/messagePart_test.go
@@ -0,0 +1,70 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package parse
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+)
+
+// Expected messagePart for checking against, generated by gotmp in Test_newMessagePart
+var emp = messagePart{
+	Data: []uint8{0x0, 0x0, 0x0, 0x20, 0x6, 0x0, 0x7, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67},
+	Id:   []uint8{0x0, 0x0, 0x0, 0x20}, Part: []uint8{0x6},
+	Len:      []uint8{0x0, 0x7},
+	Contents: []uint8{0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67},
+}
+
+// This tests that a new function part is successfully created
+func Test_newMessagePart(t *testing.T) {
+	gotmp := newMessagePart(32, 6, []byte{'t', 'e', 's', 't', 'i', 'n', 'g'})
+	if !reflect.DeepEqual(gotmp, emp) {
+		t.Errorf("MessagePart received and MessagePart expected do not match.\n\tGot: %#v\n\tExpected: %#v", gotmp, emp)
+	}
+}
+
+// Test that GetID returns the correct ID
+func TestMessagePart_GetID(t *testing.T) {
+	if emp.GetID() != 32 {
+		t.Errorf("received and expected do not match."+
+			"\n\tGot: %#v\n\tExpected: %#v", emp.GetID(), 32)
+	}
+}
+
+// Test that GetPart returns the correct part number
+func TestMessagePart_GetPart(t *testing.T) {
+	if emp.GetPart() != 6 {
+		t.Errorf("received and expected do not match."+
+			"\n\tGot: %#v\n\tExpected: %#v", emp.GetPart(), 6)
+	}
+}
+
+// Test that GetContents returns the message contests
+func TestMessagePart_GetContents(t *testing.T) {
+	if bytes.Compare(emp.GetContents(), []byte{'t', 'e', 's', 't', 'i', 'n', 'g'}) != 0 {
+		t.Errorf("received and expected do not match."+
+			"\n\tGot: %#v\n\tExpected: %#v", emp.GetContents(), 6)
+	}
+}
+
+// Test that GetSizedContents returns the message contests
+func TestMessagePart_GetSizedContents(t *testing.T) {
+	if bytes.Compare(emp.GetSizedContents(), []byte{'t', 'e', 's', 't', 'i', 'n', 'g'}) != 0 {
+		t.Errorf("received and expected do not match."+
+			"\n\tGot: %#v\n\tExpected: %#v", emp.GetSizedContents(), 6)
+	}
+}
+
+// Test that GetContentsLength returns the message length
+func TestMessagePart_GetContentsLength(t *testing.T) {
+	if emp.GetContentsLength() != 7 {
+		t.Errorf("received and expected do not match."+
+			"\n\tGot: %#v\n\tExpected: %#v", emp.GetContentsLength(), 7)
+	}
+}
diff --git a/network/message/parse/partition.go b/network/message/parse/partition.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b690a5bef804dd269cd62cbcc0ecac6d2667439
--- /dev/null
+++ b/network/message/parse/partition.go
@@ -0,0 +1,114 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package parse
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const MaxMessageParts = 255
+
+type Partitioner struct {
+	baseMessageSize   int
+	firstContentsSize int
+	partContentsSize  int
+	deltaFirstPart    int
+	maxSize           int
+	session           *storage.Session
+}
+
+func NewPartitioner(messageSize int, session *storage.Session) Partitioner {
+	p := Partitioner{
+		baseMessageSize:   messageSize,
+		firstContentsSize: messageSize - firstHeaderLen,
+		partContentsSize:  messageSize - headerLen,
+		deltaFirstPart:    firstHeaderLen - headerLen,
+		session:           session,
+	}
+	p.maxSize = p.firstContentsSize + (MaxMessageParts-1)*p.partContentsSize
+	return p
+}
+
+func (p Partitioner) Partition(recipient *id.ID, mt message.Type,
+	timestamp time.Time, payload []byte) ([][]byte, uint64, error) {
+
+	if len(payload) > p.maxSize {
+		return nil, 0, errors.Errorf("Payload is too long, max payload "+
+			"length is %v, received %v", p.maxSize, len(payload))
+	}
+
+	//Get the ID of the sent message
+	fullMessageID, messageID := p.session.Conversations().Get(recipient).GetNextSendID()
+
+	// get the number of parts of the message. This equates to just a linear
+	// equation
+	numParts := uint8((len(payload) + p.deltaFirstPart + p.partContentsSize - 1) / p.partContentsSize)
+	parts := make([][]byte, numParts)
+
+	//Create the first message part
+	var sub []byte
+	sub, payload = splitPayload(payload, p.firstContentsSize)
+	parts[0] = newFirstMessagePart(mt, messageID, numParts, timestamp, sub).Bytes()
+
+	//create all subsiquent message parts
+	for i := uint8(1); i < numParts; i++ {
+		sub, payload = splitPayload(payload, p.partContentsSize)
+		parts[i] = newMessagePart(messageID, i, sub).Bytes()
+	}
+
+	return parts, fullMessageID, nil
+}
+
+func (p Partitioner) HandlePartition(sender *id.ID, e message.EncryptionType,
+	contents []byte, relationshipFingerprint []byte) (message.Receive, bool) {
+
+	//If it is the first message in a set, handle it as so
+	if isFirst(contents) {
+		//decode the message structure
+		fm := FirstMessagePartFromBytes(contents)
+		timestamp, err := fm.GetTimestamp()
+		if err != nil {
+			jww.FATAL.Panicf("Failed Handle Partition, failed to get "+
+				"timestamp message from %s messageID %v: %s", sender,
+				fm.Timestamp, err)
+		}
+
+		//Handle the message ID
+		messageID := p.session.Conversations().Get(sender).
+			ProcessReceivedMessageID(fm.GetID())
+
+		//Return the
+		return p.session.Partition().AddFirst(sender, fm.GetType(),
+			messageID, fm.GetPart(), fm.GetNumParts(), timestamp,
+			fm.GetSizedContents(), relationshipFingerprint)
+		//If it is a subsiquent message part, handle it as so
+	} else {
+		mp := MessagePartFromBytes(contents)
+		messageID := p.session.Conversations().Get(sender).
+			ProcessReceivedMessageID(mp.GetID())
+
+		return p.session.Partition().Add(sender, messageID, mp.GetPart(),
+			mp.GetSizedContents(), relationshipFingerprint)
+	}
+}
+
+func splitPayload(payload []byte, length int) ([]byte, []byte) {
+	if len(payload) < length {
+		return payload, payload
+	}
+	return payload[:length], payload[length:]
+}
+
+func isFirst(payload []byte) bool {
+	return payload[idLen] == 0
+}
diff --git a/network/message/parse/partition_test.go b/network/message/parse/partition_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..59963cfefc57ab70d8d76e07fd384d8aae293a40
--- /dev/null
+++ b/network/message/parse/partition_test.go
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package parse
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+var ipsumTestStr = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sit amet euismod est. Donec dolor " +
+	"neque, efficitur et interdum eu, lacinia quis mi. Duis bibendum elit ac lacus finibus pharetra. Suspendisse " +
+	"blandit erat in odio faucibus consectetur. Suspendisse sed consequat purus. Curabitur fringilla mi sit amet odio " +
+	"interdum suscipit. Etiam vitae dui posuere, congue mi a, convallis odio. In commodo risus at lorem volutpat " +
+	"placerat. In cursus magna purus, suscipit dictum lorem aliquam non. Praesent efficitur."
+
+// Test that NewPartitioner outputs a correctly made Partitioner
+func TestNewPartitioner(t *testing.T) {
+	storeSession := storage.InitTestingSession(t)
+	p := NewPartitioner(4096, storeSession)
+
+	if p.baseMessageSize != 4096 {
+		t.Errorf("baseMessageSize content mismatch"+
+			"\n\texpected: %v\n\treceived: %v",
+			4096, p.baseMessageSize)
+	}
+
+	if p.deltaFirstPart != 20 {
+		t.Errorf("deltaFirstPart content mismatch"+
+			"\n\texpected: %v\n\treceived: %v",
+			20, p.deltaFirstPart)
+	}
+
+	if p.firstContentsSize != 4069 {
+		t.Errorf("firstContentsSize content mismatch"+
+			"\n\texpected: %v\n\treceived: %v",
+			4069, p.firstContentsSize)
+	}
+
+	if p.maxSize != 1042675 {
+		t.Errorf("maxSize content mismatch"+
+			"\n\texpected: %v\n\treceived: %v",
+			1042675, p.maxSize)
+	}
+
+	if p.partContentsSize != 4089 {
+		t.Errorf("partContentsSize content mismatch"+
+			"\n\texpected: %v\n\treceived: %v",
+			4089, p.partContentsSize)
+	}
+
+	if p.session != storeSession {
+		t.Errorf("session content mismatch")
+	}
+}
+
+// Test that no error is returned running Partition
+func TestPartitioner_Partition(t *testing.T) {
+	storeSession := storage.InitTestingSession(t)
+	p := NewPartitioner(len(ipsumTestStr), storeSession)
+
+	_, _, err := p.Partition(&id.DummyUser, message.Text,
+		time.Now(), []byte(ipsumTestStr))
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+// Test that HandlePartition can handle a message part
+func TestPartitioner_HandlePartition(t *testing.T) {
+	storeSession := storage.InitTestingSession(t)
+	p := NewPartitioner(len(ipsumTestStr), storeSession)
+
+	m := newMessagePart(1107, 1, []byte(ipsumTestStr))
+
+	_, _ = p.HandlePartition(
+		&id.DummyUser,
+		message.None,
+		m.Bytes(),
+		[]byte{'t', 'e', 's', 't', 'i', 'n', 'g',
+			's', 't', 'r', 'i', 'n', 'g'},
+	)
+}
+
+// Test that HandlePartition can handle a first message part
+func TestPartitioner_HandleFirstPartition(t *testing.T) {
+	storeSession := storage.InitTestingSession(t)
+	p := NewPartitioner(len(ipsumTestStr), storeSession)
+
+	m := newFirstMessagePart(message.Text, 1107, 1, time.Now(), []byte(ipsumTestStr))
+
+	_, _ = p.HandlePartition(
+		&id.DummyUser,
+		message.None,
+		m.Bytes(),
+		[]byte{'t', 'e', 's', 't', 'i', 'n', 'g',
+			's', 't', 'r', 'i', 'n', 'g'},
+	)
+}
diff --git a/network/message/sendCmix.go b/network/message/sendCmix.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a5892da077268b3ca9b5881e037fad7842480e3
--- /dev/null
+++ b/network/message/sendCmix.go
@@ -0,0 +1,268 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	"github.com/golang-collections/collections/set"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/crypto/fingerprint"
+	"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"
+	"strings"
+	"time"
+)
+
+// interface for SendCMIX comms; allows mocking this in testing
+type sendCmixCommsInterface interface {
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+	SendPutMessage(host *connect.Host, message *pb.GatewaySlot) (*pb.GatewaySlotResponse, error)
+}
+
+const sendTimeBuffer = 100 * time.Millisecond
+
+// WARNING: Potentially Unsafe
+// Public manager function to send a message over CMIX
+func (m *Manager) SendCMIX(msg format.Message, recipient *id.ID, param params.CMIX) (id.Round, ephemeral.Id, error) {
+	return sendCmixHelper(msg, recipient, param, m.Instance, m.Session, m.nodeRegistration, m.Rng, m.TransmissionID, m.Comms)
+}
+
+// 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
+// Internal SendCmix which bypasses the network check, will attempt to send to
+// the network without checking state. It has a built in retry system which can
+// be configured through the params object.
+// If the message is successfully sent, the id of the round sent it is returned,
+// which can be registered with the network instance to get a callback on
+// its status
+func sendCmixHelper(msg format.Message, recipient *id.ID, param params.CMIX, instance *network.Instance,
+	session *storage.Session, nodeRegistration chan network.NodeGateway, rng *fastRNG.StreamGenerator, senderId *id.ID,
+	comms sendCmixCommsInterface) (id.Round, ephemeral.Id, error) {
+
+	timeStart := time.Now()
+	attempted := set.New()
+
+	jww.INFO.Printf("Looking for round to send cMix message to %s "+
+		"(msgDigest: %s)", recipient, msg.Digest())
+
+	for numRoundTries := uint(0); numRoundTries < param.RoundTries; numRoundTries++ {
+		elapsed := time.Now().Sub(timeStart)
+
+		if elapsed > param.Timeout {
+			jww.INFO.Printf("No rounds to send to %s (msgDigest: %s) "+
+				"were found before timeout %s", recipient, msg.Digest(),
+				param.Timeout)
+			return 0, ephemeral.Id{}, errors.New("Sending cmix message timed out")
+		}
+		if numRoundTries > 0 {
+			jww.INFO.Printf("Attempt %d to find round to send message "+
+				"to %s (msgDigest: %s)", numRoundTries+1, recipient,
+				msg.Digest())
+		}
+
+		remainingTime := param.Timeout - elapsed
+		//find the best round to send to, excluding attempted rounds
+		bestRound, _ := instance.GetWaitingRounds().GetUpcomingRealtime(remainingTime, attempted)
+		if bestRound == nil {
+			continue
+		}
+
+		//add the round on to the list of attempted so it is not tried again
+		attempted.Insert(bestRound)
+
+		//compute if the round is too close to send to
+		roundCutoffTime := time.Unix(0,
+			int64(bestRound.Timestamps[states.QUEUED]))
+		roundCutoffTime.Add(sendTimeBuffer)
+		now := time.Now()
+
+		jww.DEBUG.Printf("Found round %d to send to %s (msgDigest: %s)",
+			bestRound.ID, recipient, msg.Digest())
+
+		if now.After(roundCutoffTime) {
+			jww.WARN.Printf("Round %d for sending to %s (msgDigest: %s) "+
+				"received which has already started realtime: \n\t started: "+
+				"%s \n\t now: %s", bestRound.ID, recipient, msg.Digest(),
+				roundCutoffTime, now)
+			continue
+		}
+
+		//set the ephemeral ID
+		ephID, _, _, err := ephemeral.GetId(recipient,
+			uint(bestRound.AddressSpaceSize),
+			int64(bestRound.Timestamps[states.QUEUED]))
+		if err != nil {
+			jww.FATAL.Panicf("Failed to generate ephemeral ID when "+
+				"sending to %s (msgDigest: %s):  %+v", err, recipient,
+				msg.Digest())
+		}
+
+		stream := rng.GetStream()
+		ephIdFilled, err := ephID.Fill(uint(bestRound.AddressSpaceSize), stream)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to obfuscate the ephemeralID when "+
+				"sending to %s (msgDigest: %s): %+v", recipient, msg.Digest(),
+				err)
+		}
+		stream.Close()
+
+		msg.SetEphemeralRID(ephIdFilled[:])
+
+		//set the identity fingerprint
+		ifp, err := fingerprint.IdentityFP(msg.GetContents(), recipient)
+		if err != nil {
+			jww.FATAL.Panicf("failed to generate the Identity "+
+				"fingerprint due to unrecoverable error when sending to %s "+
+				"(msgDigest: %s): %+v", recipient, msg.Digest(), err)
+		}
+
+		msg.SetIdentityFP(ifp)
+
+		//build the topology
+		idList, err := id.NewIDListFromBytes(bestRound.Topology)
+		if err != nil {
+			jww.ERROR.Printf("Failed to use topology for round %d when "+
+				"sending to %s (msgDigest: %s): %+v", bestRound.ID,
+				recipient, msg.Digest(), err)
+			continue
+		}
+		topology := connect.NewCircuit(idList)
+		//get they keys for the round, reject if any nodes do not have
+		//keying relationships
+		roundKeys, missingKeys := session.Cmix().GetRoundKeys(topology)
+		if len(missingKeys) > 0 {
+			jww.WARN.Printf("Failed to send on round %d to %s "+
+				"(msgDigest: %s) due to missing relationships with nodes: %s",
+				bestRound.ID, recipient, msg.Digest(), missingKeys)
+			go handleMissingNodeKeys(instance, nodeRegistration, missingKeys)
+			time.Sleep(param.RetryDelay)
+			continue
+		}
+
+		//get the gateway to transmit to
+		firstGateway := topology.GetNodeAtIndex(0).DeepCopy()
+		firstGateway.SetType(id.Gateway)
+
+		transmitGateway, ok := comms.GetHost(firstGateway)
+		if !ok {
+			jww.ERROR.Printf("Failed to get host for gateway %s when "+
+				"sending to %s (msgDigest: %s)", transmitGateway, recipient,
+				msg.Digest())
+			time.Sleep(param.RetryDelay)
+			continue
+		}
+
+		//encrypt the message
+		stream = rng.GetStream()
+		salt := make([]byte, 32)
+		_, err = stream.Read(salt)
+		stream.Close()
+
+		if err != nil {
+			jww.ERROR.Printf("Failed to generate salt when sending to "+
+				"%s (msgDigest: %s): %+v", recipient, msg.Digest(), err)
+			return 0, ephemeral.Id{}, errors.WithMessage(err,
+				"Failed to generate salt, this should never happen")
+		}
+
+		encMsg, kmacs := roundKeys.Encrypt(msg, salt, id.Round(bestRound.ID))
+
+		//build the message payload
+		msgPacket := &pb.Slot{
+			SenderID: senderId.Bytes(),
+			PayloadA: encMsg.GetPayloadA(),
+			PayloadB: encMsg.GetPayloadB(),
+			Salt:     salt,
+			KMACs:    kmacs,
+		}
+
+		//create the wrapper to the gateway
+		wrappedMsg := &pb.GatewaySlot{
+			Message: msgPacket,
+			RoundID: bestRound.ID,
+		}
+		//Add the mac proving ownership
+		wrappedMsg.MAC = roundKeys.MakeClientGatewayKey(salt,
+			network.GenerateSlotDigest(wrappedMsg))
+
+		jww.INFO.Printf("Sending to EphID %d (%s) on round %d, "+
+			"(msgDigest: %s, ecrMsgDigest: %s) via gateway %s",
+			ephID.Int64(), recipient, bestRound.ID, msg.Digest(),
+			encMsg.Digest(), transmitGateway.GetId())
+		//		//Send the payload
+		gwSlotResp, err := comms.SendPutMessage(transmitGateway, wrappedMsg)
+		//if the comm errors or the message fails to send, continue retrying.
+		//return if it sends properly
+		if err != nil {
+			if strings.Contains(err.Error(),
+				"try a different round.") {
+				jww.WARN.Printf("Failed to send to %s (msgDigest: %s) "+
+					"due to round error with round %d, retrying: %+v",
+					recipient, msg.Digest(), bestRound.ID, err)
+				continue
+			}else if strings.Contains(err.Error(),
+				"Could not authenticate client. Is the client registered " +
+				"with this node?"){
+				jww.WARN.Printf("Failed to send to %s (msgDigest: %s) "+
+					"via %s due to failed authentication: %s",
+					recipient, msg.Digest(), transmitGateway.GetId(), err)
+				//if we failed to send due to the gateway not recognizing our
+				// authorization, renegotiate with the node to refresh it
+				nodeID := transmitGateway.GetId().DeepCopy()
+				nodeID.SetType(id.Node)
+				//delete the keys
+				session.Cmix().Remove(nodeID)
+				//trigger
+				go handleMissingNodeKeys(instance, nodeRegistration, []*id.ID{nodeID})
+				continue
+			}
+			jww.ERROR.Printf("Failed to send to EphID %d (%s) on "+
+				"round %d, bailing: %+v", ephID.Int64(), recipient,
+				bestRound.ID, err)
+			return 0, ephemeral.Id{}, errors.WithMessage(err, "Failed to put cmix message")
+		} else if gwSlotResp.Accepted {
+			jww.INFO.Printf("Successfully sent to EphID %v (source: %s) "+
+				"in round %d", ephID.Int64(), recipient, bestRound.ID)
+			return id.Round(bestRound.ID), ephID, nil
+		} else {
+			jww.FATAL.Panicf("Gateway %s returned no error, but failed "+
+				"to accept message when sending to EphID %d (%s) on round %d",
+				transmitGateway.GetId(), ephID.Int64(), recipient, bestRound.ID)
+		}
+	}
+	return 0, ephemeral.Id{}, errors.New("failed to send the message, " +
+		"unknown error")
+}
+
+// Signals to the node registration thread to register a node if keys are
+// missing. Identity is triggered automatically when the node is first seen,
+// so this should on trigger on rare events.
+func handleMissingNodeKeys(instance *network.Instance,
+	newNodeChan chan network.NodeGateway, nodes []*id.ID) {
+	for _, n := range nodes {
+		ng, err := instance.GetNodeAndGateway(n)
+		if err != nil {
+			jww.ERROR.Printf("Node contained in round cannot be found: %s", err)
+			continue
+		}
+		select {
+		case newNodeChan <- ng:
+		default:
+			jww.ERROR.Printf("Failed to send node registration for %s", n)
+		}
+
+	}
+}
diff --git a/network/message/sendCmix_test.go b/network/message/sendCmix_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7ea83223a809ed5e58a1e5a4e1ec6961b1755776
--- /dev/null
+++ b/network/message/sendCmix_test.go
@@ -0,0 +1,158 @@
+package message
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/network/internal"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e"
+	"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/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"testing"
+	"time"
+)
+
+type MockSendCMIXComms struct {
+	t *testing.T
+}
+
+func (mc *MockSendCMIXComms) GetHost(hostId *id.ID) (*connect.Host, bool) {
+	nid1 := id.NewIdFromString("zezima", id.Node, mc.t)
+	gwid := nid1.DeepCopy()
+	gwid.SetType(id.Gateway)
+	h, _ := connect.NewHost(gwid, "0.0.0.0", []byte(""), connect.HostParams{
+		MaxRetries:  0,
+		AuthEnabled: false,
+	})
+	return h, true
+}
+func (mc *MockSendCMIXComms) SendPutMessage(host *connect.Host, message *mixmessages.GatewaySlot) (*mixmessages.GatewaySlotResponse, error) {
+	return &mixmessages.GatewaySlotResponse{
+		Accepted: true,
+		RoundID:  3,
+	}, nil
+}
+
+func Test_attemptSendCmix(t *testing.T) {
+	sess1 := storage.InitTestingSession(t)
+
+	sess2 := storage.InitTestingSession(t)
+
+	sw := switchboard.New()
+	l := TestListener{
+		ch: make(chan bool),
+	}
+	sw.RegisterListener(sess2.GetUser().TransmissionID, message.Raw, l)
+	comms, err := client.NewClientComms(sess1.GetUser().TransmissionID, nil, nil, nil)
+	if err != nil {
+		t.Errorf("Failed to start client comms: %+v", err)
+	}
+	inst, err := network.NewInstanceTesting(comms.ProtoComms, getNDF(), nil, nil, nil, t)
+	if err != nil {
+		t.Errorf("Failed to start instance: %+v", err)
+	}
+	now := time.Now()
+	nid1 := id.NewIdFromString("zezima", id.Node, 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))
+
+	timestamps := []uint64{
+		uint64(now.Add(-30 * time.Second).UnixNano()), //PENDING
+		uint64(now.Add(-25 * time.Second).UnixNano()), //PRECOMPUTING
+		uint64(now.Add(-5 * time.Second).UnixNano()),  //STANDBY
+		uint64(now.Add(5 * time.Second).UnixNano()),   //QUEUED
+		0} //REALTIME
+
+	inst.GetWaitingRounds().Insert(&mixmessages.RoundInfo{
+		ID:                         3,
+		UpdateID:                   0,
+		State:                      uint32(states.QUEUED),
+		BatchSize:                  0,
+		Topology:                   [][]byte{nid1.Marshal(), nid2.Marshal(), nid3.Marshal()},
+		Timestamps:                 timestamps,
+		Errors:                     nil,
+		ClientErrors:               nil,
+		ResourceQueueTimeoutMillis: 0,
+		Signature:                  nil,
+		AddressSpaceSize:           4,
+	})
+	i := internal.Internal{
+		Session:          sess1,
+		Switchboard:      sw,
+		Rng:              fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG),
+		Comms:            comms,
+		Health:           nil,
+		TransmissionID:   sess1.GetUser().TransmissionID,
+		Instance:         inst,
+		NodeRegistration: nil,
+	}
+	m := NewManager(i, params.Messages{
+		MessageReceptionBuffLen:        20,
+		MessageReceptionWorkerPoolSize: 20,
+		MaxChecksGarbledMessage:        20,
+		GarbledMessageWait:             time.Hour,
+	}, nil)
+	msgCmix := format.NewMessage(m.Session.Cmix().GetGroup().GetP().ByteLen())
+	msgCmix.SetContents([]byte("test"))
+	e2e.SetUnencrypted(msgCmix, m.Session.User().GetCryptographicIdentity().GetTransmissionID())
+	_, _, err = sendCmixHelper(msgCmix, sess2.GetUser().ReceptionID, params.GetDefaultCMIX(),
+		m.Instance, m.Session, m.nodeRegistration, m.Rng,
+		m.TransmissionID, &MockSendCMIXComms{t: t})
+	if err != nil {
+		t.Errorf("Failed to sendcmix: %+v", err)
+	}
+}
+
+func getNDF() *ndf.NetworkDefinition {
+	return &ndf.NetworkDefinition{
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}
diff --git a/network/message/sendE2E.go b/network/message/sendE2E.go
new file mode 100644
index 0000000000000000000000000000000000000000..14e6708cf83d05268ad27d738598b4f76d002229
--- /dev/null
+++ b/network/message/sendE2E.go
@@ -0,0 +1,118 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package message
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/keyExchange"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+func (m *Manager) SendE2E(msg message.Send, param params.E2E) ([]id.Round, e2e.MessageID, error) {
+	if msg.MessageType == message.Raw {
+		return nil, e2e.MessageID{}, errors.Errorf("Raw (%d) is a reserved "+
+			"message type", msg.MessageType)
+	}
+	//timestamp the message
+	ts := time.Now()
+
+	//partition the message
+	partitions, internalMsgId, err := m.partitioner.Partition(msg.Recipient, msg.MessageType, ts,
+		msg.Payload)
+	if err != nil {
+		return nil, e2e.MessageID{}, errors.WithMessage(err, "failed to send unsafe message")
+	}
+
+	//encrypt then send the partitions over cmix
+	roundIds := make([]id.Round, len(partitions))
+	errCh := make(chan error, len(partitions))
+
+	// get the key manager for the partner
+	partner, err := m.Session.E2e().GetPartner(msg.Recipient)
+	if err != nil {
+		return nil, e2e.MessageID{}, errors.WithMessagef(err, "Could not send End to End encrypted "+
+			"message, no relationship found with %s", msg.Recipient)
+	}
+
+	wg := sync.WaitGroup{}
+
+	jww.INFO.Printf("E2E sending %d messages to %s",
+		len(partitions), msg.Recipient)
+
+	for i, p := range partitions {
+		//create the cmix message
+		msgCmix := format.NewMessage(m.Session.Cmix().GetGroup().GetP().ByteLen())
+		msgCmix.SetContents(p)
+
+		//get a key to end to end encrypt
+		key, err := partner.GetKeyForSending(param.Type)
+		keyTries := 0
+		for err != nil && keyTries < param.RetryCount {
+			jww.WARN.Printf("Out of sending keys for %s "+
+				"(msgDigest: %s, partition: %d), this can "+
+				"happen when sending messages faster than "+
+				"the client can negotiate keys. Please "+
+				"adjust your e2e key parameters",
+				msg.Recipient, msgCmix.Digest(), i)
+			keyTries++
+			time.Sleep(param.RetryDelay)
+			key, err = partner.GetKeyForSending(param.Type)
+		}
+		if err != nil {
+			return nil, e2e.MessageID{}, errors.WithMessagef(err, "Failed to get key "+
+				"for end to end encryption")
+		}
+
+		//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())
+
+		//send the cmix message, each partition in its own thread
+		wg.Add(1)
+		go func(i int) {
+			var err error
+			roundIds[i], _, err = m.SendCMIX(msgEnc, msg.Recipient, param.CMIX)
+			if err != nil {
+				errCh <- err
+			}
+			wg.Done()
+		}(i)
+	}
+
+	// while waiting check if any rekeys need to happen and trigger them. This
+	// can happen now because the key popping happens in this thread,
+	// only the sending is parallelized
+	keyExchange.CheckKeyExchanges(m.Instance, m.SendE2E, m.Session, partner, 1*time.Minute)
+
+	wg.Wait()
+
+	//see if any parts failed to send
+	numFail, errRtn := getSendErrors(errCh)
+	if numFail > 0 {
+		jww.INFO.Printf("Failed to E2E send %d/%d to %s",
+			numFail, len(partitions), msg.Recipient)
+		return nil, e2e.MessageID{}, errors.Errorf("Failed to E2E send %v/%v sub payloads:"+
+			" %s", numFail, len(partitions), errRtn)
+	} else {
+		jww.INFO.Printf("Sucesfully E2E sent %d/%d to %s",
+			len(partitions)-numFail, len(partitions), msg.Recipient)
+	}
+
+	//return the rounds if everything send successfully
+	msgID := e2e.NewMessageID(partner.GetSendRelationshipFingerprint(), internalMsgId)
+	return roundIds, msgID, nil
+}
diff --git a/network/message/sendUnsafe.go b/network/message/sendUnsafe.go
new file mode 100644
index 0000000000000000000000000000000000000000..d039b18724af24efbf3c60c711e55092b1fe40b8
--- /dev/null
+++ b/network/message/sendUnsafe.go
@@ -0,0 +1,108 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package message
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+// WARNING: Unsafe
+// Payloads are not End to End encrypted, MetaData is NOT protected with
+// this call, see SendE2E for End to End encryption and full privacy protection
+// Internal SendUnsafe which bypasses the network check, will attempt to send to
+// the network without checking state.
+// This partitions payloads into multi-part messages but does NOT end to encrypt
+// them
+// Sends using SendCMIX and returns a list of rounds the messages are in. Will
+// return an error if a single part of the message fails to send.
+func (m *Manager) SendUnsafe(msg message.Send, param params.Unsafe) ([]id.Round, error) {
+	if msg.MessageType == message.Raw {
+		return nil, errors.Errorf("Raw (%d) is a reserved message type",
+			msg.MessageType)
+	}
+	//timestamp the message
+	ts := time.Now()
+
+	//partition the message
+	partitions, _, err := m.partitioner.Partition(msg.Recipient, msg.MessageType, ts,
+		msg.Payload)
+
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed to send unsafe message")
+	}
+
+	//send the partitions over cmix
+	roundIds := make([]id.Round, len(partitions))
+	errCh := make(chan error, len(partitions))
+
+	wg := sync.WaitGroup{}
+
+	jww.INFO.Printf("Unsafe sending %d messages to %s",
+		len(partitions), msg.Recipient)
+
+	for i, p := range partitions {
+		myID := m.Session.User().GetCryptographicIdentity()
+		msgCmix := format.NewMessage(m.Session.Cmix().GetGroup().GetP().ByteLen())
+		msgCmix.SetContents(p)
+		e2e.SetUnencrypted(msgCmix, myID.GetReceptionID())
+
+		jww.INFO.Printf("Unsafe sending %d/%d to %s with msgDigest: %s",
+			i+i, len(partitions), msg.Recipient, msgCmix.Digest())
+
+		wg.Add(1)
+		go func(i int) {
+			var err error
+			roundIds[i], _, err = m.SendCMIX(msgCmix, msg.Recipient, param.CMIX)
+			if err != nil {
+				errCh <- err
+			}
+			wg.Done()
+		}(i)
+	}
+
+	wg.Wait()
+
+	//see if any parts failed to send
+	numFail, errRtn := getSendErrors(errCh)
+	if numFail > 0 {
+		jww.INFO.Printf("Failed to Unsafe send %d/%d to %s",
+			numFail, len(partitions), msg.Recipient)
+		return nil, errors.Errorf("Failed to send %v/%v sub payloads:"+
+			" %s", numFail, len(partitions), errRtn)
+	} else {
+		jww.INFO.Printf("Sucesfully Unsafe sent %d/%d to %s",
+			len(partitions)-numFail, len(partitions), msg.Recipient)
+	}
+
+	//return the rounds if everything send successfully
+	return roundIds, nil
+}
+
+//returns any errors on the error channel
+func getSendErrors(c chan error) (int, string) {
+	var errRtn string
+	numFail := 0
+	done := false
+	for !done {
+		select {
+		case err := <-c:
+			errRtn += err.Error()
+			numFail++
+		default:
+			done = true
+		}
+	}
+	return numFail, errRtn
+}
diff --git a/network/node/register.go b/network/node/register.go
new file mode 100644
index 0000000000000000000000000000000000000000..2b59d1e7ffd23853705541c46eb14242e8c3ca76
--- /dev/null
+++ b/network/node/register.go
@@ -0,0 +1,245 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package node
+
+import (
+	"crypto/rand"
+	"crypto/sha256"
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/cmix"
+	"gitlab.com/elixxir/client/storage/user"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/crypto/hash"
+	"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"
+	"strconv"
+	"time"
+)
+
+type RegisterNodeCommsInterface interface {
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+	SendRequestNonceMessage(host *connect.Host,
+		message *pb.NonceRequest) (*pb.Nonce, error)
+	SendConfirmNonceMessage(host *connect.Host,
+		message *pb.RequestRegistrationConfirmation) (*pb.RegistrationConfirmation, error)
+}
+
+func StartRegistration(instance *network.Instance, session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface,
+	c chan network.NodeGateway, numParallel uint) stoppable.Stoppable {
+
+	multi := stoppable.NewMulti("NodeRegistrations")
+
+	for i:=uint(0);i<numParallel;i++{
+		stop := stoppable.NewSingle(fmt.Sprintf("NodeRegistration %d", i))
+
+		go registerNodes(session, rngGen, comms, stop, c)
+		multi.Add(stop)
+	}
+
+	return multi
+}
+
+func registerNodes(session *storage.Session, rngGen *fastRNG.StreamGenerator, comms RegisterNodeCommsInterface,
+	stop *stoppable.Single, c chan network.NodeGateway) {
+	u := session.User()
+	regSignature := u.GetTransmissionRegistrationValidationSignature()
+	uci := u.GetCryptographicIdentity()
+	cmix := session.Cmix()
+
+	rng := rngGen.GetStream()
+	interval := time.Duration(500) * time.Millisecond
+	t := time.NewTicker(interval)
+	for true {
+		select {
+		case <-stop.Quit():
+			t.Stop()
+			return
+		case gw := <-c:
+			err := registerWithNode(comms, gw, regSignature, uci, cmix, rng)
+			if err != nil {
+				jww.ERROR.Printf("Failed to register node: %+v", err)
+			}
+		case <-t.C:
+		}
+	}
+}
+
+//registerWithNode serves as a helper for RegisterWithNodes
+// It registers a user with a specific in the client's ndf.
+func registerWithNode(comms RegisterNodeCommsInterface, ngw network.NodeGateway, regSig []byte,
+	uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source) error {
+	nodeID, err := ngw.Node.GetNodeId()
+	if err != nil {
+		jww.ERROR.Println("registerWithNode() failed to decode nodeId")
+		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
+	}
+
+	jww.INFO.Printf("registerWithNode() begin registration with node: %s", nodeID)
+
+	var transmissionKey *cyclic.Int
+	// TODO: should move this to a precanned user initialization
+	if uci.IsPrecanned() {
+		userNum := int(uci.GetTransmissionID().Bytes()[7])
+		h := sha256.New()
+		h.Reset()
+		h.Write([]byte(strconv.Itoa(int(4000 + userNum))))
+
+		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(comms, gatewayID, regSig, uci, store, rng)
+		if err != nil {
+			return errors.Errorf("Failed to request nonce: %+v", err)
+		}
+
+		// Load server DH pubkey
+		serverPubDH := store.GetGroup().NewIntFromBytes(dhPub)
+
+		// Confirm received nonce
+		jww.INFO.Println("Register: Confirming received nonce")
+		err = confirmNonce(comms, uci.GetTransmissionID().Bytes(),
+			nonce, uci.GetTransmissionRSA(), gatewayID)
+		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)
+
+	jww.INFO.Printf("Completed registration with node %s", nodeID)
+
+	return nil
+}
+
+func requestNonce(comms RegisterNodeCommsInterface, gwId *id.ID, regHash []byte,
+	uci *user.CryptographicIdentity, store *cmix.Store, rng csprng.Source) ([]byte, []byte, error) {
+	dhPub := store.GetDHPublicKey().Bytes()
+	opts := rsa.NewDefaultOptions()
+	opts.Hash = hash.CMixHash
+	h, _ := hash.NewCMixHash()
+	h.Write(dhPub)
+	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
+	}
+
+	// Request nonce message from gateway
+	jww.INFO.Printf("Register: Requesting nonce from gateway %v",
+		gwId.Bytes())
+
+	host, ok := comms.GetHost(gwId)
+	if !ok {
+		return nil, nil, errors.Errorf("Failed to find host with ID %s", gwId.String())
+	}
+	nonceResponse, err := comms.SendRequestNonceMessage(host,
+		&pb.NonceRequest{
+			Salt:            uci.GetTransmissionSalt(),
+			ClientRSAPubKey: string(rsa.CreatePublicKeyPem(uci.GetTransmissionRSA().GetPublic())),
+			ClientSignedByServer: &messages.RSASignature{
+				Signature: regHash,
+			},
+			ClientDHPubKey: dhPub,
+			RequestSignature: &messages.RSASignature{
+				Signature: clientSig,
+			},
+		})
+
+	if err != nil {
+		errMsg := fmt.Sprintf("Register: Failed requesting nonce from gateway: %+v", err)
+		return nil, nil, errors.New(errMsg)
+	}
+	if nonceResponse.Error != "" {
+		err := errors.New(fmt.Sprintf("requestNonce: nonceResponse error: %s", nonceResponse.Error))
+		return nil, nil, err
+	}
+	// Use Client keypair to sign Server nonce
+	return nonceResponse.Nonce, nonceResponse.DHPubKey, nil
+}
+
+// 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(comms RegisterNodeCommsInterface, UID, nonce []byte,
+	privateKeyRSA *rsa.PrivateKey, gwID *id.ID) 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 nonce & sign
+	sig, err := rsa.Sign(rand.Reader, privateKeyRSA, opts.Hash, data, opts)
+	if err != nil {
+		jww.ERROR.Printf(
+			"Register: Unable to sign nonce! %s", err)
+		return err
+	}
+
+	// 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,
+		},
+	}
+
+	host, ok := comms.GetHost(gwID)
+	if !ok {
+		return errors.Errorf("Failed to find host with ID %s", gwID.String())
+	}
+	confirmResponse, err := comms.SendConfirmNonceMessage(host, msg)
+	if err != nil {
+		err := errors.New(fmt.Sprintf(
+			"confirmNonce: Unable to send signed nonce! %s", err))
+		return err
+	}
+	if confirmResponse.Error != "" {
+		err := errors.New(fmt.Sprintf(
+			"confirmNonce: Error confirming nonce: %s", confirmResponse.Error))
+		return err
+	}
+	return nil
+}
diff --git a/network/node/register_test.go b/network/node/register_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..95d0765bfdfa47d2443ff45245d770d1eafaeb8b
--- /dev/null
+++ b/network/node/register_test.go
@@ -0,0 +1,123 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package node
+
+import (
+	//"crypto/rand"
+	//"gitlab.com/elixxir/client/stoppable"
+	//"gitlab.com/elixxir/client/storage"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	//"gitlab.com/elixxir/comms/network"
+	//"gitlab.com/xx_network/crypto/csprng"
+	//"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/comms/connect"
+	//"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	//"gitlab.com/xx_network/primitives/ndf"
+	//"testing"
+	//"time"
+)
+
+// Mock client comms object
+type MockClientComms struct {
+	request chan bool
+	confirm chan bool
+}
+
+func NewMockClientComms() *MockClientComms {
+	return &MockClientComms{
+		request: make(chan bool, 1),
+		confirm: make(chan bool, 1),
+	}
+}
+
+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) {
+	// 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:                "",
+	}, nil
+}
+
+// func TestRegisterNodes(t *testing.T) {
+// 	privKey, err := rsa.LoadPrivateKeyFromPem([]byte("-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC7Dkb6VXFn4cdp\nU0xh6ji0nTDQUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZr\ntzujFPBRFp9O14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfI\nTVCv8CLE0t1ibiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGes\nkWEFa2VttHqF910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq\n6/OAXCU1JLi3kW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzf\nrarmsGM0LZh6JY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYI\nCqldpt79gaET9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8V\nMKbrCaOkzD5zgnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4S\no9AppDQB41SH3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenP\nel2ApMXp+LVRdDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/u\nSALsU2v9UHBzprdrLSZk2YpozJb+CQIDAQABAoICAARjDFUYpeU6zVNyCauOM7BA\ns4FfQdHReg+zApTfWHosDQ04NIc9CGbM6e5E9IFlb3byORzyevkllf5WuMZVWmF8\nd1YBBeTftKYBn2Gwa42Ql9dl3eD0wQ1gUWBBeEoOVZQ0qskr9ynpr0o6TfciWZ5m\nF50UWmUmvc4ppDKhoNwogNU/pKEwwF3xOv2CW2hB8jyLQnk3gBZlELViX3UiFKni\n/rCfoYYvDFXt+ABCvx/qFNAsQUmerurQ3Ob9igjXRaC34D7F9xQ3CMEesYJEJvc9\nGjvr5DbnKnjx152HS56TKhK8gp6vGHJz17xtWECXD3dIUS/1iG8bqXuhdg2c+2aW\nm3MFpa5jgpAawUWc7c32UnqbKKf+HI7/x8J1yqJyNeU5SySyYSB5qtwTShYzlBW/\nyCYD41edeJcmIp693nUcXzU+UAdtpt0hkXS59WSWlTrB/huWXy6kYXLNocNk9L7g\niyx0cOmkuxREMHAvK0fovXdVyflQtJYC7OjJxkzj2rWO+QtHaOySXUyinkuTb5ev\nxNhs+ROWI/HAIE9buMqXQIpHx6MSgdKOL6P6AEbBan4RAktkYA6y5EtH/7x+9V5E\nQTIz4LrtI6abaKb4GUlZkEsc8pxrkNwCqOAE/aqEMNh91Na1TOj3f0/a6ckGYxYH\npyrvwfP2Ouu6e5FhDcCBAoIBAQDcN8mK99jtrH3q3Q8vZAWFXHsOrVvnJXyHLz9V\n1Rx/7TnMUxvDX1PIVxhuJ/tmHtxrNIXOlps80FCZXGgxfET/YFrbf4H/BaMNJZNP\nag1wBV5VQSnTPdTR+Ijice+/ak37S2NKHt8+ut6yoZjD7sf28qiO8bzNua/OYHkk\nV+RkRkk68Uk2tFMluQOSyEjdsrDNGbESvT+R1Eotupr0Vy/9JRY/TFMc4MwJwOoy\ns7wYr9SUCq/cYn7FIOBTI+PRaTx1WtpfkaErDc5O+nLLEp1yOrfktl4LhU/r61i7\nfdtafUACTKrXG2qxTd3w++mHwTwVl2MwhiMZfxvKDkx0L2gxAoIBAQDZcxKwyZOy\ns6Aw7igw1ftLny/dpjPaG0p6myaNpeJISjTOU7HKwLXmlTGLKAbeRFJpOHTTs63y\ngcmcuE+vGCpdBHQkaCev8cve1urpJRcxurura6+bYaENO6ua5VzF9BQlDYve0YwY\nlbJiRKmEWEAyULjbIebZW41Z4UqVG3MQI750PRWPW4WJ2kDhksFXN1gwSnaM46KR\nPmVA0SL+RCPcAp/VkImCv0eqv9exsglY0K/QiJfLy3zZ8QvAn0wYgZ3AvH3lr9rJ\nT7pg9WDb+OkfeEQ7INubqSthhaqCLd4zwbMRlpyvg1cMSq0zRvrFpwVlSY85lW4F\ng/tgjJ99W9VZAoIBAH3OYRVDAmrFYCoMn+AzA/RsIOEBqL8kaz/Pfh9K4D01CQ/x\naqryiqqpFwvXS4fLmaClIMwkvgq/90ulvuCGXeSG52D+NwW58qxQCxgTPhoA9yM9\nVueXKz3I/mpfLNftox8sskxl1qO/nfnu15cXkqVBe4ouD+53ZjhAZPSeQZwHi05h\nCbJ20gl66M+yG+6LZvXE96P8+ZQV80qskFmGdaPozAzdTZ3xzp7D1wegJpTz3j20\n3ULKAiIb5guZNU0tEZz5ikeOqsQt3u6/pVTeDZR0dxnyFUf/oOjmSorSG75WT3sA\n0ZiR0SH5mhFR2Nf1TJ4JHmFaQDMQqo+EG6lEbAECggEAA7kGnuQ0lSCiI3RQV9Wy\nAa9uAFtyE8/XzJWPaWlnoFk04jtoldIKyzHOsVU0GOYOiyKeTWmMFtTGANre8l51\nizYiTuVBmK+JD/2Z8/fgl8dcoyiqzvwy56kX3QUEO5dcKO48cMohneIiNbB7PnrM\nTpA3OfkwnJQGrX0/66GWrLYP8qmBDv1AIgYMilAa40VdSyZbNTpIdDgfP6bU9Ily\nG7gnyF47HHPt5Cx4ouArbMvV1rof7ytCrfCEhP21Lc46Ryxy81W5ZyzoQfSxfdKb\nGyDR+jkryVRyG69QJf5nCXfNewWbFR4ohVtZ78DNVkjvvLYvr4qxYYLK8PI3YMwL\nsQKCAQB9lo7JadzKVio+C18EfNikOzoriQOaIYowNaaGDw3/9KwIhRsKgoTs+K5O\ngt/gUoPRGd3M2z4hn5j4wgeuFi7HC1MdMWwvgat93h7R1YxiyaOoCTxH1klbB/3K\n4fskdQRxuM8McUebebrp0qT5E0xs2l+ABmt30Dtd3iRrQ5BBjnRc4V//sQiwS1aC\nYi5eNYCQ96BSAEo1dxJh5RI/QxF2HEPUuoPM8iXrIJhyg9TEEpbrEJcxeagWk02y\nOMEoUbWbX07OzFVvu+aJaN/GlgiogMQhb6IiNTyMlryFUleF+9OBA8xGHqGWA6nR\nOaRA5ZbdE7g7vxKRV36jT3wvD7W+\n-----END PRIVATE KEY-----\n"))
+// 	if err != nil || privKey == nil {
+// 		t.Error("Failed to load privKey\n")
+// 	}
+// 	pub := "-----BEGIN CERTIFICATE-----\nMIIGHTCCBAWgAwIBAgIUOcAn9cpH+hyRH8/UfqtbFDoSxYswDQYJKoZIhvcNAQEL\nBQAwgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJQ2xhcmVt\nb250MRAwDgYDVQQKDAdFbGl4eGlyMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEZMBcG\nA1UEAwwQZ2F0ZXdheS5jbWl4LnJpcDEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZWxp\neHhpci5pbzAeFw0xOTA4MTYwMDQ4MTNaFw0yMDA4MTUwMDQ4MTNaMIGSMQswCQYD\nVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUNsYXJlbW9udDEQMA4GA1UE\nCgwHRWxpeHhpcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGTAXBgNVBAMMEGdhdGV3\nYXkuY21peC5yaXAxHzAdBgkqhkiG9w0BCQEWEGFkbWluQGVsaXh4aXIuaW8wggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7Dkb6VXFn4cdpU0xh6ji0nTDQ\nUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZrtzujFPBRFp9O\n14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfITVCv8CLE0t1i\nbiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGeskWEFa2VttHqF\n910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq6/OAXCU1JLi3\nkW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzfrarmsGM0LZh6\nJY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYICqldpt79gaET\n9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8VMKbrCaOkzD5z\ngnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4So9AppDQB41SH\n3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenPel2ApMXp+LVR\ndDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/uSALsU2v9UHBz\nprdrLSZk2YpozJb+CQIDAQABo2kwZzAdBgNVHQ4EFgQUDaTvG7SwgRQ3wcYx4l+W\nMcZjX7owHwYDVR0jBBgwFoAUDaTvG7SwgRQ3wcYx4l+WMcZjX7owDwYDVR0TAQH/\nBAUwAwEB/zAUBgNVHREEDTALgglmb28uY28udWswDQYJKoZIhvcNAQELBQADggIB\nADKz0ST0uS57oC4rT9zWhFqVZkEGh1x1XJ28bYtNUhozS8GmnttV9SnJpq0EBCm/\nr6Ub6+Wmf60b85vCN5WDYdoZqGJEBjGGsFzl4jkYEE1eeMfF17xlNUSdt1qLCE8h\nU0glr32uX4a6nsEkvw1vo1Liuyt+y0cOU/w4lgWwCqyweu3VuwjZqDoD+3DShVzX\n8f1p7nfnXKitrVJt9/uE+AtAk2kDnjBFbRxCfO49EX4Cc5rADUVXMXm0itquGBYp\nMbzSgFmsMp40jREfLYRRzijSZj8tw14c2U9z0svvK9vrLCrx9+CZQt7cONGHpr/C\n/GIrP/qvlg0DoLAtjea73WxjSCbdL3Nc0uNX/ymXVHdQ5husMCZbczc9LYdoT2VP\nD+GhkAuZV9g09COtRX4VP09zRdXiiBvweiq3K78ML7fISsY7kmc8KgVH22vcXvMX\nCgGwbrxi6QbQ80rWjGOzW5OxNFvjhvJ3vlbOT6r9cKZGIPY8IdN/zIyQxHiim0Jz\noavr9CPDdQefu9onizsmjsXFridjG/ctsJxcUEqK7R12zvaTxu/CVYZbYEUFjsCe\nq6ZAACiEJGvGeKbb/mSPvGs2P1kS70/cGp+P5kBCKqrm586FB7BcafHmGFrWhT3E\nLOUYkOV/gADT2hVDCrkPosg7Wb6ND9/mhCVVhf4hLGRh\n-----END CERTIFICATE-----\n"
+
+// 	salt := make([]byte, 32)
+// 	_, err = rand.Read(salt)
+// 	if err != nil {
+// 		t.Errorf("Failed to generate salt: %+v", err)
+// 	}
+// 	//uid := id.NewIdFromString("zezima", id.User, t)
+// 	comms := NewMockClientComms()
+
+// 	instanceComms := &connect.ProtoComms{}
+// 	_, err = instanceComms.AddHost(&id.Permissioning, "0.0.0.0:420", []byte(pub),
+// 		connect.GetDefaultHostParams())
+// 	if err != nil {
+// 		t.Errorf("Faield to add perm host: %+v", err)
+// 	}
+
+// 	sess := storage.InitTestingSession(t)
+
+// 	rng := fastRNG.NewStreamGenerator(7, 3, csprng.NewSystemRNG)
+
+// 	stop := stoppable.NewSingle("test")
+// 	c := make(chan network.NodeGateway, 100)
+// 	go registerNodes(sess, rng, comms, stop, c)
+
+// 	c <- network.NodeGateway{
+// 		Node: ndf.Node{
+// 			ID:             id.NewIdFromString("zezima", id.Node, t).Bytes(),
+// 			Address:        "0.0.0.0:420",
+// 			TlsCertificate: pub,
+// 		},
+// 		Gateway: ndf.Gateway{
+// 			ID:             id.NewIdFromString("zezima", id.Gateway, t).Bytes(),
+// 			Address:        "0.0.0.0:421",
+// 			TlsCertificate: pub,
+// 		},
+// 	}
+
+// 	timeout := time.NewTimer(time.Second * 5)
+// 	select {
+// 	case <-timeout.C:
+// 		t.Errorf("Timed out waiting for request nonce channel signal")
+// 	case <-comms.request:
+// 	}
+
+// 	timeout.Reset(5 * time.Second)
+// 	select {
+// 	case <-timeout.C:
+// 		t.Errorf("Timed out waiting for confirm nonce channel signal")
+// 	case <-comms.confirm:
+// 	}
+
+// 	err = stop.Close(5 * time.Second)
+// 	if err != nil {
+// 		t.Errorf("Failed to stop registration thread: %+v", err)
+// 	}
+// }
diff --git a/network/polltracker.go b/network/polltracker.go
new file mode 100644
index 0000000000000000000000000000000000000000..497539e94ec9d6aa8cb013bd0ee40678e1360731
--- /dev/null
+++ b/network/polltracker.go
@@ -0,0 +1,45 @@
+package network
+
+import (
+	"fmt"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+type pollTracker map[id.ID]map[int64]uint
+
+func newPollTracker() *pollTracker {
+	pt := make(pollTracker)
+	return &pt
+}
+
+//tracks a single poll
+func (pt *pollTracker) Track(ephID ephemeral.Id, source *id.ID) {
+	if _, exists := (*pt)[*source]; !exists {
+		(*pt)[*source] = make(map[int64]uint)
+		(*pt)[*source][ephID.Int64()] = 0
+	} else if _, exists := (*pt)[*source][ephID.Int64()]; !exists {
+		(*pt)[*source][ephID.Int64()] = 0
+	} else {
+		(*pt)[*source][ephID.Int64()] = (*pt)[*source][ephID.Int64()] + 1
+	}
+}
+
+//reports all resent polls
+func (pt *pollTracker) Report() string {
+	report := ""
+	numReports := uint(0)
+
+	for source := range *pt {
+		numSubReports := uint(0)
+		subReport := ""
+		for ephID, reports := range (*pt)[source] {
+			numSubReports += reports
+			subReport += fmt.Sprintf("\n\t\tEphID %d polled %d times", ephID, reports)
+		}
+		subReport = fmt.Sprintf("\n\tID %s polled %d times", &source, numSubReports)
+		numReports += numSubReports
+	}
+
+	return fmt.Sprintf("\nPolled the network %d times", numReports) + report
+}
diff --git a/network/rounds/check.go b/network/rounds/check.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8b1c64dd419bc8d349c04bc69db39c61637cbbd
--- /dev/null
+++ b/network/rounds/check.go
@@ -0,0 +1,105 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package rounds
+
+import (
+	"encoding/binary"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// the round checker is a single use function which is meant to be wrapped
+// and adhere to the knownRounds checker interface. it receives a round ID and
+// looks up the state of that round to determine if the client has a message
+// waiting in it.
+// It will return true if it can conclusively determine no message exists,
+// returning false and set the round to processing if it needs further
+// investigation.
+// Once it determines messages might be waiting in a round, it determines
+// if the information about that round is already present, if it is the data is
+// sent to Message Retrieval Workers, otherwise it is sent to Historical Round
+// Retrieval
+func (m *Manager) Checker(roundID id.Round, filters []*RemoteFilter, identity reception.IdentityUse) bool {
+	// Set round to processing, if we can
+	status, count := m.p.Process(roundID, identity.EphId, identity.Source)
+	jww.INFO.Printf("checking: %d, status: %s", roundID, status)
+
+	switch status{
+	case Processing:
+		return false
+	case Done:
+		return true
+	}
+
+	//if the number of times the round has been checked has hit the max, drop it
+	if count == m.params.MaxAttemptsCheckingARound {
+		jww.ERROR.Printf("Looking up Round %v for %d (%s) failed "+
+			"the maximum number of times (%v), stopping retrval attempt",
+			roundID, identity.EphId, identity.Source,
+			m.params.MaxAttemptsCheckingARound)
+		m.p.Done(roundID, identity.EphId, identity.Source)
+		return true
+	}
+
+	hasRound := false
+	//find filters that could have the round and check them
+	serialRid := serializeRound(roundID)
+	for _, filter := range filters {
+		if filter != nil && filter.FirstRound() <= roundID &&
+			filter.LastRound() >= roundID {
+			if filter.GetFilter().Test(serialRid) {
+				hasRound = true
+				break
+			}
+		}
+	}
+
+	//if it is not present, set the round as checked
+	//that means no messages are available for the user in the round
+	if !hasRound {
+		jww.DEBUG.Printf("No messages found for %d (%s) in round %d, "+
+			"will not check again", identity.EphId.Int64(), identity.Source, roundID)
+		m.p.Done(roundID, identity.EphId, identity.Source)
+		return true
+	}
+
+	// Go get the round from the round infos, if it exists
+	ri, err := m.Instance.GetRound(roundID)
+	if err != nil || m.params.ForceHistoricalRounds {
+		if m.params.ForceHistoricalRounds {
+			jww.WARN.Printf("Forcing use of historical rounds for round ID %d.",
+				roundID)
+		}
+		jww.INFO.Printf("Messages found in round %d for %d (%s), looking "+
+			"up messages via historical lookup", roundID, identity.EphId.Int64(),
+			identity.Source)
+		// If we didn't find it, send to Historical Rounds Retrieval
+		m.historicalRounds <- historicalRoundRequest{
+			rid:      roundID,
+			identity: identity,
+		}
+	} else {
+		jww.INFO.Printf("Messages found in round %d for %d (%s), looking " +
+			"up messages via in ram lookup", roundID, identity.EphId.Int64(),
+			identity.Source)
+		// If found, send to Message Retrieval Workers
+		m.lookupRoundMessages <- roundLookup{
+			roundInfo: ri,
+			identity:  identity,
+		}
+	}
+
+	return false
+}
+
+func serializeRound(roundId id.Round) []byte {
+	b := make([]byte, 8)
+	binary.LittleEndian.PutUint64(b, uint64(roundId))
+	return b
+}
diff --git a/network/rounds/historical.go b/network/rounds/historical.go
new file mode 100644
index 0000000000000000000000000000000000000000..1a0f5c100697044ebdbdaf7975b5019e87be0356
--- /dev/null
+++ b/network/rounds/historical.go
@@ -0,0 +1,143 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package rounds
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/network/gateway"
+	"gitlab.com/elixxir/client/storage/reception"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// Historical Rounds looks up the round history via random gateways.
+// It batches these quests but never waits longer than
+// params.HistoricalRoundsPeriod to do a lookup.
+// Historical rounds receives input from:
+//   - Network Follower (/network/follow.go)
+// Historical Rounds sends the output to:
+//	 - Message Retrieval Workers (/network/round/retrieve.go)
+
+//interface to increase east of testing of historical rounds
+type historicalRoundsComms interface {
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+	RequestHistoricalRounds(host *connect.Host,
+		message *pb.HistoricalRounds) (*pb.HistoricalRoundsResponse, error)
+}
+
+//structure which contains a historical round lookup
+type historicalRoundRequest struct {
+	rid      id.Round
+	identity reception.IdentityUse
+}
+
+// Long running thread which process historical rounds
+// Can be killed by sending a signal to the quit channel
+// takes a comms interface to aid in testing
+func (m *Manager) processHistoricalRounds(comm historicalRoundsComms, quitCh <-chan struct{}) {
+
+	timerCh := make(<-chan time.Time)
+
+	rng := m.Rng.GetStream()
+	var roundRequests []historicalRoundRequest
+
+	done := false
+	for !done {
+		shouldProcess := false
+		// wait for a quit or new round to check
+		select {
+		case <-quitCh:
+			rng.Close()
+			// return all roundRequests in the queue to the input channel so they can
+			// be checked in the future. If the queue is full, disable them as
+			// processing so they are picked up from the beginning
+			for _, r := range roundRequests {
+				select {
+				case m.historicalRounds <- r:
+				default:
+					m.p.NotProcessing(r.rid, r.identity.EphId, r.identity.Source)
+				}
+			}
+			done = true
+		// if the timer elapses process roundRequests to ensure the delay isn't too long
+		case <-timerCh:
+			if len(roundRequests) > 0 {
+				shouldProcess = true
+			}
+		// get new round to lookup and force a lookup if
+		case r := <-m.historicalRounds:
+			jww.DEBUG.Printf("Recieved and quing round %d for "+
+				"historical rounds lookup", r.rid)
+			roundRequests = append(roundRequests, r)
+			if len(roundRequests) > int(m.params.MaxHistoricalRounds) {
+				shouldProcess = true
+			} else if len(roundRequests) != 0 {
+				//if this is the first round, start the timeout
+				timerCh = time.NewTimer(m.params.HistoricalRoundsPeriod).C
+			}
+		}
+		if !shouldProcess {
+			continue
+		}
+
+		//find a gateway to request about the roundRequests
+		gwHost, err := gateway.Get(m.Instance.GetPartialNdf().Get(), comm, rng)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to track network, NDF has corrupt "+
+				"data: %s", err)
+		}
+
+		rounds := make([]uint64, len(roundRequests))
+		for i, rr := range roundRequests {
+			rounds[i] = uint64(rr.rid)
+		}
+
+		//send the historical roundRequests request
+		hr := &pb.HistoricalRounds{
+			Rounds: rounds,
+		}
+
+		jww.DEBUG.Printf("Requesting Historical rounds %v from "+
+			"gateway %s", rounds, gwHost.GetId())
+
+		response, err := comm.RequestHistoricalRounds(gwHost, hr)
+		if err != nil {
+			jww.ERROR.Printf("Failed to request historical roundRequests "+
+				"data for rounds %v: %s", rounds, response)
+			// if the check fails to resolve, break the loop and so they will be
+			// checked again
+			timerCh = time.NewTimer(m.params.HistoricalRoundsPeriod).C
+			continue
+		}
+
+		// process the returned historical roundRequests.
+		for i, roundInfo := range response.Rounds {
+			// The interface has missing returns returned as nil, such roundRequests
+			// need be be removes as processing so the network follower will
+			// pick them up in the future.
+			if roundInfo == nil {
+				jww.ERROR.Printf("Failed to retreive "+
+					"historical round %d", roundRequests[i].rid)
+				m.p.Fail(roundRequests[i].rid, roundRequests[i].identity.EphId, roundRequests[i].identity.Source)
+				continue
+			}
+			// Successfully retrieved roundRequests are sent to the Message
+			// Retrieval Workers
+			rl := roundLookup{
+				roundInfo: roundInfo,
+				identity:  roundRequests[i].identity,
+			}
+			m.lookupRoundMessages <- rl
+		}
+
+		//clear the buffer now that all have been checked
+		roundRequests = make([]historicalRoundRequest, 0)
+	}
+}
diff --git a/network/rounds/manager.go b/network/rounds/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2a3a37b2acf611d73d7e190525bf4d25fd84620
--- /dev/null
+++ b/network/rounds/manager.go
@@ -0,0 +1,68 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package rounds
+
+import (
+	"fmt"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/network/internal"
+	"gitlab.com/elixxir/client/network/message"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+type Manager struct {
+	params params.Rounds
+
+	p *processing
+
+	internal.Internal
+
+	historicalRounds    chan historicalRoundRequest
+	lookupRoundMessages chan roundLookup
+	messageBundles      chan<- message.Bundle
+}
+
+func NewManager(internal internal.Internal, params params.Rounds,
+	bundles chan<- message.Bundle) *Manager {
+	m := &Manager{
+		params: params,
+		p:      newProcessingRounds(),
+
+		historicalRounds:    make(chan historicalRoundRequest, params.HistoricalRoundsBufferLen),
+		lookupRoundMessages: make(chan roundLookup, params.LookupRoundsBufferLen),
+		messageBundles:      bundles,
+	}
+
+	m.Internal = internal
+	return m
+}
+
+func (m *Manager) StartProcessors() stoppable.Stoppable {
+
+	multi := stoppable.NewMulti("Rounds")
+
+	//start the historical rounds thread
+	historicalRoundsStopper := stoppable.NewSingle("ProcessHistoricalRounds")
+	go m.processHistoricalRounds(m.Comms, historicalRoundsStopper.Quit())
+	multi.Add(historicalRoundsStopper)
+
+	//start the message retrieval worker pool
+	for i := uint(0); i < m.params.NumMessageRetrievalWorkers; i++ {
+		stopper := stoppable.NewSingle(fmt.Sprintf("Messager Retriever %v", i))
+		go m.processMessageRetrieval(m.Comms, stopper.Quit())
+		multi.Add(stopper)
+	}
+	return multi
+}
+
+func (m *Manager) DeleteProcessingRoundDelete(round id.Round, eph ephemeral.Id, source *id.ID) {
+
+	m.p.Delete(round, eph, source)
+}
diff --git a/network/rounds/processingRounds.go b/network/rounds/processingRounds.go
new file mode 100644
index 0000000000000000000000000000000000000000..954773201bcf7f270d9bab1674320be6da860a3b
--- /dev/null
+++ b/network/rounds/processingRounds.go
@@ -0,0 +1,156 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package rounds
+
+// File for storing info about which rounds are processing
+
+import (
+	"crypto/md5"
+	"encoding/binary"
+	"fmt"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"sync"
+)
+
+type Status uint8
+
+const (
+	NotProcessing Status = iota
+	Processing
+	Done
+)
+
+func (s Status)String()string{
+	switch s{
+	case NotProcessing:
+		return "NotProcessing"
+	case Processing:
+		return "Processing"
+	case Done:
+		return "Done"
+	default:
+		return fmt.Sprintf("Unknown Status: %d", s)
+	}
+}
+
+
+type status struct {
+	failCount  uint
+	Status
+}
+
+// processing struct with a lock so it can be managed with concurrent threads.
+type processing struct {
+	rounds map[hashID]*status
+	sync.RWMutex
+}
+
+type hashID [16]byte
+
+func makeHashID(round id.Round, eph ephemeral.Id, source *id.ID) hashID {
+	h := md5.New()
+	ridbytes := make([]byte, 8)
+	binary.BigEndian.PutUint64(ridbytes, uint64(round))
+	h.Write(ridbytes)
+	h.Write(eph[:])
+	h.Write(source.Bytes())
+
+	hBytes := h.Sum(nil)
+	hid := hashID{}
+	copy(hid[:], hBytes)
+	return hid
+}
+
+// newProcessingRounds returns a new processing rounds object.
+func newProcessingRounds() *processing {
+	return &processing{
+		rounds: make(map[hashID]*status),
+	}
+}
+
+// Process adds a round to the list of processing rounds. The returned boolean
+// is true when the round changes from "not processing" to "processing". The
+// returned count is the number of times the round has been processed.
+func (pr *processing) Process(round id.Round, eph ephemeral.Id, source *id.ID) (Status, uint) {
+	hid := makeHashID(round, eph, source)
+
+	pr.Lock()
+	defer pr.Unlock()
+
+	var rs *status
+	var ok bool
+
+	if rs, ok = pr.rounds[hid]; ok && rs.Status == NotProcessing {
+		rs.Status = Processing
+		return NotProcessing, rs.failCount
+	}else if !ok{
+		rs = &status{
+			failCount: 0,
+			Status:    Processing,
+		}
+		pr.rounds[hid] = rs
+		return NotProcessing, rs.failCount
+	}
+
+	return rs.Status, rs.failCount
+}
+
+// IsProcessing determines if a round ID is marked as processing.
+func (pr *processing) IsProcessing(round id.Round, eph ephemeral.Id, source *id.ID) bool {
+	hid := makeHashID(round, eph, source)
+	pr.RLock()
+	defer pr.RUnlock()
+
+	if rs, ok := pr.rounds[hid]; ok {
+		return rs.Status == Processing
+	}
+
+	return false
+}
+
+// Fail sets a round's processing status to failed and increments its fail
+// counter so that it can be retried.
+func (pr *processing) Fail(round id.Round, eph ephemeral.Id, source *id.ID) {
+	hid := makeHashID(round, eph, source)
+	pr.Lock()
+	defer pr.Unlock()
+	if rs, ok := pr.rounds[hid]; ok {
+		rs.Status = NotProcessing
+		rs.failCount++
+	}
+}
+
+// Done deletes a round from the processing list.
+func (pr *processing) Done(round id.Round, eph ephemeral.Id, source *id.ID) {
+	hid := makeHashID(round, eph, source)
+	pr.Lock()
+	defer pr.Unlock()
+	if rs, ok := pr.rounds[hid]; ok {
+		rs.Status = Done
+	}
+}
+
+// NotProcessing sets a round's processing status to failed so that it can be
+// retried but does not increment its fail counter.
+func (pr *processing) NotProcessing(round id.Round, eph ephemeral.Id, source *id.ID) {
+	hid := makeHashID(round, eph, source)
+	pr.Lock()
+	defer pr.Unlock()
+	if rs, ok := pr.rounds[hid]; ok {
+		rs.Status = NotProcessing
+	}
+}
+
+// Done deletes a round from the processing list.
+func (pr *processing) Delete(round id.Round, eph ephemeral.Id, source *id.ID) {
+	hid := makeHashID(round, eph, source)
+	pr.Lock()
+	defer pr.Unlock()
+	delete(pr.rounds, hid)
+}
diff --git a/network/rounds/processingRounds_test.go b/network/rounds/processingRounds_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ce186dfbedb0ea84b55fca15b1109d49349fc1b3
--- /dev/null
+++ b/network/rounds/processingRounds_test.go
@@ -0,0 +1,124 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package rounds
+
+import (
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"reflect"
+	"testing"
+)
+
+// Testing functions for Processing Round structure
+
+// Tests happy path of newProcessingRounds.
+func Test_newProcessingRounds(t *testing.T) {
+	expectedPr := &processing{
+		rounds: make(map[hashID]*status),
+	}
+
+	pr := newProcessingRounds()
+
+	if !reflect.DeepEqual(expectedPr, pr) {
+		t.Errorf("Did not get expected processing."+
+			"\n\texpected: %v\n\trecieved: %v", expectedPr, pr)
+	}
+}
+
+// Tests happy path of Process.
+func TestProcessing_Process(t *testing.T) {
+	pr := newProcessingRounds()
+
+	ephID := ephemeral.Id{}
+	source := &id.ID{}
+
+	testData := []struct {
+		rid    id.Round
+		status Status
+		count  uint
+	}{
+		{10, NotProcessing, 0},
+		{10, NotProcessing, 0},
+		{10, Processing, 0},
+		{100, NotProcessing, 0},
+		{100, NotProcessing, 0},
+		{100, Processing, 0},
+	}
+
+	for i, d := range testData {
+		hid := makeHashID(d.rid, ephID, source)
+		if _, exists := pr.rounds[hid]; exists {
+			pr.rounds[hid].Status = d.status
+		}
+		status, count := pr.Process(d.rid, ephID, source)
+		if status != d.status {
+			t.Errorf("Process() did not return the correct boolean for round "+
+				"ID %d (%d).\nexpected: %s\nrecieved: %s",
+				d.rid, i, d.status, status)
+		}
+		if count != d.count {
+			t.Errorf("Process did not return the expected count for round ID "+
+				"%d (%d).\n\texpected: %d\n\trecieved: %d",
+				d.rid, i, d.count, count)
+		}
+
+		if _, ok := pr.rounds[hid]; !ok {
+			t.Errorf("Process() did not add round ID %d to the map (%d).",
+				d.rid, i)
+		}
+	}
+
+}
+
+// Tests happy path of IsProcessing.
+func TestProcessing_IsProcessing(t *testing.T) {
+	pr := newProcessingRounds()
+	ephID := ephemeral.Id{}
+	source := &id.ID{}
+	rid := id.Round(10)
+	hid := makeHashID(rid, ephID, source)
+	pr.rounds[hid] = &status{0, Processing}
+	if !pr.IsProcessing(rid, ephID, source) {
+		t.Errorf("IsProcessing() should have returned %s for round ID %d.", Processing, rid)
+	}
+	pr.rounds[hid].Status = NotProcessing
+	if pr.IsProcessing(rid, ephID, source) {
+		t.Errorf("IsProcessing() should have returned %s for round ID %d.", NotProcessing, rid)
+	}
+}
+
+// Tests happy path of Fail.
+func TestProcessing_Fail(t *testing.T) {
+	pr := newProcessingRounds()
+	rid := id.Round(10)
+	ephID := ephemeral.Id{}
+	source := &id.ID{}
+	hid := makeHashID(rid, ephID, source)
+	pr.rounds[hid] = &status{0, Processing}
+	pr.Fail(rid, ephID, source)
+	if pr.rounds[hid].Status == Processing {
+		t.Errorf("Fail() did not mark processing as false for round id %d.", rid)
+	}
+	if pr.rounds[hid].failCount != 1 {
+		t.Errorf("Fail() did not increment the fail count of round id %d.", rid)
+	}
+}
+
+// Tests happy path of Done.
+func TestProcessing_Done(t *testing.T) {
+	pr := newProcessingRounds()
+	rid := id.Round(10)
+	ephID := ephemeral.Id{}
+	source := &id.ID{}
+	hid := makeHashID(rid, ephID, source)
+	pr.rounds[hid] = &status{0, Processing}
+	pr.Done(rid, ephID, source)
+	if s, _ := pr.rounds[hid]; s.Status != Done {
+		t.Errorf("Done() failed to flag round ID %d.", rid)
+	}
+}
diff --git a/network/rounds/remoteFilters.go b/network/rounds/remoteFilters.go
new file mode 100644
index 0000000000000000000000000000000000000000..e2a225632c088a9082a63ed08367b90114f80a50
--- /dev/null
+++ b/network/rounds/remoteFilters.go
@@ -0,0 +1,77 @@
+package rounds
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	bloom "gitlab.com/elixxir/bloomfilter"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+func NewRemoteFilter(data *mixmessages.ClientBloom) *RemoteFilter {
+	return &RemoteFilter{
+		data: data,
+	}
+}
+
+type RemoteFilter struct {
+	data   *mixmessages.ClientBloom
+	filter *bloom.Ring
+}
+
+func (rf *RemoteFilter) GetFilter() *bloom.Ring {
+
+	if rf.filter == nil {
+		var err error
+		rf.filter, _ = bloom.InitByParameters(interfaces.BloomFilterSize,
+			interfaces.BloomFilterHashes)
+		err = rf.filter.UnmarshalBinary(rf.data.Filter)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to properly unmarshal the bloom filter: %+v", err)
+		}
+	}
+	return rf.filter
+}
+
+func (rf *RemoteFilter) FirstRound() id.Round {
+	return id.Round(rf.data.FirstRound)
+}
+
+func (rf *RemoteFilter) LastRound() id.Round {
+	return id.Round(rf.data.FirstRound + uint64(rf.data.RoundRange))
+}
+
+// ValidFilterRange calculates which of the returned filters are valid for the identity
+func ValidFilterRange(identity reception.IdentityUse, filters *mixmessages.ClientBlooms) (startIdx int, endIdx int, outOfBounds bool) {
+	outOfBounds = false
+
+	firstElementTS := filters.FirstTimestamp
+
+	identityStart := identity.StartValid.UnixNano()
+	identityEnd := identity.EndValid.UnixNano()
+
+	startIdx = int((identityStart - firstElementTS) / filters.Period)
+	if startIdx < 0 {
+		startIdx = 0
+	}
+
+	if startIdx > len(filters.Filters)-1 {
+		outOfBounds = true
+		return startIdx, endIdx, outOfBounds
+	}
+
+	endIdx = int((identityEnd - firstElementTS) / filters.Period)
+	if endIdx < 0 {
+		outOfBounds = true
+		return startIdx, endIdx, outOfBounds
+	}
+
+	if endIdx > len(filters.Filters)-1 {
+		endIdx = len(filters.Filters) - 1
+	}
+
+	// Add 1 to the end index so that it follows Go's convention; the last index
+	// is exclusive to the range
+	return startIdx, endIdx + 1, outOfBounds
+}
diff --git a/network/rounds/retrieve.go b/network/rounds/retrieve.go
new file mode 100644
index 0000000000000000000000000000000000000000..5269357776e9be37984cacfbe31a2208dda912d2
--- /dev/null
+++ b/network/rounds/retrieve.go
@@ -0,0 +1,155 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package rounds
+
+import (
+	"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/storage/reception"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type messageRetrievalComms interface {
+	GetHost(hostId *id.ID) (*connect.Host, bool)
+	RequestMessages(host *connect.Host,
+		message *pb.GetMessages) (*pb.GetMessagesResponse, error)
+}
+
+type roundLookup struct {
+	roundInfo *pb.RoundInfo
+	identity  reception.IdentityUse
+}
+
+const noRoundError = "does not have round"
+
+// processMessageRetrieval received a roundLookup request and pings the gateways
+// of that round for messages for the requested identity in the roundLookup
+func (m *Manager) processMessageRetrieval(comms messageRetrievalComms,
+	quitCh <-chan struct{}) {
+
+	done := false
+	for !done {
+		select {
+		case <-quitCh:
+			done = true
+		case rl := <-m.lookupRoundMessages:
+			ri := rl.roundInfo
+			var bundle message.Bundle
+
+			// Get a shuffled list of gateways in the round
+			gwHosts, err := gateway.GetAllShuffled(comms, ri)
+			if err != nil {
+				jww.WARN.Printf("Failed to get gateway hosts from "+
+					"round %v, not requesting from them",
+					ri.ID)
+				break
+			}
+
+			// Attempt to request messages for every gateway in the list.
+			// If we retrieve without error, then we exit. If we error, then
+			// we retry with the next gateway in the list until we exhaust the list
+			for i, gwHost := range gwHosts {
+				// Attempt to request for this gateway
+				bundle, err = m.getMessagesFromGateway(id.Round(ri.ID), rl.identity, comms, gwHost)
+				if err != nil {
+
+					jww.WARN.Printf("Failed on gateway [%d/%d] to get messages for round %v",
+						i, len(gwHosts), ri.ID)
+
+					// Retry for the next gateway in the list
+					continue
+				}
+
+				// If a non-error request, no longer retry
+				break
+
+			}
+			gwIDs := make([]*id.ID, 0)
+			for _, gwHost := range gwHosts {
+				gwIDs = append(gwIDs, gwHost.GetId())
+			}
+
+			// After trying all gateways, if none returned we mark the round as a
+			// failure and print out the last error
+			if err != nil {
+				m.p.Fail(id.Round(ri.ID), rl.identity.EphId, rl.identity.Source)
+				jww.ERROR.Printf("Failed to get pickup round %d "+
+					"from all gateways (%v): final gateway %s returned : %s",
+					id.Round(ri.ID), gwIDs, gwHosts[len(gwHosts)-1].GetId(), err)
+			} else if len(bundle.Messages) != 0 {
+				// If successful and there are messages, we send them to another thread
+				bundle.Identity = rl.identity
+				m.messageBundles <- bundle
+			}
+		}
+	}
+}
+
+// getMessagesFromGateway attempts to get messages from their assigned
+// gateway host in the round specified. If successful
+func (m *Manager) getMessagesFromGateway(roundID id.Round, identity reception.IdentityUse,
+	comms messageRetrievalComms, gwHost *connect.Host) (message.Bundle, error) {
+
+	jww.DEBUG.Printf("Trying to get messages for round %v for ephmeralID %d (%v)  "+
+		"via Gateway: %s", roundID, identity.EphId.Int64(), identity.Source.String(), gwHost.GetId())
+
+	// send the request
+	msgReq := &pb.GetMessages{
+		ClientID: identity.EphId[:],
+		RoundID:  uint64(roundID),
+	}
+	msgResp, err := comms.RequestMessages(gwHost, msgReq)
+	// Fail the round if an error occurs so it can be tried again later
+	if err != nil {
+		return message.Bundle{}, errors.WithMessagef(err, "Failed to "+
+			"request messages from %s for round %d", gwHost.GetId(), roundID)
+	}
+	// if the gateway doesnt have the round, return an error
+	if !msgResp.GetHasRound() {
+		m.p.Done(roundID, identity.EphId, identity.Source)
+		return message.Bundle{}, errors.Errorf(noRoundError)
+	}
+
+	// If there are no messages print a warning. Due to the probabilistic nature
+	// of the bloom filters, false positives will happen some times
+	msgs := msgResp.GetMessages()
+	if msgs == nil || len(msgs) == 0 {
+		jww.WARN.Printf("host %s has no messages for client %s "+
+			" in round %d. This happening every once in a while is normal,"+
+			" but can be indicitive of a problem if it is consistant", gwHost,
+			m.TransmissionID, roundID)
+		return message.Bundle{}, nil
+	}
+
+	jww.INFO.Printf("Received %d messages in Round %v via Gateway %s for %d (%s)",
+		len(msgs), roundID, gwHost.GetId(), identity.EphId.Int64(), identity.Source)
+
+	//build the bundle of messages to send to the message processor
+	bundle := message.Bundle{
+		Round:    roundID,
+		Messages: make([]format.Message, len(msgs)),
+		Finish: func() {
+			m.p.Done(roundID, identity.EphId, identity.Source)
+		},
+	}
+
+	for i, slot := range msgs {
+		msg := format.NewMessage(m.Session.Cmix().GetGroup().GetP().ByteLen())
+		msg.SetPayloadA(slot.PayloadA)
+		msg.SetPayloadB(slot.PayloadB)
+		bundle.Messages[i] = msg
+	}
+
+	return bundle, nil
+
+}
diff --git a/network/rounds/retrieve_test.go b/network/rounds/retrieve_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2efd2c89c6865ba335d2e167cab343bb786370db
--- /dev/null
+++ b/network/rounds/retrieve_test.go
@@ -0,0 +1,383 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package rounds
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/network/message"
+	"gitlab.com/elixxir/client/storage/reception"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Happy path
+func TestManager_ProcessMessageRetrieval(t *testing.T) {
+	// General initializations
+	testManager := newManager(t)
+	roundId := id.Round(5)
+	mockComms := &mockMessageRetrievalComms{testingSignature: t}
+	quitChan := make(chan struct{})
+
+	// Create a local channel so reception is possible (testManager.messageBundles is
+	// send only via newManager call above)
+	messageBundleChan := make(chan message.Bundle)
+	testManager.messageBundles = messageBundleChan
+
+	// Initialize the message retrieval
+	go testManager.processMessageRetrieval(mockComms, quitChan)
+
+	// Construct expected values for checking
+	expectedEphID := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	payloadMsg := []byte(PayloadMessage)
+	expectedPayload := make([]byte, 256)
+	copy(expectedPayload, payloadMsg)
+
+	go func() {
+		requestGateway := id.NewIdFromString(ReturningGateway, id.Gateway, t)
+
+		// Construct the round lookup
+		iu := reception.IdentityUse{
+			Identity: reception.Identity{
+				EphId:  expectedEphID,
+				Source: requestGateway,
+			},
+		}
+
+		idList := [][]byte{requestGateway.Bytes()}
+
+		roundInfo := &pb.RoundInfo{
+			ID:       uint64(roundId),
+			Topology: idList,
+		}
+
+		// Send a round look up request
+		testManager.lookupRoundMessages <- roundLookup{
+			roundInfo: roundInfo,
+			identity:  iu,
+		}
+
+	}()
+
+	var testBundle message.Bundle
+	go func() {
+		// Receive the bundle over the channel
+		time.Sleep(1 * time.Second)
+		testBundle = <-messageBundleChan
+
+		// Close the process
+		quitChan <- struct{}{}
+
+	}()
+
+	// Ensure bundle received and has expected values
+	time.Sleep(2 * time.Second)
+	if reflect.DeepEqual(testBundle, message.Bundle{}) {
+		t.Errorf("Did not receive a message bundle over the channel")
+		t.FailNow()
+	}
+
+	if testBundle.Identity.EphId.Int64() != expectedEphID.Int64() {
+		t.Errorf("Unexpected ephemeral ID in bundle."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", expectedEphID, testBundle.Identity.EphId)
+	}
+
+	if !bytes.Equal(expectedPayload, testBundle.Messages[0].GetPayloadA()) {
+		t.Errorf("Unexpected ephemeral ID in bundle."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", expectedPayload, testBundle.Messages[0].GetPayloadA())
+
+	}
+
+}
+
+// Utilize the mockComms to construct a gateway which does not have the round
+func TestManager_ProcessMessageRetrieval_NoRound(t *testing.T) {
+	// General initializations
+	testManager := newManager(t)
+	roundId := id.Round(5)
+	mockComms := &mockMessageRetrievalComms{testingSignature: t}
+	quitChan := make(chan struct{})
+
+	// Create a local channel so reception is possible (testManager.messageBundles is
+	// send only via newManager call above)
+	messageBundleChan := make(chan message.Bundle)
+	testManager.messageBundles = messageBundleChan
+
+	// Initialize the message retrieval
+	go testManager.processMessageRetrieval(mockComms, quitChan)
+
+	expectedEphID := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+
+	// Construct a gateway without keyword ID in utils_test.go
+	// ie mockComms does not return a round
+	dummyGateway := id.NewIdFromString("Sauron", id.Gateway, t)
+
+	go func() {
+		// Construct the round lookup
+		iu := reception.IdentityUse{
+			Identity: reception.Identity{
+				EphId:  expectedEphID,
+				Source: dummyGateway,
+			},
+		}
+
+		idList := [][]byte{dummyGateway.Bytes()}
+
+		roundInfo := &pb.RoundInfo{
+			ID:       uint64(roundId),
+			Topology: idList,
+		}
+
+		// Send a round look up request
+		testManager.lookupRoundMessages <- roundLookup{
+			roundInfo: roundInfo,
+			identity:  iu,
+		}
+
+	}()
+
+	var testBundle message.Bundle
+	go func() {
+		// Receive the bundle over the channel
+		time.Sleep(1 * time.Second)
+		testBundle = <-messageBundleChan
+
+		// Close the process
+		quitChan <- struct{}{}
+
+	}()
+
+	time.Sleep(2 * time.Second)
+	if !reflect.DeepEqual(testBundle, message.Bundle{}) {
+		t.Errorf("Should not receive a message bundle, mock gateway should not return round."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", message.Bundle{}, testBundle)
+	}
+}
+
+// Test the path where there are no messages,
+// simulating a false positive in a bloom filter
+func TestManager_ProcessMessageRetrieval_FalsePositive(t *testing.T) {
+	// General initializations
+	testManager := newManager(t)
+	roundId := id.Round(5)
+	mockComms := &mockMessageRetrievalComms{testingSignature: t}
+	quitChan := make(chan struct{})
+
+	// Create a local channel so reception is possible (testManager.messageBundles is
+	// send only via newManager call above)
+	messageBundleChan := make(chan message.Bundle)
+	testManager.messageBundles = messageBundleChan
+
+	// Initialize the message retrieval
+	go testManager.processMessageRetrieval(mockComms, quitChan)
+
+	// Construct expected values for checking
+	expectedEphID := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	payloadMsg := []byte(PayloadMessage)
+	expectedPayload := make([]byte, 256)
+	copy(expectedPayload, payloadMsg)
+
+	go func() {
+		// Construct the round lookup
+		iu := reception.IdentityUse{
+			Identity: reception.Identity{
+				EphId:  expectedEphID,
+				Source: id.NewIdFromString("Source", id.User, t),
+			},
+		}
+
+		requestGateway := id.NewIdFromString(FalsePositive, id.Gateway, t)
+
+		idList := [][]byte{requestGateway.Bytes()}
+
+		roundInfo := &pb.RoundInfo{
+			ID:       uint64(roundId),
+			Topology: idList,
+		}
+
+		// Send a round look up request
+		testManager.lookupRoundMessages <- roundLookup{
+			roundInfo: roundInfo,
+			identity:  iu,
+		}
+
+	}()
+
+	var testBundle message.Bundle
+	go func() {
+		// Receive the bundle over the channel
+		time.Sleep(1 * time.Second)
+		testBundle = <-messageBundleChan
+
+		// Close the process
+		quitChan <- struct{}{}
+
+	}()
+
+	// Ensure no bundle was received due to false positive test
+	time.Sleep(2 * time.Second)
+	if !reflect.DeepEqual(testBundle, message.Bundle{}) {
+		t.Errorf("Received a message bundle over the channel, should receive empty message list")
+		t.FailNow()
+	}
+
+}
+
+// Ensure that the quit chan closes the program, on an otherwise happy path
+func TestManager_ProcessMessageRetrieval_Quit(t *testing.T) {
+	// General initializations
+	testManager := newManager(t)
+	roundId := id.Round(5)
+	mockComms := &mockMessageRetrievalComms{testingSignature: t}
+	quitChan := make(chan struct{})
+
+	// Create a local channel so reception is possible (testManager.messageBundles is
+	// send only via newManager call above)
+	messageBundleChan := make(chan message.Bundle)
+	testManager.messageBundles = messageBundleChan
+
+	// Initialize the message retrieval
+	go testManager.processMessageRetrieval(mockComms, quitChan)
+
+	// Close the process early, before any logic below can be completed
+	quitChan <- struct{}{}
+
+	// Construct expected values for checking
+	expectedEphID := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	payloadMsg := []byte(PayloadMessage)
+	expectedPayload := make([]byte, 256)
+	copy(expectedPayload, payloadMsg)
+
+	go func() {
+		// Construct the round lookup
+		iu := reception.IdentityUse{
+			Identity: reception.Identity{
+				EphId: expectedEphID,
+			},
+		}
+
+		requestGateway := id.NewIdFromString(ReturningGateway, id.Gateway, t)
+
+		idList := [][]byte{requestGateway.Bytes()}
+
+		roundInfo := &pb.RoundInfo{
+			ID:       uint64(roundId),
+			Topology: idList,
+		}
+
+		// Send a round look up request
+		testManager.lookupRoundMessages <- roundLookup{
+			roundInfo: roundInfo,
+			identity:  iu,
+		}
+
+	}()
+
+	var testBundle message.Bundle
+	go func() {
+		// Receive the bundle over the channel
+		testBundle = <-messageBundleChan
+
+	}()
+
+	time.Sleep(1 * time.Second)
+	// Ensure no bundle was received due to quiting process early
+	if !reflect.DeepEqual(testBundle, message.Bundle{}) {
+		t.Errorf("Received a message bundle over the channel, process should have quit before reception")
+		t.FailNow()
+	}
+
+}
+
+// Path in which multiple error comms are encountered before a happy path comms
+func TestManager_ProcessMessageRetrieval_MultipleGateways(t *testing.T) {
+	// General initializations
+	testManager := newManager(t)
+	roundId := id.Round(5)
+	mockComms := &mockMessageRetrievalComms{testingSignature: t}
+	quitChan := make(chan struct{})
+
+	// Create a local channel so reception is possible (testManager.messageBundles is
+	// send only via newManager call above)
+	messageBundleChan := make(chan message.Bundle)
+	testManager.messageBundles = messageBundleChan
+
+	// Initialize the message retrieval
+	go testManager.processMessageRetrieval(mockComms, quitChan)
+
+	// Construct expected values for checking
+	expectedEphID := ephemeral.Id{1, 2, 3, 4, 5, 6, 7, 8}
+	payloadMsg := []byte(PayloadMessage)
+	expectedPayload := make([]byte, 256)
+	copy(expectedPayload, payloadMsg)
+
+	go func() {
+		requestGateway := id.NewIdFromString(ReturningGateway, id.Gateway, t)
+		errorGateway := id.NewIdFromString(ErrorGateway, id.Gateway, t)
+		// Construct the round lookup
+		iu := reception.IdentityUse{
+			Identity: reception.Identity{
+				EphId:  expectedEphID,
+				Source: requestGateway,
+			},
+		}
+
+		// Create a list of ID's in which some error gateways must be contacted before the happy path
+		idList := [][]byte{errorGateway.Bytes(), errorGateway.Bytes(), requestGateway.Bytes()}
+
+		roundInfo := &pb.RoundInfo{
+			ID:       uint64(roundId),
+			Topology: idList,
+		}
+
+		// Send a round look up request
+		testManager.lookupRoundMessages <- roundLookup{
+			roundInfo: roundInfo,
+			identity:  iu,
+		}
+
+	}()
+
+	var testBundle message.Bundle
+	go func() {
+		// Receive the bundle over the channel
+		time.Sleep(1 * time.Second)
+		testBundle = <-messageBundleChan
+
+		// Close the process
+		quitChan <- struct{}{}
+
+	}()
+
+	// Ensure that expected bundle is still received from happy comm
+	// despite initial errors
+	time.Sleep(2 * time.Second)
+	if reflect.DeepEqual(testBundle, message.Bundle{}) {
+		t.Errorf("Did not receive a message bundle over the channel")
+		t.FailNow()
+	}
+
+	if testBundle.Identity.EphId.Int64() != expectedEphID.Int64() {
+		t.Errorf("Unexpected ephemeral ID in bundle."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", expectedEphID, testBundle.Identity.EphId)
+	}
+
+	if !bytes.Equal(expectedPayload, testBundle.Messages[0].GetPayloadA()) {
+		t.Errorf("Unexpected ephemeral ID in bundle."+
+			"\n\tExpected: %v"+
+			"\n\tReceived: %v", expectedPayload, testBundle.Messages[0].GetPayloadA())
+
+	}
+
+}
diff --git a/network/rounds/utils_test.go b/network/rounds/utils_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8f14e32778574850519b02fcd73da6d2171e2e5
--- /dev/null
+++ b/network/rounds/utils_test.go
@@ -0,0 +1,94 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+package rounds
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/network/internal"
+	"gitlab.com/elixxir/client/network/message"
+	"gitlab.com/elixxir/client/storage"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+func newManager(face interface{}) *Manager {
+	sess1 := storage.InitTestingSession(face)
+
+	testManager := &Manager{
+		lookupRoundMessages: make(chan roundLookup),
+		messageBundles:      make(chan message.Bundle),
+		p:                   newProcessingRounds(),
+		Internal: internal.Internal{
+			Session:        sess1,
+			TransmissionID: sess1.GetUser().TransmissionID,
+		},
+	}
+
+	return testManager
+}
+
+// Build ID off of this string for expected gateway
+// which will return on over mock comm
+const ReturningGateway = "GetMessageRequest"
+const FalsePositive = "FalsePositive"
+const PayloadMessage = "Payload"
+const ErrorGateway = "Error"
+type mockMessageRetrievalComms struct {
+	testingSignature *testing.T
+}
+
+func (mmrc *mockMessageRetrievalComms) GetHost(hostId *id.ID) (*connect.Host, bool) {
+	h, _ := connect.NewHost(hostId, "0.0.0.0", []byte(""), connect.HostParams{
+		MaxRetries:  0,
+		AuthEnabled: false,
+	})
+	return h, true
+}
+
+// Mock comm which returns differently based on the host ID
+// ReturningGateway returns a happy path response, in which there is a message
+// FalsePositive returns a response in which there were no messages in the round
+// ErrorGateway returns an error on the mock comm
+// Any other ID returns default no round errors
+func (mmrc *mockMessageRetrievalComms) RequestMessages(host *connect.Host,
+	message *pb.GetMessages) (*pb.GetMessagesResponse, error) {
+	payloadMsg := []byte(PayloadMessage)
+	payload := make([]byte, 256)
+	copy(payload, payloadMsg)
+	testSlot := &pb.Slot{
+		PayloadA: payload,
+		PayloadB: payload,
+	}
+
+	// If we are the requesting on the returning gateway, return a mock response
+	returningGateway := id.NewIdFromString(ReturningGateway, id.Gateway, mmrc.testingSignature)
+	if host.GetId().Cmp(returningGateway) {
+		return &pb.GetMessagesResponse{
+			Messages: []*pb.Slot{testSlot},
+			HasRound: true,
+		}, nil
+	}
+
+	// Return an empty message structure (ie a false positive in the bloom filter)
+	falsePositive := id.NewIdFromString(FalsePositive, id.Gateway, mmrc.testingSignature)
+	if host.GetId().Cmp(falsePositive) {
+		return &pb.GetMessagesResponse{
+			Messages: []*pb.Slot{},
+			HasRound: true,
+		}, nil
+	}
+
+	// Return a mock error
+	errorGateway := id.NewIdFromString(ErrorGateway, id.Gateway, mmrc.testingSignature)
+	if host.GetId().Cmp(errorGateway) {
+		return &pb.GetMessagesResponse{}, errors.Errorf("Connection error")
+	}
+
+	return nil, nil
+}
diff --git a/network/send.go b/network/send.go
new file mode 100644
index 0000000000000000000000000000000000000000..0e3e9020aa187716e3a7dd7da9f538bff47ba3e9
--- /dev/null
+++ b/network/send.go
@@ -0,0 +1,64 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package network
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+// SendCMIX sends a "raw" CMIX message payload to the provided
+// recipient. Note that both SendE2E and SendUnsafe call SendCMIX.
+// Returns the round ID of the round the payload was sent or an error
+// if it fails.
+func (m *manager) SendCMIX(msg format.Message, recipient *id.ID, param params.CMIX) (id.Round, ephemeral.Id, error) {
+	if !m.Health.IsHealthy() {
+		return 0, ephemeral.Id{}, errors.New("Cannot send cmix message when the " +
+			"network is not healthy")
+	}
+
+	return m.message.SendCMIX(msg, recipient, param)
+}
+
+// SendUnsafe sends an unencrypted payload to the provided recipient
+// with the provided msgType. Returns the list of rounds in which parts
+// of the message were sent or an error if it fails.
+// NOTE: Do not use this function unless you know what you are doing.
+// This function always produces an error message in client logging.
+func (m *manager) SendUnsafe(msg message.Send, param params.Unsafe) ([]id.Round, error) {
+	if !m.Health.IsHealthy() {
+		return nil, errors.New("cannot send unsafe message when the " +
+			"network is not healthy")
+	}
+
+	jww.WARN.Println("Sending unsafe message. Unsafe payloads have no end" +
+		" to end encryption, they have limited security and privacy " +
+		"preserving properties")
+
+	return m.message.SendUnsafe(msg, param)
+}
+
+// SendE2E sends an end-to-end payload to the provided recipient with
+// the provided msgType. Returns the list of rounds in which parts of
+// the message were sent or an error if it fails.
+func (m *manager) SendE2E(msg message.Send, e2eP params.E2E) (
+	[]id.Round, e2e.MessageID, error) {
+
+	if !m.Health.IsHealthy() {
+		return nil, e2e.MessageID{}, errors.New("Cannot send e2e " +
+			"message when the network is not healthy")
+	}
+
+	return m.message.SendE2E(msg, e2eP)
+}
diff --git a/parse/body.go b/parse/body.go
deleted file mode 100644
index 165e7c8c4366dc1a4e0cb146e1313b2258e89dc1..0000000000000000000000000000000000000000
--- a/parse/body.go
+++ /dev/null
@@ -1,47 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package parse
-
-import (
-	"encoding/binary"
-	"github.com/pkg/errors"
-)
-
-// To allow mobile to bind this module if necessary, we'll return the two parts
-// of a body in a struct
-type TypedBody struct {
-	MessageType int32
-	Body        []byte
-}
-
-// Determine the type of a message body. Returns the type and the part of the
-// body that doesn't include the type.
-func Parse(body []byte) (*TypedBody, error) {
-	messageType, numBytesRead := binary.Uvarint(body)
-	if numBytesRead < 0 {
-		return nil, errors.New("Body type parse: Type too long. " +
-			"Set a byte's most significant bit to 0 within the first 8 bytes.")
-	}
-	result := &TypedBody{}
-	result.MessageType = int32(messageType)
-	result.Body = body[numBytesRead:]
-	return result, nil
-}
-
-// Pack this message for the network
-func Pack(body *TypedBody) []byte {
-	// Assumes that the underlying type of cmixproto.Type is int32
-	return append(TypeAsBytes(int32(body.MessageType)), body.Body...)
-}
-
-// Mobile or other packages can use this wrapper to easily determine the
-// correct magic number for a type
-func TypeAsBytes(messageType int32) []byte {
-	buf := make([]byte, binary.MaxVarintLen64)
-	n := binary.PutUvarint(buf, uint64(messageType))
-	return buf[:n]
-}
diff --git a/parse/body_test.go b/parse/body_test.go
deleted file mode 100644
index fd1e5a67360c2f996f5338cc73bf38c0a6406c2e..0000000000000000000000000000000000000000
--- a/parse/body_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package parse
-
-import (
-	"bytes"
-	"testing"
-)
-
-func TestParse(t *testing.T) {
-	body := []byte{0x80, 0x02, 0x89, 0x02, 0x03, 0x04}
-	actual, err := Parse(body)
-	expected := &TypedBody{}
-	expected.Body = []byte{0x89, 0x02, 0x03, 0x04}
-	expected.MessageType = 256
-
-	if err != nil {
-		t.Error(err.Error())
-	}
-
-	if actual.MessageType != expected.MessageType {
-		t.Errorf("Body type didn't match. Expected: %v, actual: %v",
-			expected.MessageType, actual.MessageType)
-	} else if !bytes.Equal(actual.Body, expected.Body) {
-		t.Errorf("Body didn't match. Expected: %v, actual: %v",
-			expected.Body, actual.Body)
-	}
-}
-
-func TestParseTypeTooLong(t *testing.T) {
-	body := []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
-		0x80, 0x80, 0x01, 0x02, 0x03, 0x04}
-	_, err := Parse(body)
-
-	if err == nil {
-		t.Error("Didn't get an error from Parse(" +
-			") when the body type was too long")
-	}
-}
-
-func TestTypeAsBytes(t *testing.T) {
-	expected := []byte{0x80, 0x02}
-	actual := TypeAsBytes(256)
-	if !bytes.Equal(expected, actual) {
-		t.Errorf("Type magic number didn't match. Expected: %v, actual: %v",
-			expected, actual)
-	}
-}
-
-func TestPack(t *testing.T) {
-	expected := []byte{0x01, 0x02, 0x03, 0x04}
-	actual := Pack(&TypedBody{
-		MessageType: 1,
-		Body:        []byte{0x02, 0x03, 0x04},
-	})
-	if !bytes.Equal(expected, actual) {
-		t.Errorf("Pack didn't return correctly packed byte slice. "+
-			"Expected: %v, actual: %v", expected, actual)
-	}
-}
diff --git a/parse/message.go b/parse/message.go
deleted file mode 100644
index 9e626dc2c524e9660275ee74854769b0852cd689..0000000000000000000000000000000000000000
--- a/parse/message.go
+++ /dev/null
@@ -1,149 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package parse
-
-import (
-	"crypto/sha256"
-	"gitlab.com/elixxir/primitives/id"
-	"time"
-)
-
-const MessageHashLenBits = 256
-const MessageHashLen = MessageHashLenBits / 8
-
-type MessageHash [MessageHashLen]byte
-
-type Message struct {
-	TypedBody
-	// The crypto type is inferred from the message's contents
-	InferredType CryptoType
-	Sender       *id.ID
-	Receiver     *id.ID
-	Nonce        []byte
-	Timestamp    time.Time
-}
-
-type CryptoType int32
-
-const (
-	None CryptoType = iota
-	Unencrypted
-	Rekey
-	E2E
-)
-
-var cryptoTypeStrArr = []string{"None", "Unencrypted", "Rekey", "E2E"}
-
-func (ct CryptoType) String() string {
-	return cryptoTypeStrArr[ct]
-}
-
-// Interface used to standardize message definitions
-type MessageInterface interface {
-	// Returns the message's sender ID
-	// (uint64) BigEndian serialized into a byte slice
-	GetSender() *id.ID
-	// Returns the message payload, without packed type
-	GetPayload() []byte
-	// Returns the message's recipient ID
-	// (uint64) BigEndian serialized into a byte slice
-	GetRecipient() *id.ID
-	// Return the message's inner type
-	GetMessageType() int32
-	// Returns the message's outer type
-	GetCryptoType() CryptoType
-	// Returns the message's timestamp in ns since linux epoc
-	GetTimestamp() time.Time
-	// Return the message fully serialized including the type prefix
-	// Does this really belong in the interface?
-	Pack() []byte
-}
-
-func (m Message) Hash() MessageHash {
-	var mh MessageHash
-
-	h := sha256.New()
-
-	h.Write(TypeAsBytes(int32(m.MessageType)))
-	h.Write(m.Body)
-	h.Write(m.Sender.Bytes())
-	h.Write(m.Receiver.Bytes())
-	//h.Write(m.Nonce)
-
-	hashed := h.Sum(nil)
-
-	copy(mh[:], hashed[:MessageHashLen])
-
-	return mh
-}
-
-func (m *Message) GetSender() *id.ID {
-	return m.Sender
-}
-
-func (m *Message) GetRecipient() *id.ID {
-	return m.Receiver
-}
-
-func (m *Message) GetPayload() []byte {
-	return m.Body
-}
-
-func (m *Message) GetMessageType() int32 {
-	return m.MessageType
-}
-
-func (m *Message) GetCryptoType() CryptoType {
-	return m.InferredType
-}
-
-func (m *Message) GetTimestamp() time.Time {
-	return m.Timestamp
-}
-
-func (m *Message) Pack() []byte {
-	return Pack(&m.TypedBody)
-}
-
-// Implements Message type compatibility with bindings package's limited types
-type BindingsMessageProxy struct {
-	Proxy *Message
-}
-
-func (p *BindingsMessageProxy) GetSender() []byte {
-	return p.Proxy.GetSender().Bytes()
-}
-
-func (p *BindingsMessageProxy) GetRecipient() []byte {
-	return p.Proxy.GetRecipient().Bytes()
-}
-
-// TODO Should we actually pass this over the boundary as a byte slice?
-//  It's essentially a binary blob, so probably yes.
-func (p *BindingsMessageProxy) GetPayload() []byte {
-	return p.Proxy.GetPayload()
-}
-
-func (p *BindingsMessageProxy) GetMessageType() int32 {
-	return int32(p.Proxy.GetMessageType())
-}
-
-func (p *BindingsMessageProxy) GetTimestampNano() int64 {
-	return p.Proxy.Timestamp.UnixNano()
-}
-
-func (p *BindingsMessageProxy) GetTimestamp() int64 {
-	return p.Proxy.Timestamp.Unix()
-}
-
-// Includes the type. Not sure if this is the right way to approach this.
-func (p *BindingsMessageProxy) Pack() []byte {
-	return Pack(&TypedBody{
-		MessageType: p.Proxy.GetMessageType(),
-		Body:        p.Proxy.GetPayload(),
-	})
-}
diff --git a/parse/message_test.go b/parse/message_test.go
deleted file mode 100644
index 15933f84cfa7c526a2b4cf1dd33af1c63eca751f..0000000000000000000000000000000000000000
--- a/parse/message_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package parse
-
-import (
-	"gitlab.com/elixxir/primitives/id"
-	"reflect"
-	"testing"
-	"time"
-)
-
-//Shows that MessageHash ia an independent function of every field in Message
-func TestMessage_Hash(t *testing.T) {
-	m := Message{}
-	m.MessageType = 0
-	m.Body = []byte{0, 0}
-	m.Sender = &id.ZeroUser
-	m.Receiver = &id.ZeroUser
-	m.Nonce = []byte{0, 0}
-
-	baseHash := m.Hash()
-
-	m.MessageType = 1
-
-	typeHash := m.Hash()
-
-	if reflect.DeepEqual(baseHash, typeHash) {
-		t.Errorf("Message.Hash: Output did not change with modified type")
-	}
-
-	m.MessageType = 0
-
-	m.Body = []byte{1, 1}
-
-	bodyHash := m.Hash()
-
-	if reflect.DeepEqual(baseHash, bodyHash) {
-		t.Errorf("Message.Hash: Output did not change with modified body")
-	}
-
-	m.Body = []byte{0, 0}
-
-	newID := id.NewIdFromUInt(1, id.User, t)
-	oldID := m.Sender
-	m.Sender = newID
-
-	senderHash := m.Hash()
-
-	if reflect.DeepEqual(baseHash, senderHash) {
-		t.Errorf("Message.Hash: Output did not change with modified sender")
-	}
-
-	m.Sender = oldID
-
-	m.Receiver = newID
-
-	receiverHash := m.Hash()
-
-	if reflect.DeepEqual(baseHash, receiverHash) {
-		t.Errorf("Message.Hash: Output did not change with modified receiver")
-	}
-
-	m.Receiver = oldID
-
-	// FIXME Add a "bake" step to the message to partition and nonceify it
-	// before hashing. We need this to be able to identify messages by their
-	// hash on both the message's sending and receiving clients.
-	//m.Nonce = []byte{1, 1}
-	//
-	//nonceHash := m.Hash()
-	//
-	//if reflect.DeepEqual(baseHash, nonceHash) {
-	//	t.Errorf("Message.Hash: Output did not change with modified nonce")
-	//}
-	//
-	//m.Nonce = []byte{0, 0}
-}
-
-func TestCryptoType_String(t *testing.T) {
-	cs := CryptoType(0)
-
-	if cs.String() != "None" {
-		t.Errorf("String() did not return the correct value"+
-			"\n\texpected: %s\n\treceived: %s",
-			cs.String(), "None")
-	}
-
-	cs = CryptoType(1)
-
-	if cs.String() != "Unencrypted" {
-		t.Errorf("String() did not return the correct value"+
-			"\n\texpected: %s\n\treceived: %s",
-			cs.String(), "Unencrypted")
-	}
-
-	cs = CryptoType(2)
-
-	if cs.String() != "Rekey" {
-		t.Errorf("String() did not return the correct value"+
-			"\n\texpected: %s\n\treceived: %s",
-			cs.String(), "Rekey")
-	}
-
-	cs = CryptoType(3)
-
-	if cs.String() != "E2E" {
-		t.Errorf("String() did not return the correct value"+
-			"\n\texpected: %s\n\treceived: %s",
-			cs.String(), "E2E")
-	}
-}
-
-func TestMessage_GetTimestamp(t *testing.T) {
-	testTime := time.Now()
-	message := Message{Timestamp: testTime}
-
-	if message.GetTimestamp() != testTime {
-		t.Errorf("GetTimestamp() did not return the correct timestamp"+
-			"\n\texpected: %v\n\treceived: %v", message.GetTimestamp(), testTime)
-	}
-}
-
-func TestBindingsMessageProxy_GetTimestamp(t *testing.T) {
-	testTime := time.Now()
-	message := BindingsMessageProxy{Proxy: &Message{Timestamp: testTime}}
-
-	if message.GetTimestamp() != testTime.Unix() {
-		t.Errorf("GetTimestamp() did not return the correct timestamp"+
-			"\n\texpected: %v\n\treceived: %v", message.GetTimestamp(), testTime.Unix())
-	}
-}
-
-func TestBindingsMessageProxy_GetTimestampNano(t *testing.T) {
-	testTime := time.Now()
-	message := BindingsMessageProxy{Proxy: &Message{Timestamp: testTime}}
-
-	if message.GetTimestampNano() != testTime.UnixNano() {
-		t.Errorf("GetTimestampNano() did not return the correct timestamp"+
-			"\n\texpected: %v\n\treceived: %v", message.GetTimestampNano(), testTime.UnixNano())
-	}
-}
diff --git a/parse/partition.go b/parse/partition.go
deleted file mode 100644
index b36c92de67722cfafaf71c7bd967b8c9cb507a0b..0000000000000000000000000000000000000000
--- a/parse/partition.go
+++ /dev/null
@@ -1,191 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package parse
-
-import (
-	"encoding/binary"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/primitives/format"
-	"math"
-	"sync"
-)
-
-func getMaxMessageLength() int {
-	return format.ContentsLen - format.PadMinLen
-}
-
-// TODO is there a better way to generate unique message IDs locally?
-func IDCounter() func() []byte {
-	// 32 bits to put a smaller upper bound on the varint size on the wire
-	// It should be possible to go back down to just a byte for the message ID
-	// field once the message format includes a timestamp, since you won't send
-	// more than 256 individual messages to one other user in a second. If we
-	// can use that timestamp information to help identify different multi-part
-	// messages, the currentID becomes a lot more superfluous.
-	currentID := uint32(0)
-	var mux sync.Mutex
-	return func() []byte {
-		// this will use up to 5 bytes for the message ID
-		result := make([]byte, binary.MaxVarintLen32)
-		mux.Lock()
-		n := binary.PutUvarint(result, uint64(currentID))
-		currentID++
-		mux.Unlock()
-		return result[:n]
-	}
-}
-
-const MessageTooLongError = "Partition(): Message is too long to partition"
-
-// length in bytes of index and max index together
-// change this if you change the index type
-const IndexLength = 2
-
-// The max index is 0 for one partition, 1 for two partitions, 2 for three
-// partitions, and so on. This assumes that each partition can be completely
-// filled.
-func GetMaxIndex(body []byte, id []byte) int32 {
-	bodyLen := len(body)
-	if bodyLen > 0 {
-		bodyLen--
-	}
-	maxIndex := bodyLen / (getMaxMessageLength() - len(id) - IndexLength)
-	return int32(maxIndex)
-}
-
-func Partition(body []byte, id []byte) ([][]byte, error) {
-	// index and quantity of the partitioned message are a fixed length of 8
-	// bits because sending more than that through the system is really slow and
-	// making them variable length makes the required length of the body part
-	// of the partitions different per partition depending on what the length
-	// of the index is for the input message
-	// the bottom line is that there's a dependency cycle to calculate the right
-	// number of partitions if you do them that way and i'm having none of that
-
-	// a zero here means that the message has one partition
-	maxIndex := GetMaxIndex(body, id)
-	if maxIndex > math.MaxUint8 {
-		return nil, errors.New(MessageTooLongError)
-	}
-
-	partitions := make([][]byte, maxIndex+1)
-	var lastPartitionLength int
-	partitionReadIdx := 0
-	for i := range partitions {
-		maxPartitionLength := getMaxMessageLength()
-		partitions[i], lastPartitionLength = makePartition(maxPartitionLength,
-			body[partitionReadIdx:], id, byte(i), byte(maxIndex))
-		partitionReadIdx += lastPartitionLength
-	}
-
-	var file []byte
-	for i := range partitions {
-		file = append(file, []byte(fmt.Sprintf("%q\n", partitions[i]))...)
-	}
-
-	return partitions, nil
-}
-
-// can you believe that golang doesn't provide a min function in the std lib?
-// neither can i
-func min(a int, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-// makePartition makes a new partition of a multi-part message and prepends the
-// id, index, and length that are needed to rebuild it on the receiving client.
-// It returns the new partition and the length of the body that it consumed
-// when making the new partition.
-func makePartition(maxLength int, body []byte, id []byte, i byte,
-	maxIndex byte) ([]byte, int) {
-
-	partition := make([]byte, 0, maxLength)
-
-	// Append the front matter
-	partition = append(partition, id...)
-	partition = append(partition, i, maxIndex)
-	lengthBeforeBodyAppend := len(partition)
-
-	// Find the biggest part of the body that can fit into the message length
-	bodyWriteLength := min(maxLength-len(partition), len(body))
-
-	// Append body
-	partition = append(partition, body[:bodyWriteLength]...)
-
-	// Return new partition and number of bytes from the body that are in it
-	return partition, len(partition) - lengthBeforeBodyAppend
-}
-
-// Assemble assumes that messages are correctly ordered by their index
-// It also assumes that messages have had all of their front matter and
-// padding stripped.
-func Assemble(partitions [][]byte) ([]byte, error) {
-	// this will allocate a bit more capacity than needed but not so much that
-	// it breaks the bank
-	result := make([]byte, 0, int(format.ContentsLen-format.PadMinLen)*
-		len(partitions))
-
-	for i := range partitions {
-		result = append(result, partitions[i]...)
-	}
-	return result, nil
-}
-
-type MultiPartMessage struct {
-	ID       []byte
-	Index    byte
-	MaxIndex byte
-	Body     []byte
-}
-
-func ValidatePartition(partition []byte) (message *MultiPartMessage,
-	err error) {
-	globals.Log.DEBUG.Printf("Partition: %v\n", partition)
-	// ID is first, and it's variable length
-	msbMask := byte(0x80)
-	indexInformationStart := 0
-	for i := 0; i < len(partition); i++ {
-		if msbMask&partition[i] == 0 {
-			// this is the last byte in the ID. stop the loop
-			indexInformationStart = i + 1
-			globals.Log.DEBUG.Println("Index information start:", indexInformationStart)
-			break
-		}
-	}
-	// validate: make sure that there's a payload beyond the front matter
-	if indexInformationStart+IndexLength >= len(partition) {
-		return nil, errors.New("There was nothing after the partition info")
-		// make sure that the ID is within the length we expect
-	} else if indexInformationStart > binary.MaxVarintLen32 {
-		return nil, errors.New("ID was longer than expected")
-		// make sure that the index is less than or equal to the maximum
-	} else if partition[indexInformationStart] > partition[indexInformationStart+1] {
-		return nil, errors.New(fmt.Sprintf(
-			"Index %v was more than max index %v",
-			partition[indexInformationStart],
-			partition[indexInformationStart+1]))
-		// make sure that we found a boundary between the index and ID
-	} else if indexInformationStart == 0 {
-		return nil, errors.New("Couldn't find end of ID")
-	}
-
-	result := &MultiPartMessage{
-		ID:       partition[:indexInformationStart],
-		Index:    partition[indexInformationStart],
-		MaxIndex: partition[indexInformationStart+1],
-		Body:     partition[indexInformationStart+2:],
-	}
-
-	globals.Log.DEBUG.Printf("Result of partition validation: %v, %v, %v, %v\n", result.ID,
-		result.Index, result.MaxIndex, string(result.Body))
-	return result, nil
-}
diff --git a/parse/partition_test.go b/parse/partition_test.go
deleted file mode 100644
index cfd856a57f84f4ff22b6741100e5648b4420a894..0000000000000000000000000000000000000000
--- a/parse/partition_test.go
+++ /dev/null
@@ -1,471 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package parse
-
-import (
-	"bytes"
-	"math/rand"
-	"testing"
-)
-
-func randomString(seed int64, length int) []byte {
-	buffer := make([]byte, length)
-	rand.Seed(seed)
-	rand.Read(buffer)
-	return buffer
-}
-
-// Partitioning an empty message should result in one byte slice with just
-// the front matter.
-func TestPartitionEmptyMessage(t *testing.T) {
-	id := []byte{0x05}
-	actual, err := Partition(randomString(0, 0), id)
-	if err != nil {
-		t.Error(err.Error())
-	}
-	expected := [][]byte{{0x05, 0x0, 0x0}}
-	for i := range actual {
-		if !bytes.Equal(actual[i], expected[i]) {
-			t.Errorf("Partition empty message: expected partition %v differed"+
-				" from actual partition %v", expected[i], actual[i])
-		}
-	}
-}
-
-// Partitioning a short message should result in one partition that includes
-// the short message.
-func TestPartitionShort(t *testing.T) {
-	id := []byte{0x03}
-	randomBytes := randomString(0, 50)
-	actual, err := Partition(randomBytes, id)
-	if err != nil {
-		t.Error(err.Error())
-	}
-	expected := [][]byte{{0x03, 0x0, 0x0}}
-	expected[0] = append(expected[0], randomBytes...)
-	for i := range actual {
-		if !bytes.Equal(actual[i], expected[i]) {
-			t.Errorf("Partition short message: expected partition %v differed"+
-				" from actual partition %v", expected[i], actual[i])
-		}
-	}
-}
-
-// Partitioning a longer message should result in more than one partition that,
-// in sum, contains the whole message.
-func TestPartitionLong(t *testing.T) {
-	id := []byte{0xa2, 0x54}
-	// This should be about the right length
-	// With the other length, the test panicked with out of bounds
-	randomBytes := randomString(0, getMaxMessageLength()*2-12)
-	actual, err := Partition(randomBytes, id)
-
-	if err != nil {
-		t.Error(err.Error())
-	}
-
-	expected := make([][]byte, 2)
-	// id
-	expected[0] = append(expected[0], id...)
-	// index
-	expected[0] = append(expected[0], 0, 1)
-	// part of random string
-	expected[0] = append(expected[0],
-		randomBytes[:getMaxMessageLength()-4]...)
-
-	// id
-	expected[1] = append(expected[1], id...)
-	// index
-	expected[1] = append(expected[1], 1, 1)
-	// other part of random string
-	expected[1] = append(expected[1],
-		randomBytes[getMaxMessageLength()-4:]...)
-
-	for i := range actual {
-		if !bytes.Equal(actual[i], expected[i]) {
-			t.Errorf("Partition long message: expected partition %v differed"+
-				" from actual partition %v", expected[i], actual[i])
-		}
-	}
-}
-
-// Due to the data types I used to fill out the front matter, there's a limit to
-// how many parts a message can be for one multi-part message. This test makes
-// sure that the indexes grow as expected to fill the whole space.
-func TestPartitionLongest(t *testing.T) {
-	// I'm assuming that 5 bytes will be the longest possible ID because that
-	// is the max length of a uvarint with 32 bits
-	id := []byte{0x1f, 0x2f, 0x3f, 0x4f, 0x5f}
-	actual, err := Partition(randomString(0, 52736), id)
-
-	if err != nil {
-		t.Fatalf(err.Error())
-	}
-
-	expectedNumberOfPartitions := 139
-
-	if len(actual) != expectedNumberOfPartitions {
-		t.Errorf("Expected a 52480-byte message to split into %v partitions, got %v instead",
-			expectedNumberOfPartitions,
-			len(actual))
-	}
-
-	// check the index and max index of the last partition
-	lastIndex := len(actual) - 1
-	expectedIdx := byte(138)
-	idxLocation := len(id)
-	maxIdxLocation := len(id) + 1
-	actualIdx := actual[lastIndex][idxLocation]
-	actualMaxIdx := actual[lastIndex][maxIdxLocation]
-	if actualIdx != expectedIdx {
-		t.Errorf("Expected index of %v on the last partition, got %v",
-			expectedIdx, actualIdx)
-	}
-	if actualMaxIdx != expectedIdx {
-		t.Errorf("Expected max index of %v on the last partition, got %v",
-			expectedIdx, actualMaxIdx)
-	}
-}
-
-// Tests production of the error that occurs when you ask to partition a
-// message that's too long to partition
-func TestPartitionTooLong(t *testing.T) {
-	id := []byte{0x1f, 0x2f, 0x3f, 0x4f, 0x5f}
-	_, err := Partition(randomString(0, 257856), id)
-
-	if err == nil {
-		t.Error("Partition() processed a message that was too long to be" +
-			" partitioned")
-	}
-}
-
-// Tests Assemble with a synthetic test case, without invoking Partition.
-func TestOnlyAssemble(t *testing.T) {
-	messageChunks := []string{"Han Singular, ", "my child, ",
-		"awaken and embrace ", "the glory that is", " your birthright."}
-
-	completeMessage := ""
-	for i := range messageChunks {
-		completeMessage += messageChunks[i]
-	}
-
-	partitions := make([][]byte, len(messageChunks))
-	for i := range partitions {
-		partitions[i] = append(partitions[i], []byte(messageChunks[i])...)
-	}
-
-	assembled, err := Assemble(partitions)
-	if err != nil {
-		t.Error(err.Error())
-	}
-	if completeMessage != string(assembled) {
-		t.Errorf("TestOnlyAssemble: got \"%v\";\n expected \"%v\".",
-			string(assembled), completeMessage)
-	}
-}
-
-// This tests the pipeline end-to-end, making sure that the same text that goes
-// into partitioning can come out of it intact.
-func TestAssembleAndPartition(t *testing.T) {
-	expected := []string{
-		"short message",
-		// 203 bytes: definitely fits in one partition (passes)
-		"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
-		//204 bytes: should fit in one partition (used to fail)
-		"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-		// 205 bytes: just long enough for two partitions (passes)
-		"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
-		// 5008 bytes
-		`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tristique neque sed diam efficitur pulvinar. Proin posuere tortor id sodales elementum. Ut nec viverra libero. Proin et dui consequat nulla rhoncus facilisis. Phasellus semper at tortor ut ullamcorper. Aliquam accumsan auctor elit, vel tincidunt nulla bibendum et. Integer dictum ligula mauris, sit amet dignissim quam ornare sed. Mauris diam orci, ultrices vitae tellus non, faucibus scelerisque ante. Morbi fringilla massa purus, eu fringilla eros ultricies vel. Suspendisse nisi nisl, interdum quis porttitor quis, facilisis ac mauris. Integer in pretium erat, sed egestas quam. Donec eleifend felis dapibus mauris ullamcorper feugiat. Nulla at pharetra lectus. Pellentesque libero metus, efficitur at venenatis non, pharetra eu nisl. Donec id lorem dignissim, euismod elit vel, efficitur lacus. In finibus, orci ut rhoncus mollis, sem ex aliquet nunc, sed pretium eros justo ac tortor. Etiam vehicula dapibus lectus sed condimentum. Cras porta nulla sit amet pretium suscipit. Vivamus vestibulum sed nibh non vestibulum. Suspendisse sit amet purus at sapien mollis sollicitudin eu id turpis. Nulla dapibus in urna sit amet luctus. Proin faucibus quis dui porta volutpat. Duis sed ultrices lacus. Integer interdum finibus sem, in finibus urna eleifend at. Curabitur urna mi, auctor et ligula a, tristique pretium ex. Vivamus vitae felis non nunc rhoncus mattis. Integer fringilla volutpat lorem ac dictum. Praesent sed nibh et purus sollicitudin iaculis at eu metus. Nunc lobortis fermentum magna, quis varius velit blandit vel. Quisque fringilla lacinia magna ac euismod. Vestibulum velit ipsum, bibendum sagittis leo sed, pretium porta magna. Nulla facilisi. Aenean elementum posuere consequat. Cras placerat vulputate magna, at condimentum nibh sagittis quis. Pellentesque auctor tortor vehicula ante tristique, in auctor purus efficitur. Vivamus sapien lorem, viverra ut lacinia at, laoreet nec diam. Proin finibus, elit ac ultricies fermentum, eros erat imperdiet lacus, sed laoreet dui elit sed odio. Etiam id hendrerit quam, quis rhoncus mauris. Proin ac ante bibendum, malesuada mauris vitae, tempor quam. Nulla vitae pulvinar nunc. Vestibulum quis vulputate risus, et gravida enim. Sed tellus lacus, sagittis sit amet sodales non, varius ultrices massa. Aliquam nec volutpat sem. Nam porttitor, nibh vitae iaculis posuere, magna ante placerat elit, ut suscipit odio dui vitae libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam tincidunt tellus risus, eget laoreet odio suscipit et. Nulla scelerisque interdum sapien, in tempus mi malesuada vel. Donec et urna sit amet purus pulvinar tincidunt. Mauris fermentum quis lacus at scelerisque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean viverra erat vel sagittis blandit. Praesent in purus sed tortor consequat vehicula. Mauris non iaculis diam, nec vestibulum nisl. Phasellus arcu mi, luctus id felis sit amet, feugiat pellentesque tortor. Curabitur at dui dolor. Nunc semper quam pharetra, suscipit quam at, fringilla justo. In feugiat ipsum eu lectus aliquet ultrices. Curabitur fringilla tincidunt vehicula. Donec laoreet facilisis ante ac maximus. Aliquam lectus diam, pulvinar quis arcu in, molestie tincidunt quam. Sed aliquet orci id arcu finibus congue. Ut nulla lacus, dictum eget sem in, condimentum mattis massa. Donec suscipit, sapien nec euismod tincidunt, velit lectus iaculis ligula, sed sagittis tellus odio at nisl. Aenean mattis tellus in convallis aliquet. Duis posuere, augue id pellentesque accumsan, enim orci congue diam, a venenatis metus tellus id nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed ex massa, lobortis sed risus in, blandit tincidunt enim. Suspendisse fringilla lacinia velit sit amet varius. Donec ac malesuada nisl, vel sagittis mauris. Sed eu blandit orci. Ut porta orci sed dui blandit tristique. Donec ac tellus et nisl fermentum volutpat. Nullam ipsum mi, aliquet ut mattis non, imperdiet non massa. Phasellus tincidunt mauris ac convallis convallis. Nunc blandit velit vel fermentum rhoncus. Nam dictum mi in fringilla semper. Nunc tristique congue velit et cursus. Vivamus rhoncus porta lacus posuere sodales. Quisque in interdum lectus, in imperdiet lacus. Proin vel arcu non arcu commodo rhoncus ac rhoncus velit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla eget massa et nibh suscipit egestas nec a justo. Morbi a semper diam, vitae rutrum odio. Nunc a nisl quam. Proin ornare luctus sem, et rhoncus est mattis in. Donec hendrerit, augue id sodales maximus, justo magna faucibus libero, eu hendrerit diam elit vel massa. Nulla dictum purus nisi, eget varius dui lacinia non. Fusce ut mauris ut massa imperdiet consequat. Proin id eros vitae odio gravida convallis. Donec faucibus, massa quis volutpat. `,
-		// Near max length of multi-part messages
-		`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ipsum odio, suscipit nec tempor vitae, pretium convallis felis. Integer rutrum dolor sit amet tellus semper volutpat. Mauris eleifend massa ac iaculis aliquam. Ut ac urna faucibus, commodo risus vitae, eleifend urna. Duis nec diam quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum aliquam at elit ut eleifend. Phasellus id massa malesuada, elementum ante sed, porta velit. In finibus a nulla at maximus. Suspendisse condimentum consequat volutpat. Cras varius volutpat dapibus. Curabitur venenatis semper varius. Etiam condimentum ligula diam, eu commodo purus placerat id. Morbi ut ipsum ac elit egestas tempor.
-
-Ut consectetur elementum orci, nec feugiat sapien tincidunt eget. Vivamus tincidunt ut massa nec imperdiet. Suspendisse potenti. Donec venenatis iaculis libero, vel facilisis diam dictum vel. Nam id orci turpis. Integer nec turpis id nunc consequat ornare. Aenean rhoncus interdum tortor, ut suscipit leo faucibus ornare. Etiam fringilla neque sit amet dolor ullamcorper rutrum. Quisque pharetra maximus nibh quis tincidunt. Phasellus eu risus nisi. Nunc vitae viverra tellus, id porta nisi. Proin condimentum fringilla risus, ut placerat arcu imperdiet eget. Nunc nec interdum est. Sed semper, purus non cursus interdum, turpis tellus ultricies felis, nec venenatis velit nunc at lacus.
-
-Phasellus et eleifend quam, sit amet sagittis sem. Mauris in sagittis quam, non viverra metus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed vitae bibendum quam, id convallis nisi. Sed ultricies malesuada enim, non posuere mi tempor at. Nam quis ante quis leo rhoncus semper vel at metus. Vestibulum vitae viverra leo, nec luctus ante. Maecenas sed elementum purus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed ornare odio ut mi tempor mattis.
-
-Mauris a ex sem. Praesent efficitur lacinia ligula et rhoncus. Quisque id nunc urna. Nulla laoreet urna suscipit, varius quam eget, consectetur est. Donec suscipit, quam vestibulum vehicula viverra, ipsum purus aliquam magna, sed elementum leo nulla sed augue. Curabitur semper non ligula sed varius. Vivamus nec nulla dolor. Sed ornare commodo mollis. Pellentesque augue ipsum, imperdiet id laoreet non, aliquam id ex. Donec vel enim nibh.
-
-Vivamus accumsan, nunc sit amet lacinia condimentum, metus libero mollis velit, sit amet gravida nulla ex vel orci. Quisque gravida, nisi vel molestie ultrices, mauris dolor viverra lacus, at egestas turpis nibh tincidunt elit. Praesent luctus nunc ut mollis malesuada. Integer mi risus, dapibus tristique justo ac, malesuada commodo felis. Donec mollis tincidunt ex sed bibendum. Curabitur vitae blandit lorem, nec finibus turpis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin mattis, metus in porttitor tincidunt, nibh ligula lacinia tellus, vel finibus mauris sapien eget odio. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque mattis velit sed nunc tincidunt iaculis. Nam commodo nulla ac diam semper tempus. Integer risus libero, aliquam vitae lectus vitae, fermentum efficitur neque. Praesent tempor nisi nisl, quis rutrum mi sodales a. Phasellus quam est, sagittis eu bibendum sed, eleifend non ipsum. Sed magna tortor, sollicitudin a sem ac, bibendum cursus eros. Aliquam dictum risus quis felis consectetur tempor.
-
-Donec elementum, velit a consectetur euismod, mi enim lobortis odio, id sodales nulla odio efficitur justo. Aliquam erat volutpat. Integer nisl ex, tincidunt fermentum vehicula sed, malesuada non dui. Proin a lacinia diam. Quisque et eros gravida, ultricies dui vel, cursus tellus. Pellentesque eget dictum orci. Fusce metus lectus, eleifend eget tempus non, laoreet quis dolor. Etiam dui odio, blandit vel erat nec, ultricies molestie nulla. Etiam urna enim, imperdiet vestibulum dolor in, lobortis aliquam mi. Maecenas tincidunt sed lorem sed malesuada. Suspendisse elementum rutrum massa, sit amet bibendum metus tincidunt sed. Quisque et euismod metus.
-
-Pellentesque auctor, risus sit amet pellentesque tempor, odio metus sodales tortor, commodo blandit nibh nisi eget sem. Nunc non mattis erat, sit amet imperdiet justo. Vestibulum commodo nisl id facilisis eleifend. Duis a nulla sapien. Donec vulputate lobortis odio. Aliquam in enim porttitor, dictum ante id, condimentum orci. In hac habitasse platea dictumst.
-
-Vestibulum sollicitudin sollicitudin urna, non suscipit dolor tristique ac. Nulla congue non odio non elementum. Integer id leo diam. Curabitur sed massa mi. Curabitur maximus elit mi, nec placerat felis scelerisque eu. Maecenas vitae ante ex. Sed interdum suscipit tortor. Integer semper dolor nisl, sed tempus dui pulvinar ornare. Curabitur lobortis orci nibh, vitae pharetra est imperdiet non. Mauris sodales vehicula neque id hendrerit. Etiam tincidunt elit metus, vitae sollicitudin mi aliquet non. Ut faucibus eleifend vulputate. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer ut tincidunt turpis, eu dictum elit. Etiam bibendum fringilla est. Nullam sed congue quam.
-
-Phasellus dapibus leo est, id elementum dolor tristique in. Duis vitae porttitor lectus. Vivamus convallis tortor ex, sed ullamcorper est interdum sed. Quisque ante quam, scelerisque at efficitur eget, facilisis ut mi. Vestibulum sit amet facilisis turpis. Fusce interdum gravida efficitur. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas tincidunt sagittis pulvinar. Suspendisse imperdiet, justo ut aliquet posuere, mauris odio tincidunt purus, id finibus erat lacus in nisi. Nam vitae consectetur risus. Etiam diam lorem, maximus vel vestibulum quis, lobortis in mi. Nulla eu sagittis lectus.
-
-Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam erat volutpat. Morbi sollicitudin aliquam tellus, sed porttitor lorem imperdiet quis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin nulla ipsum, sagittis et egestas in, egestas eget mauris. Etiam maximus eros sit amet luctus elementum. Praesent nibh lacus, ultrices vel sodales vitae, rhoncus vestibulum nulla. Duis accumsan lacinia dui sit amet convallis. Nullam massa neque, varius eu orci a, pretium porttitor risus. Fusce mollis ante sit amet velit sollicitudin hendrerit. Morbi eu iaculis ante. Aliquam laoreet nisi a ex dignissim lobortis in vel felis. Maecenas sagittis nibh ut purus tempor, at elementum velit commodo. Fusce ac neque pharetra dolor varius facilisis.
-
-Nam ex purus, luctus sit amet lorem vitae, volutpat auctor tellus. Fusce euismod nibh non ex vestibulum, in iaculis justo placerat. Vivamus vulputate interdum molestie. Nulla pretium tempus nulla nec fermentum. Morbi blandit, metus non suscipit maximus, massa nisi sodales leo, eleifend ornare ex dolor ut justo. Sed pellentesque nec ex vitae placerat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque vel magna non tortor vulputate tristique.
-
-Sed facilisis libero nec arcu cursus pulvinar. Quisque ipsum elit, porttitor non risus porta, tincidunt mattis ex. Fusce convallis ligula eget consectetur fringilla. Quisque fermentum, leo at porttitor semper, est elit fermentum mi, vitae efficitur felis magna ac mauris. Quisque ut efficitur lectus. Proin consequat imperdiet odio. Nulla dui lorem, viverra a dapibus laoreet, lacinia in libero. Suspendisse congue, diam ac facilisis hendrerit, tortor lorem sollicitudin diam, in dapibus lacus erat vel arcu. Praesent dolor nisl, commodo ut condimentum quis, mattis sed risus. Vivamus a ante et neque efficitur aliquet.
-
-Nunc eget blandit nisi. Nullam pretium felis ac neque fermentum fermentum. Proin varius nulla ex, ut vulputate neque consequat quis. Etiam ac enim egestas felis luctus dapibus. Nullam vel eros eget dui euismod vestibulum. Vestibulum interdum quam libero, id blandit quam efficitur vitae. Integer viverra iaculis nibh consectetur euismod. Integer laoreet eu risus sed venenatis. Ut quis neque tortor. Maecenas vulputate magna eu mi tincidunt cursus eget vitae elit. Nam a mi hendrerit, porta orci in, dignissim est.
-
-Phasellus sit amet ligula nec lorem commodo dignissim. Sed gravida, augue eu pharetra bibendum, augue justo ultrices sapien, molestie tempor quam mauris eget ipsum. Ut tristique tristique arcu, elementum pulvinar sapien aliquam a. Aliquam nec eros faucibus, laoreet massa eu, imperdiet velit. Suspendisse finibus nisl ac tellus fringilla, eget aliquet sem dapibus. Ut quam turpis, aliquam ac fringilla non, imperdiet sodales eros. Phasellus maximus tristique erat, a viverra metus imperdiet ac. Vivamus faucibus ante id porttitor volutpat.
-
-Vestibulum ullamcorper eleifend velit, id tempus nisl varius et. Cras vitae nunc nibh. Nam dictum facilisis dictum. Cras blandit id tellus sed blandit. Sed porta eros id mi venenatis facilisis. Nullam sed felis sed tellus dictum lobortis bibendum ac libero. Aenean et mollis ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent faucibus faucibus fermentum. Vestibulum id risus placerat, rhoncus arcu eu, tincidunt lectus. Sed interdum venenatis fermentum. Mauris ultricies nisl diam, sit amet fermentum dui blandit sed.
-
-Fusce tempor semper tellus vitae facilisis. Nam suscipit sapien vel nulla porta aliquet. Vestibulum fringilla condimentum dictum. Nullam pharetra dolor non mauris fermentum, eget elementum nisi dictum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed eu tristique erat, vitae lacinia felis. Sed tincidunt magna quis luctus pulvinar. Donec commodo, velit iaculis ullamcorper posuere, elit nunc commodo felis, at sagittis ex sapien vitae purus.
-
-Donec ac ligula in dui laoreet aliquet. Etiam lobortis arcu lectus, facilisis bibendum turpis feugiat sit amet. In in ex non arcu egestas luctus. Aenean eget diam vel ex auctor pretium. Vivamus elementum ac mauris eu interdum. Curabitur maximus elit sed egestas dictum. Phasellus mattis velit vel enim bibendum blandit. Nullam hendrerit est eros. Vivamus eu tincidunt quam. Sed vel efficitur erat. Etiam sit amet diam a arcu malesuada vulputate. Ut mattis justo id velit lacinia suscipit. Sed sit amet massa ante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean fermentum id mi id bibendum. Vestibulum metus velit, consequat ac imperdiet mollis, ultrices et nunc.
-
-In hac habitasse platea dictumst. Cras sit amet vestibulum turpis, quis posuere nunc. Proin cursus sapien laoreet felis vehicula, et condimentum diam ultrices. Vivamus a accumsan ligula. Aliquam tempor sollicitudin quam, eu auctor odio dapibus sit amet. Proin interdum rhoncus lacus sit amet feugiat. Maecenas non dui et sem consectetur rutrum quis sed nunc. Maecenas lobortis neque lorem, sed tincidunt justo pharetra non.
-
-Nam vel mauris sit amet ex viverra accumsan. Proin vel rutrum leo. Mauris mi nisl, mattis eget ornare ut, congue vitae arcu. Nam consequat mi sed fringilla dictum. Vivamus sodales, enim sed condimentum viverra, magna velit commodo tortor, quis dictum lacus mi eget lectus. Vestibulum dignissim dui magna, sit amet tempor est semper vel. Aliquam ornare nunc eu porttitor mollis. Maecenas nec augue nec nisl luctus eleifend sed et ligula.
-
-Morbi sollicitudin nisl mi, sed bibendum enim efficitur sit amet. Ut luctus magna leo, in semper mi imperdiet molestie. Cras molestie augue iaculis sem pharetra, quis congue nisi volutpat. Maecenas sodales interdum justo, a dictum mauris placerat id. Suspendisse eleifend dui vitae tortor pellentesque, eget condimentum nisi euismod. Fusce lobortis, sem et lacinia iaculis, nisl lectus eleifend leo, eget consectetur purus nulla in diam. Maecenas ultricies dignissim mauris eu faucibus. Nulla facilisi. Nunc lobortis justo nec egestas blandit.
-
-Praesent mattis maximus nibh sed ultricies. Morbi venenatis nunc id sollicitudin blandit. Integer commodo hendrerit lorem, id mollis velit luctus ut. Phasellus posuere mi eu vulputate gravida. Nullam euismod, diam at feugiat dictum, nunc felis gravida ante, accumsan elementum purus sem ut lectus. Nam lacinia odio tristique, maximus tellus ac, finibus nibh. Maecenas venenatis ipsum sed orci euismod tincidunt. Nulla convallis mauris non lectus ultricies semper.
-
-Quisque sed sodales velit. Nunc gravida commodo scelerisque. Nam ultricies posuere neque in tincidunt. Proin a massa id metus egestas vehicula eget quis eros. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus sit amet scelerisque enim, in posuere quam. Sed orci ante, tempus eget justo et, vestibulum faucibus eros. Curabitur quam sapien, suscipit quis aliquet nec, aliquet vel ipsum. Cras elit purus, sodales non nulla eu, cursus tristique neque. Phasellus pulvinar pellentesque diam eu pulvinar. In faucibus tincidunt mi.
-
-Nunc tincidunt diam non nulla auctor iaculis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a placerat nisl. Praesent dictum, velit eget fermentum aliquam, sapien eros ultrices enim, eu pharetra mauris dui a risus. In hac habitasse platea dictumst. Nullam ut elit non nisl scelerisque fringilla quis id velit. Phasellus eu dui sodales, mollis diam quis, commodo quam. Nulla cursus dapibus odio ut placerat. Curabitur id rutrum lorem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus sed cursus risus, nec eleifend orci. Nulla auctor, enim non auctor viverra, nisl mi viverra sem, eu molestie leo nisi non nibh. In dolor neque, accumsan ut felis et, mattis cursus purus.
-
-Suspendisse nisl dolor, lacinia at vulputate et, accumsan sed magna. Aenean blandit massa at eros lobortis luctus. Cras ac arcu vitae turpis consequat lacinia. Pellentesque pellentesque nisi in turpis mattis mattis. Aliquam erat volutpat. Suspendisse accumsan est dapibus tellus finibus venenatis. Quisque blandit tellus id rhoncus mattis. Suspendisse potenti. Aliquam ultricies, libero vel condimentum volutpat, nunc velit posuere urna, sed hendrerit quam sapien vitae sapien.
-
-Nulla euismod faucibus ipsum in euismod. Fusce sed maximus nisl. Ut sit amet risus convallis, condimentum elit ac, viverra metus. Vivamus dignissim ligula eu tellus scelerisque, non vestibulum orci malesuada. Aliquam congue at felis non rhoncus. Nullam dapibus eros risus, in tristique ipsum commodo ac. Sed porttitor eros vel libero placerat, et molestie enim scelerisque. Praesent eu sapien dictum, laoreet libero suscipit, gravida sem. Donec auctor dolor a orci rhoncus porttitor. Maecenas nec volutpat ipsum. Donec commodo eu metus quis finibus. Quisque mi quam, dictum non ullamcorper id, placerat sit amet libero. Praesent interdum aliquam ornare. Curabitur blandit lorem arcu, sit amet semper elit elementum eu. Proin sit amet orci eget mi viverra semper.
-
-Nullam felis elit, consectetur sed volutpat nec, tincidunt et erat. Sed quis facilisis neque. Morbi hendrerit nisi dolor, quis egestas odio iaculis sed. Sed ultricies magna non nisi aliquet, id facilisis dui convallis. Suspendisse commodo augue ac lobortis facilisis. Ut magna dolor, condimentum vitae feugiat in, ullamcorper nec urna. Donec cursus ultrices nibh vel dignissim. Proin enim purus, dapibus vitae tempor et, tristique ac libero. Nullam mi tellus, consequat nec sem sed, pellentesque feugiat dolor. Cras luctus vestibulum neque et rhoncus. Pellentesque laoreet quam id porta fringilla. Proin commodo egestas turpis id venenatis. Pellentesque dictum massa eu gravida pulvinar. Vivamus eget finibus ante. Donec eu urna et urna volutpat feugiat. Phasellus ex est, ultricies ut risus ac, finibus posuere dui.
-
-Praesent nulla lectus, tincidunt non pharetra et, ultrices nec enim. Aenean ultricies rhoncus nisl, fringilla accumsan tortor pretium ac. Suspendisse efficitur tempor suscipit. Cras finibus eros vel eros semper condimentum. Aliquam quis odio quis enim aliquam fringilla sed vitae nisi. Ut dignissim erat odio, non eleifend orci maximus tincidunt. Vestibulum tincidunt, ligula vitae cursus tincidunt, nulla orci accumsan orci, eu tincidunt arcu orci vitae odio. Nulla facilisi. Donec at lectus eget metus vulputate cursus non venenatis quam. Proin nisi nisi, sodales id molestie quis, fringilla ut sapien. Donec maximus tempor libero, in pretium urna luctus suscipit. Morbi egestas ac ante ut feugiat. Donec tincidunt maximus neque vitae porta.
-
-Fusce massa est, tristique nec lobortis nec, pharetra id augue. Ut finibus quam et ex varius imperdiet. In sed lectus sit amet nisl vehicula sodales id ac ex. Vivamus sit amet pulvinar dolor. Proin elementum dolor id sollicitudin pulvinar. Curabitur quis nunc elit. Nulla ac consequat lectus, non varius diam. In faucibus id lorem non feugiat. In viverra massa at turpis tempor, sed consectetur velit malesuada.
-
-Integer vestibulum, metus ut tempor ultrices, neque quam gravida eros, at eleifend orci quam vel leo. Pellentesque eleifend porta libero, suscipit aliquam arcu volutpat non. Fusce id consectetur nunc. Nullam eleifend semper lorem, vitae blandit libero tempus ac. Quisque elementum ligula pellentesque feugiat dapibus. Nullam luctus in mi eget rutrum. Sed faucibus pulvinar libero at congue. Sed vestibulum orci a diam tempus venenatis sed in quam. Pellentesque sit amet convallis lorem, a ullamcorper tellus. Nunc rutrum dui ac odio accumsan viverra. Duis lacinia risus in orci accumsan sagittis. Fusce at aliquam lacus, eu feugiat ex. Mauris neque diam, malesuada eu dui sed, tincidunt euismod mi. Fusce non elit varius, ornare nunc sit amet, tempus urna. Mauris erat turpis, tempus vel nulla sit amet, malesuada lacinia est.
-
-Pellentesque et eleifend nisi, quis ullamcorper ligula. Duis eu urna quis augue suscipit blandit. Nulla sodales vel dui nec condimentum. Donec gravida, turpis sed ornare maximus, erat risus vestibulum ex, vitae volutpat quam sapien eget mauris. Integer efficitur bibendum metus nec suscipit. Suspendisse ac eros a dolor aliquet ultrices eu auctor ipsum. Nulla feugiat tristique urna ut aliquet. Vestibulum mi purus, dignissim sit amet porttitor eu, efficitur eget elit. Duis sit amet est eget lorem tincidunt dictum.
-
-Proin congue finibus nunc, at fermentum dui. Nullam auctor eleifend elit quis malesuada. Ut sit amet leo rhoncus, semper arcu ut, ornare ante. Fusce feugiat non mauris sit amet pulvinar. Pellentesque sagittis fermentum massa, a varius quam feugiat eget. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean eget urna nec odio scelerisque lacinia sit amet a ante. Quisque et posuere mi. Aenean congue posuere imperdiet. Nullam et mauris suscipit, sollicitudin lectus vitae, eleifend nisl. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum imperdiet laoreet neque. Ut quis magna sodales, molestie arcu at, dictum lectus.
-
-Praesent in commodo lorem. Curabitur at velit nisi. Donec scelerisque quam mi, sed porttitor ex varius malesuada. Curabitur a nibh nec justo dictum faucibus non volutpat diam. Ut ultrices luctus leo, vitae pretium tellus posuere eget. Nulla ligula magna, accumsan eget quam vitae, tempus dictum ante. Morbi efficitur ut augue eget efficitur. Nulla consequat, lacus vitae bibendum efficitur, ante ex porttitor odio, ut porta lectus nisi quis ipsum. Sed vulputate in ex eget rutrum.
-
-Aliquam erat volutpat. Praesent quis nisi luctus, accumsan dolor et, hendrerit orci. Phasellus tempor ante quis ex auctor interdum vestibulum sed tortor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse quam justo, dapibus eget ex eget, molestie lacinia ipsum. Etiam risus felis, rutrum id tincidunt in, ullamcorper ut diam. Mauris et accumsan dui. Vivamus eleifend euismod luctus.
-
-Fusce luctus lorem id metus hendrerit semper. Sed accumsan, urna non vehicula accumsan, mi metus imperdiet dolor, a porttitor nulla dolor id sem. Fusce tellus massa, viverra quis fermentum mattis, finibus lacinia lorem. Ut vulputate augue ut lectus porta semper. Donec quis pharetra ligula. Etiam ut ante ligula. Fusce lobortis tempus urna cursus faucibus. Mauris pellentesque dolor sed condimentum rhoncus. Fusce fringilla aliquet turpis, mattis dignissim elit sollicitudin in. Proin placerat viverra finibus.
-
-Praesent efficitur sed arcu ut venenatis. Praesent facilisis consectetur eleifend. Vestibulum molestie dui vitae rutrum convallis. Ut interdum, diam eget eleifend hendrerit, turpis quam molestie turpis, vel suscipit augue diam a elit. Vivamus vel malesuada sapien. Duis euismod ipsum erat, quis venenatis lorem ultricies in. Nunc at ultricies urna, sed commodo lacus. Aliquam et tincidunt mi. Quisque ut leo semper, vehicula urna non, semper felis. Phasellus interdum aliquet eros eget egestas.
-
-Donec scelerisque sit amet ante accumsan iaculis. Vestibulum ac mi non augue pharetra laoreet non vitae tortor. Sed magna lorem, finibus eget dolor id, blandit porta leo. Etiam semper dolor mi, quis hendrerit nunc cursus nec. Proin interdum quis justo in pretium. Interdum et malesuada fames ac ante ipsum primis in faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque id faucibus mauris. Suspendisse euismod diam vitae ligula pellentesque aliquet. Praesent ornare dictum neque. Quisque at dolor nec dui lobortis varius. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut vitae elit pellentesque, vulputate diam eu, aliquet dui. Curabitur at quam sollicitudin, pretium nibh quis, accumsan est. Phasellus rhoncus cursus eros, eget imperdiet tortor iaculis at.
-
-Praesent semper pretium urna. Fusce mattis pharetra erat, eu ornare arcu dictum eu. Etiam maximus ligula leo, eu ullamcorper massa aliquam tempor. Nunc metus est, lacinia sed aliquet at, hendrerit a tellus. Maecenas cursus id leo in porttitor. Sed vestibulum sodales ex, eget malesuada mauris ornare sit amet. Morbi sed pharetra arcu. Pellentesque gravida convallis libero nec finibus. Sed tellus nisl, fringilla quis mauris ac, tincidunt scelerisque metus. Mauris feugiat tristique odio vel porta. Etiam tincidunt enim quis tellus eleifend, sed pellentesque metus accumsan. Suspendisse consequat commodo sem congue iaculis. Donec egestas eleifend mauris blandit tempor.
-
-Integer ut pellentesque elit. Proin a urna tellus. Aenean efficitur quam ipsum, a laoreet sapien egestas vitae. Fusce commodo consectetur leo, a molestie mi facilisis quis. Etiam varius tempus mauris, nec mattis nisi sagittis eu. Duis faucibus risus sit amet justo venenatis, sed tempor odio aliquam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse et suscipit massa, non hendrerit est. Praesent laoreet quam eget quam eleifend consectetur. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
-
-Aliquam et bibendum tortor. Duis condimentum aliquet sollicitudin. Aenean lobortis, nulla vel auctor molestie, magna eros ultrices sapien, in tristique metus risus vel tortor. Nullam ac pellentesque ipsum. Maecenas vitae justo vitae ante fermentum vulputate eget vel augue. Cras laoreet dui enim, a hendrerit purus interdum vel. Pellentesque eleifend ut urna non rhoncus. Sed nec tortor scelerisque, vestibulum est eget, tincidunt dui. Praesent maximus commodo ante, in fermentum lectus dignissim id.
-
-Ut sit amet facilisis arcu. Integer viverra dui id lorem tempor, in pharetra elit dapibus. Pellentesque volutpat suscipit velit sit amet maximus. Curabitur nulla eros, finibus vitae semper sed, commodo ac massa. Aliquam erat volutpat. Proin tempus, turpis suscipit volutpat imperdiet, velit tortor luctus magna, eu fringilla felis enim nec ante. Nunc euismod odio sed hendrerit consectetur. Nam finibus justo sit amet leo rutrum consectetur. Phasellus quis neque gravida, laoreet metus hendrerit, vehicula elit. Morbi pulvinar est vel neque facilisis porta. Nullam ut faucibus neque, sed ornare sem. Duis pulvinar eget nisi et hendrerit. Donec in ante pellentesque metus porta viverra ac non orci. Sed accumsan metus nec ipsum blandit, non commodo urna lobortis. Nunc in est congue, viverra urna non, porttitor est. Curabitur ultrices eros ut quam finibus lobortis.
-
-Mauris tempus consequat urna, ut rutrum ante placerat in. Donec quis pulvinar magna. Duis malesuada condimentum quam vel feugiat. Sed eget magna eget libero tempor ornare. Maecenas nec augue eros. Donec efficitur vestibulum augue in vestibulum. Sed velit ex, viverra in felis a, pharetra mattis eros. Cras ut mi ante.
-
-Vivamus id semper elit, a eleifend mauris. Phasellus venenatis pulvinar massa aliquet ultrices. Maecenas sagittis arcu augue, vel vulputate nibh dignissim sed. Mauris scelerisque luctus erat, eget vestibulum justo. Curabitur id consectetur ex. Quisque a ipsum mattis massa efficitur facilisis. Vestibulum eu augue erat. Praesent in consectetur lorem. Sed placerat semper nibh, vel imperdiet justo. Proin at velit lacus.
-
-Nunc ut enim ac tellus aliquam feugiat malesuada nec ligula. Suspendisse lectus quam, pulvinar vitae mi sit amet, rhoncus dapibus turpis. Fusce laoreet rhoncus orci ut iaculis. Maecenas euismod euismod ipsum, eget sagittis sapien lacinia nec. Vestibulum efficitur lacus et tincidunt elementum. Donec mattis sapien nec magna lobortis, ut pretium odio tempus. Suspendisse porttitor orci sit amet mauris ullamcorper molestie. Proin vitae dignissim purus, a pretium metus. Nam rutrum felis non justo faucibus, id tincidunt sem vestibulum. Fusce auctor, felis et efficitur volutpat, mi felis bibendum ante, eu tincidunt magna nibh non turpis.
-
-Sed accumsan felis diam, non malesuada sem molestie ut. In at feugiat urna, at volutpat justo. Nunc auctor vel diam sed varius. Mauris rutrum ullamcorper dignissim. Donec et fermentum enim. Morbi quis aliquet quam. Quisque semper justo a lectus lacinia condimentum. Pellentesque fringilla vulputate rutrum. Suspendisse potenti. Nullam id gravida tellus.
-
-Nam molestie sollicitudin rhoncus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Morbi posuere metus at metus auctor, ultricies fringilla purus tincidunt. Suspendisse volutpat at dui in aliquet. Etiam ullamcorper nibh enim, quis sodales nisl suscipit sed. Duis vel neque at libero mattis eleifend. Vivamus vehicula, purus eu sollicitudin rhoncus, velit justo commodo nisl, eu pharetra est lectus sit amet tellus. Nulla egestas imperdiet arcu, non iaculis dui pharetra eu. Sed pretium nulla diam, ut hendrerit orci consequat ut. Pellentesque vel fermentum nulla, non ornare nunc.
-
-Aliquam non quam aliquet, cursus eros eget, porta ipsum. Sed fringilla est ac eros pellentesque, a lobortis lacus tincidunt. Proin tincidunt sollicitudin sagittis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam at magna rhoncus, aliquet ex sit amet, tincidunt urna. Suspendisse efficitur dolor quis dui consectetur ultricies. Donec nibh nulla, eleifend id semper ut, varius in est. Nulla blandit et nibh ut imperdiet.
-
-Donec vitae consectetur odio. Vestibulum vestibulum mauris a pharetra laoreet. Integer pharetra congue porta. Suspendisse rutrum sagittis massa, feugiat bibendum turpis volutpat nec. In neque ipsum, vulputate nec leo non, blandit commodo lacus. Pellentesque pharetra ultrices placerat. Suspendisse a lacinia mi, id congue augue. In tristique felis et ipsum dapibus molestie. Phasellus et metus a velit gravida egestas. Nulla tristique consectetur ipsum at consectetur. Aenean tempus ipsum mauris, a lobortis magna congue sed. Integer risus eros, facilisis at ante non, scelerisque faucibus justo. In molestie est efficitur elementum tristique. In accumsan pretium mi, ac consectetur neque semper scelerisque. Aenean eleifend, urna nec aliquet tempor, quam est suscipit ante, vitae congue leo tellus at eros.
-
-Suspendisse et mauris at nibh suscipit condimentum. Duis eu turpis in erat fermentum ullamcorper vitae vel sapien. Nulla volutpat interdum posuere. Aenean eleifend nec odio quis hendrerit. In eu venenatis nisi, quis condimentum tortor. Vestibulum non est eu nisi efficitur consectetur ut et velit. Suspendisse posuere nunc in turpis cursus facilisis.
-
-Fusce ut placerat nulla. Aenean placerat vel lectus quis semper. Quisque eget semper mi. Vivamus iaculis, nisl sed hendrerit posuere, felis velit elementum nunc, in mollis erat erat eget sem. Donec ultrices, felis rutrum consectetur vehicula, diam lorem cursus metus, ac rhoncus felis ex a mauris. Nulla ullamcorper magna ac turpis maximus, quis blandit arcu dapibus. Sed aliquam, neque vel placerat congue, massa sem congue nisi, vitae pulvinar mi lacus in enim.
-
-In eget ex varius, ornare diam ac, tempus magna. Praesent tincidunt, leo vitae feugiat lobortis, nisl elit luctus leo, vitae sollicitudin nibh nisl ut lacus. Suspendisse nec risus eu nibh molestie efficitur. Duis at quam eros. Nullam ut ultrices nunc. Phasellus id orci purus. Sed tristique, urna sed pharetra pellentesque, lorem tortor luctus nisl, at vestibulum nisl massa dapibus sem. Fusce vel venenatis arcu. Praesent sit amet molestie ligula, eu fermentum leo. Integer libero nulla, posuere et sagittis ut, aliquam ac eros. Ut sodales velit vestibulum urna aliquet scelerisque a eu ipsum. Sed imperdiet lorem ac mollis lobortis. In non dui urna. Nulla sollicitudin, sapien vel consectetur venenatis, neque erat euismod nibh, vitae tempus justo nulla nec nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer sit amet nunc molestie ex fringilla tincidunt.
-
-Curabitur non felis nec urna tristique pretium at vel justo. Etiam tempor eros in leo luctus gravida. Cras at sagittis tellus. Aliquam molestie, nibh eget mollis varius, ligula metus varius nibh, eu gravida mauris lectus ac orci. Vivamus non eros nulla. Suspendisse aliquam nisi quis commodo rutrum. Sed posuere nulla lacus, a vehicula ante rutrum et. Fusce in consequat elit, et molestie felis. Maecenas lectus ipsum, pellentesque vitae augue a, consectetur aliquet dolor. Proin luctus vitae erat ac interdum. Quisque sed pharetra ex. In at vulputate urna, at rhoncus ligula. In hac habitasse platea dictumst. Maecenas elit orci, hendrerit eget quam sit amet, posuere lobortis lorem. Nullam lobortis sodales feugiat. In fringilla et neque vitae scelerisque.
-
-Phasellus quis posuere neque. Sed vehicula velit ac justo semper rutrum. Nulla vel dolor in quam viverra congue et vel ligula. Quisque et rutrum nulla. Maecenas id turpis a neque gravida lacinia. Curabitur condimentum, eros quis tincidunt porta, massa magna fringilla nunc, iaculis laoreet ipsum odio vitae nisl. Cras pharetra congue mollis.
-
-Phasellus id libero in lectus tristique iaculis nec in libero. Pellentesque nibh magna, rutrum et fermentum id, dignissim a tortor. Integer lacus lacus, volutpat non fermentum vitae, gravida in sapien. Sed porta quam in urna malesuada fermentum. In hac habitasse platea dictumst. Aliquam fermentum gravida leo at lobortis. Vestibulum quis nulla rutrum, consequat sem vitae, ornare enim. Nullam sed neque quis leo ullamcorper vulputate. Suspendisse non lacus velit. Ut eget orci nec urna pellentesque pretium vitae sed eros. Nulla nulla nisl, vulputate molestie nunc eu, tempor rhoncus nisi. Morbi ut mauris vitae odio maximus efficitur.
-
-Suspendisse potenti. In interdum metus sed dui faucibus, ullamcorper feugiat turpis laoreet. Etiam nulla leo, luctus vitae dui elementum, molestie feugiat est. Pellentesque volutpat metus vel enim euismod, quis tincidunt erat posuere. Nulla facilisi. Quisque sollicitudin turpis quis dui placerat mattis. Praesent porta rutrum dui ut sollicitudin. Integer et est pulvinar, faucibus lectus a, blandit ipsum. Suspendisse accumsan lobortis congue.
-
-Sed maximus arcu ut semper varius. Integer massa metus, pulvinar sed dui ut, consectetur congue lacus. Praesent a tortor id nulla blandit elementum ut eget justo. Vivamus fermentum et nisi eu rhoncus. Vestibulum malesuada justo purus, nec eleifend arcu consequat id. Sed sit amet venenatis lorem, sit amet porttitor eros. Proin ornare tincidunt nunc. Donec ac feugiat tortor. Aliquam a mi nec purus aliquet tempor. Nullam tristique tellus risus, ac rhoncus orci lacinia sit amet.
-
-Aenean eget consectetur lacus, non pellentesque leo. Suspendisse non ante semper, mattis felis et, ornare nibh. Morbi a dignissim est, at malesuada neque. Sed elementum purus in lectus malesuada blandit. Praesent sollicitudin augue leo, sed scelerisque turpis maximus at. Nam in efficitur metus. Pellentesque ultricies, erat sit amet mattis blandit, ipsum ligula lobortis purus, in varius magna felis at libero. Pellentesque aliquet viverra felis et tempor. Nam eget fermentum arcu. Vestibulum pharetra imperdiet justo et scelerisque. Praesent malesuada velit id est lobortis posuere. Vivamus eget justo id ipsum auctor molestie quis eget metus. Ut a viverra ipsum. Nulla consectetur venenatis augue, eu faucibus urna venenatis sit amet. Phasellus porttitor, elit sed dapibus euismod, justo enim vehicula felis, id hendrerit nunc felis id nibh.
-
-Donec feugiat libero vitae ipsum ultricies vestibulum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus fermentum nec nisl in vulputate. Donec posuere mauris quis lorem eleifend, a lacinia erat tincidunt. In hac habitasse platea dictumst. Aenean lacus dui, vulputate vitae interdum at, porta ut diam. Integer non massa sed leo imperdiet iaculis ut sed lectus. Cras ultricies feugiat consectetur. Nullam commodo magna rhoncus sollicitudin rutrum. Donec tristique nunc sed odio bibendum, eget luctus dolor gravida. Suspendisse potenti.
-
-Proin mattis nisl erat, id lobortis diam luctus nec. Nullam et elit ultrices, ultricies nunc cursus, mollis augue. Nam felis elit, maximus non tellus eget, volutpat molestie erat. Pellentesque luctus fermentum magna, id commodo turpis blandit sit amet. Nullam at nulla augue. Pellentesque non facilisis neque. Donec non purus diam. Pellentesque pretium sapien a nunc tincidunt, in volutpat eros facilisis. Proin porta mauris eget enim placerat, sed laoreet nisi volutpat. Fusce consequat tincidunt lectus, vel dictum ligula consectetur et. Mauris rutrum vulputate blandit. Ut sodales non quam ut semper. Quisque neque purus, sagittis ut fringilla id, ultricies at augue. Duis ullamcorper libero nec maximus ullamcorper. Mauris aliquam consectetur ante, vitae bibendum odio hendrerit non. Maecenas efficitur pellentesque lectus, nec tempus odio finibus eget.
-
-Ut diam neque, rhoncus at bibendum id, pulvinar non dui. Fusce at lectus non velit euismod posuere. Sed a efficitur arcu. Donec mi felis, sollicitudin in nisi eget, eleifend aliquet metus. Phasellus ut sem facilisis, posuere lacus a, facilisis velit. Nam vulputate nisi nec urna dignissim ultricies. Nullam pulvinar nunc sed porttitor maximus. Cras tincidunt auctor odio, a malesuada dui ultrices eu. Curabitur dapibus luctus malesuada.
-
-Aliquam non metus non sem fringilla tempor. Praesent at velit a mauris cursus tristique quis non erat. Cras id elit laoreet, faucibus orci eu, tempor dolor. Integer eget aliquam ligula. Vivamus commodo eleifend risus non varius. Aliquam sed ornare est, in feugiat turpis. Nam egestas ultrices facilisis. Curabitur feugiat risus nibh, eget efficitur libero aliquam eget. Aenean nisl ex, efficitur eget eros eu, tincidunt consectetur metus. Nulla facilisi. Praesent ac viverra nisi. Pellentesque lacinia nulla sed lacinia porta. Integer in ante ut orci aliquet dapibus.
-
-Ut ornare sed turpis eu pellentesque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus hendrerit tincidunt condimentum. Phasellus quis eros eget ex tristique pellentesque. In hac habitasse platea dictumst. Pellentesque non rhoncus metus, eget iaculis justo. Nunc congue fringilla mattis.
-
-Sed ultrices placerat ipsum vitae semper. Aenean elementum consequat nisl, in pharetra magna rutrum sed. Nunc eget urna semper, gravida erat et, rutrum nibh. Proin nisi augue, efficitur sit amet urna fringilla, faucibus placerat neque. Integer eu neque nec felis consectetur fringilla in non nunc. Quisque id erat vitae nunc egestas posuere in sed orci. Nullam tincidunt sollicitudin nibh a rhoncus.
-
-Vivamus tempus, neque at posuere consequat, lorem eros sollicitudin arcu, eget scelerisque ipsum nunc vel tortor. Donec ultricies purus eros, a aliquam neque hendrerit non. Praesent magna urna, posuere in iaculis sed, fermentum et ante. Maecenas nec pellentesque est. Aliquam vitae nisi a orci rhoncus tincidunt. Ut ac semper urna. Etiam vel dapibus diam. Pellentesque mi mi, aliquet non egestas ac, feugiat ac lectus. Suspendisse gravida molestie urna, at laoreet nunc efficitur et.
-
-In sit amet gravida risus, nec posuere mauris. Curabitur id tempor risus. Nunc enim purus, laoreet vel libero ac, imperdiet lobortis mauris. Sed eget hendrerit diam, imperdiet scelerisque urna. Donec eget nisl in erat rhoncus pulvinar. In eget eleifend eros. Nunc tristique tempus nulla, semper hendrerit ipsum congue eu.
-
-Duis in interdum nunc, eu venenatis lorem. Sed semper scelerisque dui, sed posuere lorem dignissim sit amet. Ut tempor a diam id scelerisque. Quisque suscipit posuere nunc vitae auctor. Morbi dapibus porttitor auctor. Vivamus bibendum pellentesque fermentum. Aliquam vel quam a risus congue consectetur. Donec sed erat metus. Cras interdum faucibus augue. Curabitur ullamcorper risus a velit elementum, blandit egestas nibh malesuada. Quisque erat urna, posuere quis mauris in, bibendum mollis ex. Mauris eget leo vel lorem viverra eleifend a et sapien. Etiam in efficitur purus.
-
-Integer sed nisl sit amet enim viverra pulvinar id vitae dolor. Nulla pharetra nibh est, at dapibus lectus ultricies nec. Ut lacinia gravida dolor nec ornare. Proin quis arcu eget ipsum lacinia scelerisque. Ut dapibus et lacus sit amet mollis. Integer fringilla dapibus tellus at ultricies. Morbi rhoncus nibh at augue vulputate pulvinar. In et elit mollis, varius nibh non, mollis lorem. Aliquam non lacus nulla. Praesent blandit mattis enim, in elementum tellus porta sit amet. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec volutpat malesuada elit. Nullam cursus ex ac tempus iaculis. Nam pretium, magna vel pretium sagittis, ex dolor tempor elit, sed elementum leo leo eu mauris.
-
-Vestibulum malesuada tincidunt leo vel lobortis. Maecenas sit amet felis ante. In aliquam orci sem. Phasellus laoreet metus non libero tempus sollicitudin. Mauris id dictum ipsum, nec aliquet erat. Aenean sodales ligula est, id dapibus tortor pharetra eu. Etiam arcu ipsum, dapibus ac arcu at, bibendum finibus libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed quis nisi congue, porttitor augue quis, volutpat ipsum. Aliquam tincidunt nibh quis ex cursus, ac dignissim nibh imperdiet. Mauris nec mi non leo mollis eleifend eu vel magna.
-
-In dignissim, augue eget interdum mattis, libero enim gravida ante, sit amet convallis risus nibh id mi. Integer tristique auctor nisl nec viverra. Integer bibendum ipsum a fermentum aliquet. Phasellus tincidunt elit metus, ut placerat sapien fermentum et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum at leo eget diam varius convallis. Donec tempor, elit vel malesuada euismod, libero massa feugiat sem, sit amet condimentum nisl libero et mauris. Integer vel mi felis. Fusce in tincidunt mi, non euismod ipsum. Mauris mollis ornare sem vel ultricies. Morbi id finibus metus, eu mattis neque. Pellentesque vel eros at mi dapibus mollis id eget nunc. Vivamus aliquet maximus nisl, id vestibulum arcu volutpat quis. Mauris gravida nisi at diam elementum egestas.
-
-Donec tincidunt justo sed mauris malesuada, scelerisque pellentesque nulla euismod. Aenean egestas diam sed maximus elementum. Nunc pharetra molestie velit, vitae cursus libero varius et. Fusce pellentesque vulputate ultrices. Integer volutpat mauris sed laoreet dignissim. Maecenas et augue tincidunt, vehicula eros at, tempus mi. Nam sagittis, tortor vel consectetur pharetra, velit eros auctor mauris, at pretium augue risus euismod neque. Etiam a facilisis ipsum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla pretium neque id massa ornare, ut condimentum sem lacinia.
-
-Etiam nec convallis metus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec ut mauris ac dolor auctor pharetra sit amet ac tellus. Pellentesque suscipit turpis et mi ornare ultrices. Etiam dignissim condimentum arcu, sed tristique lectus ultrices at. Vivamus quis sapien at lacus efficitur consectetur a vel nisi. Nam malesuada ipsum sed risus mattis ultrices. Praesent vitae mollis tellus, non fringilla justo. Maecenas egestas placerat odio, at vestibulum urna tincidunt at. Fusce blandit ac libero a scelerisque.
-
-Suspendisse aliquam consectetur sollicitudin. Proin porttitor dui neque, id ultricies lorem pellentesque eu. In a enim ac elit auctor scelerisque. Ut sit amet eros vulputate, consequat purus et, vehicula magna. Praesent dui odio, suscipit non sagittis at, tempor a ex. Ut pretium eleifend facilisis. Integer tristique libero velit, maximus ornare lacus pellentesque non. Vivamus suscipit vel leo eu tempus. In a auctor mauris.
-
-Cras bibendum, nunc congue porttitor luctus, quam dolor iaculis leo, at viverra arcu odio non diam. Fusce sodales porta neque. Nulla euismod lectus at lacus lacinia dignissim. Curabitur vitae augue vehicula, vulputate felis sit amet, ornare augue. Sed ac aliquet nulla, et molestie ante. Proin posuere non quam et finibus. Nam ipsum felis, sodales ac luctus vel, elementum non purus. Aliquam hendrerit diam quis tortor varius egestas. Etiam euismod vel leo vitae hendrerit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam sagittis dolor arcu, quis volutpat enim rhoncus id. Fusce interdum, justo sit amet dictum pretium, libero nibh cursus justo, in imperdiet sem mauris in orci. Morbi quis venenatis ligula, ullamcorper tristique odio. Morbi hendrerit id risus sed lacinia. Quisque et dictum mauris, nec euismod justo.
-
-Maecenas vel purus eu lectus vulputate gravida non non nulla. Proin tincidunt libero ac est elementum, id euismod ante dignissim. Aliquam ante ex, auctor sed turpis sit amet, aliquam vestibulum risus. Nulla placerat tempor nisi, sed aliquam purus efficitur eget. Sed elementum laoreet enim, a rutrum ante vulputate ac. Duis blandit ex a metus porttitor facilisis. Maecenas mattis, elit ac pulvinar placerat, sapien purus blandit lacus, nec accumsan ipsum diam in nunc. Suspendisse at enim vitae enim rutrum volutpat blandit non lorem. Praesent congue erat quis lacus auctor, non blandit lectus convallis. Maecenas sit amet orci eu elit porta tincidunt non sit amet massa. Mauris fermentum at turpis at lobortis. Nullam dapibus tempor aliquam. Vestibulum eget interdum nulla. Sed accumsan venenatis vestibulum. Aliquam est ligula, fermentum quis dui id, dignissim auctor elit.
-
-Vestibulum aliquam pharetra mauris vitae rutrum. Sed ut tempus lectus. Duis vestibulum erat orci, pellentesque lobortis diam iaculis eu. Vivamus aliquam porta ante ultricies suscipit. Integer in quam eu elit dignissim auctor sed vel ligula. Etiam rutrum, sem eget posuere feugiat, dui tortor fringilla eros, faucibus eleifend odio eros in nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam et quam sed elit viverra efficitur. Sed sit amet mauris est. Aliquam pulvinar lorem nunc, ut vehicula ligula mollis ut. Vestibulum imperdiet lacus mattis ex euismod, sit amet tincidunt quam rutrum.
-
-Cras aliquam augue sed nulla tincidunt laoreet. Integer ornare odio augue, nec scelerisque metus cursus at. Nullam non est vel erat tempus vehicula. Nulla a vulputate nibh. Mauris varius cursus sollicitudin. Donec consequat, magna eget accumsan fermentum, libero enim tincidunt felis, vel lacinia purus nunc semper ex. Maecenas laoreet finibus pulvinar. Ut pellentesque suscipit aliquam. Donec lacus nisl, molestie nec suscipit eu, imperdiet eget metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum vel enim libero. Vestibulum semper libero id imperdiet placerat. Suspendisse porttitor eget tellus ac laoreet.
-
-Mauris ornare metus quis elit consectetur vulputate. Etiam massa orci, interdum et justo ut, condimentum lobortis turpis. Aliquam vel congue libero. Fusce justo urna, congue non tortor a, molestie rhoncus mauris. Ut ut tellus nunc. Aliquam odio ex, lobortis ac blandit volutpat, blandit sed leo. Nunc tincidunt sodales diam sed egestas.
-
-Aliquam placerat nisl sed mauris aliquet ultricies. Nunc maximus diam et commodo scelerisque. Etiam ac felis vulputate, efficitur odio non, pharetra risus. Ut ut metus velit. Ut lacinia eget dolor nec sagittis. Nullam ut tempor metus. Duis non sapien eget quam pharetra ultricies. Etiam tortor nulla, consequat eu venenatis sit amet, euismod id leo. Donec bibendum dui lacus, quis condimentum lacus hendrerit eu. Quisque congue nunc in interdum vulputate. Fusce ac felis finibus, venenatis dui ut, aliquet orci. Nunc tempus aliquam molestie. Mauris nec lorem vel ipsum euismod lobortis. Fusce id dui interdum, finibus quam a, tincidunt nibh. Mauris sit amet risus nec tortor vulputate varius nec eget lacus. Nullam commodo ligula pellentesque tempus rhoncus.
-
-Fusce auctor hendrerit ligula, consequat vestibulum orci viverra eu. Praesent est mauris, mattis ac ligula eu, aliquam congue ipsum. Nullam rutrum ac diam vitae semper. In interdum molestie nisl in euismod. In scelerisque, lorem id aliquam rutrum, risus est consequat est, a finibus metus magna sed risus. Maecenas metus erat, porta convallis leo a, posuere consequat nunc. Donec rutrum lacus risus, nec porttitor dui pretium at. Fusce et sem dictum metus cursus aliquam nec eget enim. Cras congue mollis sapien ullamcorper molestie. Cras sit amet tincidunt odio. Nunc sagittis quis elit in elementum. Nullam vel purus enim. Sed sed malesuada felis, et molestie nibh. Sed rutrum sapien sem, ut convallis odio pulvinar ac. In a neque lectus. Sed iaculis vitae metus et malesuada.
-
-Pellentesque pharetra auctor neque, at convallis dui vulputate sed. Donec lacinia diam ut mauris aliquam blandit. Ut ultrices turpis ligula, eu aliquet massa imperdiet tincidunt. Pellentesque et rutrum ante. Nulla augue nibh, molestie vitae libero ac, mattis rhoncus lectus. Quisque ante ante, vehicula non lacinia a, sollicitudin sed odio. Nulla porta, nibh quis sollicitudin tempor, nisl ex ultrices arcu, at vestibulum massa lectus at orci. Donec ornare augue ac tellus suscipit, vitae congue nunc tempor.
-
-Nullam malesuada sagittis enim eget facilisis. Nam in fermentum arcu. Vestibulum rhoncus dolor id lorem ornare iaculis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris sed magna sollicitudin, ullamcorper dui sit amet, gravida sem. Maecenas non volutpat est, sit amet mattis libero. Morbi ut libero eget massa congue fermentum vel non purus. Mauris mi felis, rutrum sit amet lorem a, pretium sagittis metus. In et tortor at diam pharetra viverra. Curabitur hendrerit condimentum lorem quis sodales.
-
-Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas facilisis aliquet velit, quis luctus odio vehicula eu. Maecenas pharetra, erat ut ultricies eleifend, urna lacus vulputate enim, at scelerisque leo augue eu arcu. Aenean aliquet metus eu metus tempor accumsan. Vivamus et maximus risus. Morbi euismod massa blandit laoreet commodo. Sed velit turpis, condimentum id auctor in, hendrerit sit amet dolor. Donec bibendum et urna eu ultrices.
-
-Praesent metus metus, bibendum quis egestas vitae, mattis hendrerit velit. Donec mollis mauris dui, vitae porttitor ipsum ultrices a. Nullam accumsan nulla sem. Maecenas diam orci, eleifend a euismod vitae, accumsan ut arcu. Vivamus massa ipsum, ornare non urna id, luctus mollis orci. Etiam id dolor odio. Nulla ornare pharetra purus ac pharetra. Ut blandit tortor et turpis vehicula, nec porttitor odio mattis. Nam feugiat venenatis lectus, a dignissim ex mattis porttitor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas lorem nisi, imperdiet vel vestibulum eget, consectetur ac est.
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas quis nunc finibus, pellentesque diam non, tempor quam. Nullam semper tempor aliquet. In accumsan ornare metus sed volutpat. Integer eget sagittis lectus, sed feugiat tortor. Suspendisse eleifend metus sit amet nunc fermentum molestie. Fusce varius ante a augue interdum dictum. Etiam mattis dictum finibus. Donec sed vulputate est, in posuere neque. Sed at ex vitae metus hendrerit efficitur vitae ut diam.
-
-In placerat augue elit, gravida luctus odio luctus vel. Duis eget aliquet tellus. Vestibulum vehicula molestie leo, vel semper felis scelerisque vel. Integer vestibulum congue dapibus. Maecenas gravida diam mauris, nec blandit lorem tempor nec. Praesent nec blandit mi. Morbi fermentum egestas mi in pellentesque. Fusce in efficitur tortor, vitae tristique urna. Phasellus mollis augue sed lacus euismod luctus. Mauris interdum at sem eget aliquam. Praesent luctus massa pharetra, venenatis lectus feugiat, viverra odio. Integer egestas mi at nulla viverra tincidunt. Nulla facilisi. `,
-	}
-
-	for i := range expected {
-		//create partitions
-		partitions, err := Partition([]byte(expected[i]), []byte{0x05})
-		if err != nil {
-			t.Error(err)
-		}
-		t.Logf("Number of partitions for index %v: %v", i, len(partitions))
-		t.Logf("First partition: %q, %v", partitions[0], len(partitions[0]))
-		//strip front matter from partitions
-		for j := range partitions {
-			strippedPartition, err := ValidatePartition(partitions[j])
-			if err != nil {
-				t.Fatalf("Didn't validate a valid partition: %v, %v, %v,"+
-					" %v", j,
-					err.Error(), partitions[j], len(partitions[j]))
-			}
-			partitions[j] = strippedPartition.Body
-		}
-		// assemble stripped partitions
-		actual, err := Assemble(partitions)
-		if err != nil {
-			t.Error(err)
-		}
-		t.Log(string(actual))
-
-		if string(actual) != expected[i] {
-			t.Errorf("Actual (length %v): %v", len(string(actual)), string(actual))
-			t.Errorf("Expected (length %v): %v", len(expected[i]), expected[i])
-		}
-	}
-}
-
-// We need to be sure that these invalid payloads get rejected for collation
-// without crashing the client with an out of bounds array access.
-func TestValidatePartition(t *testing.T) {
-	invalidPayloads := [][]byte{
-		// empty
-		{},
-		// ID only
-		{0x05},
-		// ID only, and is too long to have been generated according to our
-		// expectations
-		{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f},
-		// no ID, only index and max index
-		{0x3f, 0xff},
-		// ID without an ending byte
-		{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-		// ID and index info without a payload
-		{0x00, 0x00, 0x00},
-	}
-
-	// these contain a variable-length ID,
-	// an index that's less than or equal to the max index,
-	// a max index that's greater than or equal to the index,
-	// and a message after the front matter
-	validPayloads := [][]byte{
-		// Message 2 of 2 with id 0. Note that we're appending a payload to this
-		{0x00, 0x01, 0x01},
-		// Message 1 of 1 with id 0. Note that we're appending a payload to this
-		{0x00, 0x00, 0x00},
-		// Note that in some cases, the system validates something that's
-		// readable. In this case, the first three letters will be consumed.
-		[]byte("telecommunication is neat"),
-		// This test case is one that should be valid but failed to validate
-		// in the integration test during development after passing through the
-		// whole system.
-		// Putting it here for posterity.
-		{0, 0, 0, 1, 10, 8, 8, 216, 153, 249, 217, 5, 24, 1, 18, 0, 26, 8,
-			72, 101, 108, 108, 111, 44, 32, 50},
-	}
-
-	expectedIDs := [][]byte{{0x00}, {0x00}, {'t'}, {0}}
-	expectedIndexes := []byte{0x01, 0x00, 'e', 0}
-	expectedMaxIndexes := []byte{0x01, 0x00, 'l', 0}
-	expectedBodies := [][]byte{
-		[]byte("apples and grapes"),
-		[]byte("apples and grapes"),
-		[]byte("ecommunication is neat"),
-		{1, 10, 8, 8, 216, 153, 249, 217, 5, 24, 1, 18, 0, 26, 8,
-			72, 101, 108, 108, 111, 44, 32, 50},
-	}
-
-	// make first two payloads valid by adding a payload to them
-	for i := 0; i < 2; i++ {
-		validPayloads[i] = append(validPayloads[i], []byte("apples and grapes")...)
-	}
-
-	for i := range invalidPayloads {
-		_, err := ValidatePartition(invalidPayloads[i])
-		if err == nil {
-			t.Errorf("Payload %v was incorrectly validated.", i)
-		}
-	}
-
-	for i := range validPayloads {
-		result, err := ValidatePartition(validPayloads[i])
-		if err != nil {
-			t.Fatalf("Payload %v was incorrectly invalidated: %v", i,
-				err.Error())
-		}
-		if !bytes.Equal(result.ID, expectedIDs[i]) {
-			t.Errorf("Payload %v's ID was parsed incorrectly. Got %v, "+
-				"expected %v", i, result.ID, expectedIDs[i])
-		}
-		if result.Index != expectedIndexes[i] {
-			t.Errorf("Payload %v's index was parsed incorrectly. Got %v, "+
-				"expected %v", i, result.Index, expectedIndexes[i])
-		}
-		if result.MaxIndex != expectedMaxIndexes[i] {
-			t.Errorf("Payload %v's max index was parsed incorrectly. Got %v, "+
-				"expected %v", i, result.MaxIndex, expectedMaxIndexes[i])
-		}
-		if !bytes.Equal(result.Body, expectedBodies[i]) {
-			t.Errorf("Payload %v's body was parsed incorrectly. Got %v, "+
-				"expected %v", i, result.Body, expectedBodies[i])
-		}
-	}
-}
diff --git a/permissioning/permissioning.go b/permissioning/permissioning.go
new file mode 100644
index 0000000000000000000000000000000000000000..ebe00b3c3d41c3c9fcaf844e65d87fc6033dd829
--- /dev/null
+++ b/permissioning/permissioning.go
@@ -0,0 +1,43 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package permissioning
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+)
+
+type Permissioning struct {
+	host  *connect.Host
+	comms *client.Comms
+}
+
+func Init(comms *client.Comms, def *ndf.NetworkDefinition) (*Permissioning, error) {
+
+	perm := Permissioning{
+		host:  nil,
+		comms: comms,
+	}
+
+	var err error
+	//add the permissioning host to comms
+	hParam := connect.GetDefaultHostParams()
+	hParam.AuthEnabled = false
+
+	perm.host, err = comms.AddHost(&id.Permissioning, def.Registration.Address,
+		[]byte(def.Registration.TlsCertificate), hParam)
+
+	if err != nil {
+		return nil, errors.WithMessage(err, "failed to create permissioning")
+	}
+
+	return &perm, nil
+}
diff --git a/permissioning/permissioning_test.go b/permissioning/permissioning_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f691f3a419f5a43109206da5a802b3bb2029be3
--- /dev/null
+++ b/permissioning/permissioning_test.go
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package permissioning
+
+import (
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"testing"
+)
+
+// Init should create a valid Permissioning communications struct
+func TestInit(t *testing.T) {
+	// Create dummy comms and ndf
+	comms, err := client.NewClientComms(id.NewIdFromUInt(100, id.User, t), nil, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	def := &ndf.NetworkDefinition{
+		Registration: ndf.Registration{},
+	}
+	reg, err := Init(comms, def)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if reg.comms == nil {
+		t.Error("reg comms returned should not be nil")
+	}
+	if reg.host == nil {
+		t.Error("reg host returned should not be nil")
+	}
+}
diff --git a/permissioning/register.go b/permissioning/register.go
new file mode 100644
index 0000000000000000000000000000000000000000..1a64e54798560b270ab0d62267f7b2704a2c8ae1
--- /dev/null
+++ b/permissioning/register.go
@@ -0,0 +1,46 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package permissioning
+
+import (
+	"github.com/pkg/errors"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+)
+
+func (perm *Permissioning) Register(transmissionPublicKey, receptionPublicKey *rsa.PublicKey, registrationCode string) ([]byte, []byte, 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)
+}
+
+//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, error) {
+
+	response, err := comms.
+		SendRegistrationMessage(host,
+			&pb.UserRegistration{
+				RegistrationCode:         registrationCode,
+				ClientRSAPubKey:          string(rsa.CreatePublicKeyPem(transmissionPublicKey)),
+				ClientReceptionRSAPubKey: string(rsa.CreatePublicKeyPem(receptionPublicKey)),
+			})
+	if err != nil {
+		err = errors.Wrap(err, "sendRegistrationMessage: Unable to contact Identity Server!")
+		return nil, nil, err
+	}
+	if response.Error != "" {
+		return nil, nil, errors.Errorf("sendRegistrationMessage: error handling message: %s", response.Error)
+	}
+	return response.ClientSignedByServer.Signature, response.ClientReceptionSignedByServer.Signature, nil
+}
diff --git a/permissioning/register_test.go b/permissioning/register_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..52dcb46e948b42fdd54a8f45ca6e2f751bf8a85e
--- /dev/null
+++ b/permissioning/register_test.go
@@ -0,0 +1,129 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package permissioning
+
+import (
+	"github.com/pkg/errors"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"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"
+	"testing"
+)
+
+type MockRegistrationSender struct {
+	reg *pb.UserRegistration
+	// param passed to SendRegistrationMessage
+	host *connect.Host
+	// 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"),
+		},
+		ClientReceptionSignedByServer: &messages.RSASignature{
+			Nonce:     []byte("receptionnonce"),
+			Signature: []byte("receptionsig"),
+		},
+		Error: s.errInReply,
+	}, s.errSendRegistration
+}
+
+func (s *MockRegistrationSender) GetHost(*id.ID) (*connect.Host, bool) {
+	return s.getHost, s.succeedGetHost
+}
+
+// Shows that we get expected result from happy path
+// Shows that permissioning gets RPCs with the correct parameters
+func TestRegisterWithPermissioning(t *testing.T) {
+	rng := csprng.NewSystemRNG()
+	key, err := rsa.GenerateKey(rng, 256)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var sender MockRegistrationSender
+	sender.succeedGetHost = true
+	sender.getHost, err = connect.NewHost(&id.Permissioning, "address", nil,
+		connect.GetDefaultHostParams())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	regCode := "flooble doodle"
+	sig1, sig2, err := register(&sender, sender.getHost, key.GetPublic(), key.GetPublic(), regCode)
+	if err != nil {
+		t.Error(err)
+	}
+	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)
+	}
+	passedPub, err := rsa.LoadPublicKeyFromPem([]byte(sender.reg.ClientRSAPubKey))
+	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 sender.reg.RegistrationCode != regCode {
+		t.Error("passed regcode different from expected")
+	}
+}
+
+// Shows that returning an error from the permissioning server results in an
+// error from register
+func TestRegisterWithPermissioning_ResponseErr(t *testing.T) {
+	rng := csprng.NewSystemRNG()
+	key, err := rsa.GenerateKey(rng, 256)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var sender MockRegistrationSender
+	sender.succeedGetHost = true
+	sender.errInReply = "failure occurred on permissioning"
+	_, _, err = register(&sender, nil, key.GetPublic(), key.GetPublic(), "")
+	if err == nil {
+		t.Error("no error if registration fails on permissioning")
+	}
+}
+
+// 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)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var sender MockRegistrationSender
+	sender.succeedGetHost = true
+	sender.errSendRegistration = errors.New("connection problem")
+	_, _, err = register(&sender, nil, key.GetPublic(), key.GetPublic(), "")
+	if err == nil {
+		t.Error("no error if e.g. context deadline exceeded")
+	}
+}
diff --git a/rekey/rekey.go b/rekey/rekey.go
deleted file mode 100644
index b013ad245f9b5d53994e4466756d81eb525ef831..0000000000000000000000000000000000000000
--- a/rekey/rekey.go
+++ /dev/null
@@ -1,275 +0,0 @@
-package rekey
-
-import (
-	"bytes"
-	"fmt"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/io"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"gitlab.com/xx_network/comms/connect"
-)
-
-var session user.Session
-var topology *connect.Circuit
-var comms io.Communications
-var transmissionHost *connect.Host
-
-var rekeyTriggerList rekeyTriggerListener
-var rekeyList rekeyListener
-var rekeyConfirmList rekeyConfirmListener
-
-var rekeyChan chan struct{}
-
-type rekeyTriggerListener struct {
-	err error
-}
-
-func (l *rekeyTriggerListener) Hear(msg switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	m := msg.(*parse.Message)
-	partner := m.GetRecipient()
-	globals.Log.DEBUG.Printf("Received RekeyTrigger message for user %v", *partner)
-	err := rekeyProcess(RekeyTrigger, partner, nil)
-	if err != nil {
-		globals.Log.WARN.Printf("Error on rekeyProcess: %s", err.Error())
-		l.err = err
-	} else {
-		l.err = nil
-	}
-}
-
-type rekeyListener struct {
-	err error
-}
-
-func (l *rekeyListener) Hear(msg switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	m := msg.(*parse.Message)
-	partner := m.GetSender()
-	partnerPubKey := m.GetPayload()
-	if m.GetCryptoType() != parse.Rekey {
-		globals.Log.WARN.Printf("Received message with NO_TYPE but not Rekey CryptoType, needs to be fixed!")
-		return
-	}
-	globals.Log.DEBUG.Printf("Received Rekey message from user %v", *partner)
-	err := rekeyProcess(Rekey, partner, partnerPubKey)
-	if err != nil {
-		globals.Log.WARN.Printf("Error on rekeyProcess: %s", err.Error())
-		l.err = err
-	} else {
-		l.err = nil
-	}
-}
-
-type rekeyConfirmListener struct {
-	err error
-}
-
-func (l *rekeyConfirmListener) Hear(msg switchboard.Item, isHeardElsewhere bool, i ...interface{}) {
-	m := msg.(*parse.Message)
-	partner := m.GetSender()
-	baseKeyHash := m.GetPayload()
-	globals.Log.DEBUG.Printf("Received RekeyConfirm message from user %v", *partner)
-	err := rekeyProcess(RekeyConfirm, partner, baseKeyHash)
-	if err != nil {
-		globals.Log.WARN.Printf("Error on rekeyProcess: %s", err.Error())
-		l.err = err
-	} else {
-		l.err = nil
-	}
-}
-
-// InitRekey is called internally by the Login API
-func InitRekey(s user.Session, m io.Communications, t *connect.Circuit, host *connect.Host, rekeyChan2 chan struct{}) {
-
-	rekeyTriggerList = rekeyTriggerListener{}
-	rekeyList = rekeyListener{}
-	rekeyConfirmList = rekeyConfirmListener{}
-
-	session = s
-	topology = t
-	comms = m
-	transmissionHost = host
-
-	rekeyChan = rekeyChan2
-	l := session.GetSwitchboard()
-
-	l.Register(s.GetCurrentUser().User,
-		int32(cmixproto.Type_REKEY_TRIGGER),
-		&rekeyTriggerList)
-	// TODO(nen) Wouldn't it be possible to register these listeners based
-	//  solely on the inner type? maybe the switchboard can rebroadcast
-	//  messages that have a type that includes the outer type if that's not
-	//  possible
-	// in short, switchboard should be the package that includes outer
-	l.Register(&id.ZeroUser,
-		int32(cmixproto.Type_NO_TYPE),
-		&rekeyList)
-	l.Register(&id.ZeroUser,
-		int32(cmixproto.Type_REKEY_CONFIRM),
-		&rekeyConfirmList)
-}
-
-type rekeyType uint8
-
-const (
-	None rekeyType = iota
-	RekeyTrigger
-	Rekey
-	RekeyConfirm
-)
-
-func rekeyProcess(rt rekeyType, partner *id.ID, data []byte) error {
-	rkm := session.GetRekeyManager()
-	e2egrp := session.GetE2EGroup()
-
-	// Error handling according to Rekey Message Type
-	var ctx *keyStore.RekeyContext
-	var keys *keyStore.RekeyKeys
-	switch rt {
-	case RekeyTrigger:
-		ctx = rkm.GetCtx(partner)
-		if ctx != nil {
-			return fmt.Errorf("rekey already in progress with user %v,"+
-				" ignoring repetition", *partner)
-		}
-		keys = rkm.GetKeys(partner)
-		if keys == nil {
-			return fmt.Errorf("couldn't get RekeyKeys object for user: %v", *partner)
-		}
-	case Rekey:
-		keys = rkm.GetKeys(partner)
-		if keys == nil {
-			return fmt.Errorf("couldn't get RekeyKeys object for user: %v", *partner)
-		}
-	case RekeyConfirm:
-		ctx = rkm.GetCtx(partner)
-		if ctx == nil {
-			return fmt.Errorf("rekey not in progress with user %v,"+
-				" ignoring confirmation", *partner)
-		}
-	}
-
-	// Create Rekey Context if not existing
-	// Use set privKey and partner pubKey for Rekey
-	// For RekeyTrigger, generate new privKey / pubKey pair
-	// Add context to RekeyManager in case of RekeyTrigger
-	var privKeyCyclic *cyclic.Int
-	var pubKeyCyclic *cyclic.Int
-	var partnerPubKeyCyclic *cyclic.Int
-	var baseKey *cyclic.Int
-	if ctx == nil {
-		if rt == RekeyTrigger {
-			privKeyCyclic = e2egrp.RandomCoprime(e2egrp.NewInt(1))
-			globals.Log.DEBUG.Println("Private key actual: ", privKeyCyclic.Text(16))
-			pubKeyCyclic = e2egrp.ExpG(privKeyCyclic, e2egrp.NewInt(1))
-			// Get Current Partner Public Key from RekeyKeys
-			partnerPubKeyCyclic = keys.CurrPubKey
-			// Set new Own Private Key
-			keys.NewPrivKey = privKeyCyclic
-		} else {
-			// Get Current Own Private Key from RekeyKeys
-			privKeyCyclic = keys.CurrPrivKey
-			// Get Partner New Public Key from data
-			partnerPubKeyCyclic = e2egrp.NewIntFromBytes(data)
-			// Set new Partner Public Key
-			keys.NewPubKey = partnerPubKeyCyclic
-		}
-
-		// Generate baseKey
-		baseKey, _ = diffieHellman.CreateDHSessionKey(
-			partnerPubKeyCyclic,
-			privKeyCyclic,
-			e2egrp)
-
-		ctx = &keyStore.RekeyContext{
-			BaseKey: baseKey,
-			PrivKey: privKeyCyclic,
-			PubKey:  partnerPubKeyCyclic,
-		}
-
-		if rt == RekeyTrigger {
-			rkm.AddCtx(partner, ctx)
-		}
-		// Rotate Keys if ready
-		keys.RotateKeysIfReady()
-	}
-
-	// Generate key TTL and number of keys
-	params := session.GetKeyStore().GetKeyParams()
-	keysTTL, numKeys := e2e.GenerateKeyTTL(ctx.BaseKey.GetLargeInt(),
-		params.MinKeys, params.MaxKeys, params.TTLParams)
-	// Create Key Manager if needed
-	switch rt {
-	case Rekey:
-		// Create Receive KeyManager
-		km := keyStore.NewManager(ctx.BaseKey, ctx.PrivKey, ctx.PubKey,
-			partner, false,
-			numKeys, keysTTL, params.NumRekeys)
-		// Generate Receive Keys
-		e2ekeys := km.GenerateKeys(e2egrp, session.GetCurrentUser().User)
-		session.GetKeyStore().AddRecvManager(km)
-		session.GetKeyStore().AddReceiveKeysByFingerprint(e2ekeys)
-
-		globals.Log.DEBUG.Printf("Generated new receiving keys for E2E"+
-			" relationship with user %v", *partner)
-	case RekeyConfirm:
-		// Check baseKey Hash matches expected
-		h, _ := hash.NewCMixHash()
-		h.Write(ctx.BaseKey.Bytes())
-		expected := h.Sum(nil)
-		if bytes.Equal(expected, data) {
-			// Delete current send KeyManager
-			oldKm := session.GetKeyStore().GetSendManager(partner)
-			oldKm.Destroy(session.GetKeyStore())
-			// Create Send KeyManager
-			km := keyStore.NewManager(ctx.BaseKey, ctx.PrivKey, ctx.PubKey,
-				partner, true,
-				numKeys, keysTTL, params.NumRekeys)
-			// Generate Send Keys
-			km.GenerateKeys(e2egrp, session.GetCurrentUser().User)
-			session.GetKeyStore().AddSendManager(km)
-			// Remove RekeyContext
-			rkm.DeleteCtx(partner)
-			globals.Log.DEBUG.Printf("Generated new send keys for E2E"+
-				" relationship with user %v", *partner)
-		} else {
-			return fmt.Errorf("rekey-confirm from user %v failed,"+
-				" baseKey hash doesn't match expected", *partner)
-		}
-	}
-
-	// Send message if needed
-	switch rt {
-	case RekeyTrigger:
-		// Directly send raw publicKey bytes, without any message type
-		// This ensures that the publicKey fits in a single message, which
-		// is sent with E2E encryption using a send Rekey, and without padding
-		return comms.SendMessageNoPartition(session, topology, partner, parse.E2E,
-			pubKeyCyclic.LeftpadBytes(uint64(format.ContentsLen)), transmissionHost)
-	case Rekey:
-		// Trigger the rekey channel
-		select {
-		case rekeyChan <- struct{}{}:
-		}
-
-		// Send rekey confirm message with hash of the baseKey
-		h, _ := hash.NewCMixHash()
-		h.Write(ctx.BaseKey.Bytes())
-		baseKeyHash := h.Sum(nil)
-		msg := parse.Pack(&parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_CONFIRM),
-			Body:        baseKeyHash,
-		})
-		return comms.SendMessage(session, topology, partner, parse.None, msg, transmissionHost)
-	}
-	return nil
-}
diff --git a/rekey/rekey_test.go b/rekey/rekey_test.go
deleted file mode 100644
index cd9019f5f82024a89e7fb1da845745effdbfd892..0000000000000000000000000000000000000000
--- a/rekey/rekey_test.go
+++ /dev/null
@@ -1,394 +0,0 @@
-package rekey
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"gitlab.com/elixxir/client/cmixproto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/client/parse"
-	"gitlab.com/elixxir/client/user"
-	"gitlab.com/elixxir/crypto/csprng"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/elixxir/crypto/hash"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"os"
-	"testing"
-	"time"
-)
-
-var ListenCh chan []byte
-
-type dummyMessaging struct {
-	listener chan []byte
-}
-
-// SendMessage to the server
-func (d *dummyMessaging) SendMessage(sess user.Session,
-	topology *connect.Circuit,
-	recipientID *id.ID,
-	cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	d.listener <- message
-	return nil
-}
-
-// SendMessage without partitions to the server
-func (d *dummyMessaging) SendMessageNoPartition(sess user.Session,
-	topology *connect.Circuit,
-	recipientID *id.ID,
-	cryptoType parse.CryptoType,
-	message []byte, transmissionHost *connect.Host) error {
-	d.listener <- message
-	return nil
-}
-
-// MessageReceiver thread to get new messages
-func (d *dummyMessaging) MessageReceiver(session user.Session,
-	delay time.Duration, transmissionHost *connect.Host, callback func(error)) {
-}
-
-func TestMain(m *testing.M) {
-
-	grp, e2eGrp := getGroups()
-	user.InitUserRegistry(grp)
-	rng := csprng.NewSystemRNG()
-	u := &user.User{
-		User:     new(id.ID),
-		Username: "Bernie",
-	}
-	binary.BigEndian.PutUint64(u.User[:], 18)
-	u.User.SetType(id.User)
-	myPrivKeyCyclicCMIX := grp.RandomCoprime(grp.NewMaxInt())
-	myPubKeyCyclicCMIX := grp.ExpG(myPrivKeyCyclicCMIX, grp.NewInt(1))
-	myPrivKeyCyclicE2E := e2eGrp.RandomCoprime(e2eGrp.NewMaxInt())
-	myPubKeyCyclicE2E := e2eGrp.ExpG(myPrivKeyCyclicE2E, e2eGrp.NewInt(1))
-	partnerID := new(id.ID)
-	binary.BigEndian.PutUint64(partnerID[:], 12)
-	partnerID.SetType(id.User)
-
-	partnerPubKeyCyclic := e2eGrp.RandomCoprime(e2eGrp.NewMaxInt())
-
-	privateKeyRSA, _ := rsa.GenerateKey(rng, 768)
-	publicKeyRSA := rsa.PublicKey{PublicKey: privateKeyRSA.PublicKey}
-
-	session := user.NewSession(&globals.RamStorage{},
-		u, &publicKeyRSA, privateKeyRSA, myPubKeyCyclicCMIX,
-		myPrivKeyCyclicCMIX, myPubKeyCyclicE2E, myPrivKeyCyclicE2E, make([]byte, 1),
-		grp, e2eGrp, "password")
-	ListenCh = make(chan []byte, 100)
-	fakeComm := &dummyMessaging{
-		listener: ListenCh,
-	}
-
-	rekeyChan2 := make(chan struct{}, 50)
-	nodeID := new(id.ID)
-	nodeID.SetType(id.Node)
-	InitRekey(session, fakeComm, connect.NewCircuit([]*id.ID{nodeID}), nil, rekeyChan2)
-
-	// Create E2E relationship with partner
-	// Generate baseKey
-	baseKey, _ := diffieHellman.CreateDHSessionKey(
-		partnerPubKeyCyclic,
-		myPrivKeyCyclicE2E,
-		e2eGrp)
-
-	// Generate key TTL and number of keys
-	keyParams := session.GetKeyStore().GetKeyParams()
-	keysTTL, numKeys := e2e.GenerateKeyTTL(baseKey.GetLargeInt(),
-		keyParams.MinKeys, keyParams.MaxKeys, keyParams.TTLParams)
-
-	// Create Send KeyManager
-	km := keyStore.NewManager(baseKey, myPrivKeyCyclicE2E,
-		partnerPubKeyCyclic, partnerID, true,
-		numKeys, keysTTL, keyParams.NumRekeys)
-
-	// Generate Send Keys
-	km.GenerateKeys(grp, u.User)
-	session.GetKeyStore().AddSendManager(km)
-
-	// Create Receive KeyManager
-	km = keyStore.NewManager(baseKey, myPrivKeyCyclicE2E,
-		partnerPubKeyCyclic, partnerID, false,
-		numKeys, keysTTL, keyParams.NumRekeys)
-
-	// Generate Receive Keys
-	e2ekeys := km.GenerateKeys(grp, u.User)
-	session.GetKeyStore().AddReceiveKeysByFingerprint(e2ekeys)
-	session.GetKeyStore().AddRecvManager(km)
-	session.GetKeyStore().AddReceiveKeysByFingerprint(e2ekeys)
-
-	keys := &keyStore.RekeyKeys{
-		CurrPrivKey: myPrivKeyCyclicE2E,
-		CurrPubKey:  partnerPubKeyCyclic,
-	}
-
-	session.GetRekeyManager().AddKeys(partnerID, keys)
-
-	os.Exit(m.Run())
-}
-
-// Test RekeyTrigger
-func TestRekeyTrigger(t *testing.T) {
-	partnerID := new(id.ID)
-	binary.BigEndian.PutUint64(partnerID[:], 12)
-	partnerID.SetType(id.User)
-	km := session.GetKeyStore().GetRecvManager(partnerID)
-	partnerPubKey := km.GetPubKey()
-	// Test receiving a RekeyTrigger message
-	msg := &parse.Message{
-		Sender: session.GetCurrentUser().User,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_TRIGGER),
-			Body:        partnerPubKey.Bytes(),
-		},
-		InferredType: parse.None,
-		Receiver:     partnerID,
-	}
-	session.GetSwitchboard().Speak(msg)
-
-	// Check no error occurred in rekeytrigger processing
-	if rekeyTriggerList.err != nil {
-		t.Errorf("RekeyTrigger returned error: %v", rekeyTriggerList.err.Error())
-	}
-	// Get new PubKey from Rekey message and confirm value matches
-	// with PubKey created from privKey in Rekey Context
-	value := <-ListenCh
-	grpE2E := session.GetE2EGroup()
-	actualPubKey := grpE2E.NewIntFromBytes(value)
-	privKey := session.GetRekeyManager().GetCtx(partnerID).PrivKey
-	fmt.Println("privKey: ", privKey.Text(16))
-	expectedPubKey := grpE2E.NewInt(1)
-	grpE2E.ExpG(privKey, expectedPubKey)
-	fmt.Println("new pub key: ", value)
-
-	if expectedPubKey.Cmp(actualPubKey) != 0 {
-		t.Errorf("RekeyTrigger publicKey mismatch, expected %s,"+
-			" got %s", expectedPubKey.Text(16),
-			actualPubKey.Text(16))
-	}
-
-	// Check that trying to send another rekeyTrigger message returns an error
-	msg = &parse.Message{
-		Sender: session.GetCurrentUser().User,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_TRIGGER),
-			Body:        partnerPubKey.Bytes(),
-		},
-		InferredType: parse.None,
-		Receiver:     partnerID,
-	}
-	session.GetSwitchboard().Speak(msg)
-	time.Sleep(time.Second)
-	// Check that error occurred in rekeytrigger for repeated message
-	if rekeyTriggerList.err == nil {
-		t.Errorf("RekeyTrigger should have returned error")
-	}
-}
-
-// Test RekeyConfirm
-func TestRekeyConfirm(t *testing.T) {
-	partnerID := new(id.ID)
-	binary.BigEndian.PutUint64(partnerID[:], 12)
-	partnerID.SetType(id.User)
-	rekeyCtx := session.GetRekeyManager().GetCtx(partnerID)
-	baseKey := rekeyCtx.BaseKey
-	// Test receiving a RekeyConfirm message with wrong H(baseKey)
-	msg := &parse.Message{
-		Sender: partnerID,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_CONFIRM),
-			Body:        baseKey.Bytes(),
-		},
-		InferredType: parse.None,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-	time.Sleep(time.Second)
-	// Check that error occurred in RekeyConfirm when hash is wrong
-	if rekeyConfirmList.err == nil {
-		t.Errorf("RekeyConfirm should have returned error")
-	}
-
-	// Test with correct hash
-	h, _ := hash.NewCMixHash()
-	h.Write(baseKey.Bytes())
-	msg = &parse.Message{
-		Sender: partnerID,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_CONFIRM),
-			Body:        h.Sum(nil),
-		},
-		InferredType: parse.None,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-	time.Sleep(time.Second)
-	// Check no error occurred in rekeyConfirm processing
-	if rekeyConfirmList.err != nil {
-		t.Errorf("RekeyConfirm returned error: %v", rekeyConfirmList.err.Error())
-	}
-
-	// Confirm that user Private key in Send Key Manager
-	// differs from the one stored in session
-	if session.GetE2EDHPrivateKey().GetLargeInt().Cmp(
-		session.GetKeyStore().GetSendManager(partnerID).
-			GetPrivKey().GetLargeInt()) == 0 {
-		t.Errorf("PrivateKey remained unchanged after Outgoing Rekey!")
-	}
-
-	// Check that trying to send another rekeyConfirm message causes an error
-	// since no Rekey is in progress anymore
-	msg = &parse.Message{
-		Sender: partnerID,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_CONFIRM),
-			Body:        h.Sum(nil),
-		},
-		InferredType: parse.None,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-	time.Sleep(time.Second)
-	// Check that error occurred in RekeyConfirm for repeated message
-	if rekeyConfirmList.err == nil {
-		t.Errorf("RekeyConfirm should have returned error")
-	}
-}
-
-// Test Rekey
-func TestRekey(t *testing.T) {
-	partnerID := new(id.ID)
-	binary.BigEndian.PutUint64(partnerID[:], 12)
-	partnerID.SetType(id.User)
-	km := session.GetKeyStore().GetSendManager(partnerID)
-	// Generate new partner public key
-	_, grp := getGroups()
-	privKey := grp.RandomCoprime(grp.NewMaxInt())
-	pubKey := grp.ExpG(privKey, grp.NewMaxInt())
-	// Test receiving a Rekey message
-	msg := &parse.Message{
-		Sender: partnerID,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_NO_TYPE),
-			Body:        pubKey.Bytes(),
-		},
-		InferredType: parse.Rekey,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-
-	// Check no error occurred in rekey processing
-	if rekeyList.err != nil {
-		t.Errorf("Rekey returned error: %v", rekeyList.err.Error())
-	}
-	// Confirm hash of baseKey matches expected
-	value := <-ListenCh
-	// Get hash as last 32 bytes of message bytes
-	actual := value[len(value)-32:]
-	km = session.GetKeyStore().GetRecvManager(partnerID)
-	baseKey := grp.NewInt(1)
-	grp.Exp(km.GetPubKey(), km.GetPrivKey(), baseKey)
-	h, _ := hash.NewCMixHash()
-	h.Write(baseKey.Bytes())
-	expected := h.Sum(nil)
-
-	if !bytes.Equal(expected, actual) {
-		t.Errorf("Rekey hash(baseKey) mismatch, expected %x,"+
-			" got %x", expected, actual)
-	}
-
-	// Confirm that keys rotated properly in RekeyManager
-	rkm := session.GetRekeyManager()
-	keys := rkm.GetKeys(partnerID)
-	if keys.CurrPubKey.GetLargeInt().Cmp(session.GetE2EDHPublicKey().GetLargeInt()) == 0 {
-		t.Errorf("Own publicKey didn't update properly after both parties rekeys")
-
-	}
-	if keys.CurrPrivKey.GetLargeInt().
-		Cmp(session.GetE2EDHPrivateKey().GetLargeInt()) == 0 {
-		t.Errorf("Own PrivateKey didn't update properly after both parties rekeys")
-		t.Errorf("%s\n%s", keys.CurrPrivKey.GetLargeInt().Text(16), session.GetE2EDHPrivateKey().GetLargeInt().Text(16))
-	}
-
-	if keys.CurrPubKey.GetLargeInt().
-		Cmp(pubKey.GetLargeInt()) != 0 {
-		t.Errorf("Partner PublicKey didn't update properly after both parties rekeys")
-	}
-}
-
-// Test Rekey errors
-func TestRekey_Errors(t *testing.T) {
-	partnerID := new(id.ID)
-	binary.BigEndian.PutUint64(partnerID[:], 12)
-	partnerID.SetType(id.User)
-	km := session.GetKeyStore().GetRecvManager(partnerID)
-	partnerPubKey := km.GetPubKey()
-	// Delete RekeyKeys so that RekeyTrigger and rekey error out
-	session.GetRekeyManager().DeleteKeys(partnerID)
-	// Test receiving a RekeyTrigger message
-	msg := &parse.Message{
-		Sender: session.GetCurrentUser().User,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_REKEY_TRIGGER),
-			Body:        partnerPubKey.Bytes(),
-		},
-		InferredType: parse.None,
-		Receiver:     partnerID,
-	}
-	session.GetSwitchboard().Speak(msg)
-
-	// Check error occurred on RekeyTrigger
-	if rekeyTriggerList.err == nil {
-		t.Errorf("RekeyTrigger should have returned error")
-	}
-
-	// Test receiving a Rekey message
-	msg = &parse.Message{
-		Sender: partnerID,
-		TypedBody: parse.TypedBody{
-			MessageType: int32(cmixproto.Type_NO_TYPE),
-			Body:        []byte{},
-		},
-		InferredType: parse.Rekey,
-		Receiver:     session.GetCurrentUser().User,
-	}
-	session.GetSwitchboard().Speak(msg)
-	time.Sleep(time.Second)
-	// Check error occurred on Rekey
-	if rekeyList.err == nil {
-		t.Errorf("Rekey should have returned error")
-	}
-}
-
-func getGroups() (*cyclic.Group, *cyclic.Group) {
-
-	cmixGrp := cyclic.NewGroup(
-		large.NewIntFromString("F6FAC7E480EE519354C058BF856AEBDC43AD60141BAD5573910476D030A869979A7E23F5FC006B6CE1B1D7CDA849BDE46A145F80EE97C21AA2154FA3A5CF25C75E225C6F3384D3C0C6BEF5061B87E8D583BEFDF790ECD351F6D2B645E26904DE3F8A9861CC3EAD0AA40BD7C09C1F5F655A9E7BA7986B92B73FD9A6A69F54EFC92AC7E21D15C9B85A76084D1EEFBC4781B91E231E9CE5F007BC75A8656CBD98E282671C08A5400C4E4D039DE5FD63AA89A618C5668256B12672C66082F0348B6204DD0ADE58532C967D055A5D2C34C43DF9998820B5DFC4C49C6820191CB3EC81062AA51E23CEEA9A37AB523B24C0E93B440FDC17A50B219AB0D373014C25EE8F", 16),
-		large.NewIntFromString("B22FDF91EE6BA01BDE4969C1A986EA1F81C4A1795921403F3437D681D05E95167C2F6414CCB74AC8D6B3BA8C0E85C7E4DEB0E8B5256D37BC5C21C8BE068F5342858AFF2FC7FF2644EBED8B10271941C74C86CCD71AA6D2D98E4C8C70875044900F842998037A7DFB9BC63BAF1BC2800E73AF9615E4F5B869D4C6DE6E5F48FACE9CA594CC5D228CB7F763A0AD6BF6ED78B27F902D9ADA38A1FCD7D09E398CE377BB15A459044D3B8541DC6D8049B66AE1662682254E69FAD31CA0016251D0522EF8FE587A3F6E3AB1E5F9D8C2998874ABAB205217E95B234A7D3E69713B884918ADB57360B5DE97336C7DC2EB8A3FEFB0C4290E7A92FF5758529AC45273135427", 16))
-
-	e2eGrp := cyclic.NewGroup(
-		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B"+
-			"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE"+
-			"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F"+
-			"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041"+
-			"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45"+
-			"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209"+
-			"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29"+
-			"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E"+
-			"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2"+
-			"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696"+
-			"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E"+
-			"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873"+
-			"847AEF49F66E43873", 16),
-		large.NewIntFromString("2", 16))
-
-	return cmixGrp, e2eGrp
-
-}
diff --git a/single/callbackMap.go b/single/callbackMap.go
new file mode 100644
index 0000000000000000000000000000000000000000..39bd3cc166d96e8c3a712a39726b0c5d1cdf820a
--- /dev/null
+++ b/single/callbackMap.go
@@ -0,0 +1,60 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"sync"
+)
+
+type ReceiveComm func(payload []byte, c Contact)
+
+// callbackMap stores a list of possible callbacks that can be called when a
+// message is received. To receive a transmission, each transmitted message must
+// use the same tag as the registered callback. The tag fingerprint is a hash of
+// a tag string that is used to identify the module that the transmission
+// message belongs to. The tag can be anything, but should be long enough so
+// that it is unique.
+type callbackMap struct {
+	callbacks map[singleUse.TagFP]ReceiveComm
+	sync.RWMutex
+}
+
+// newCallbackMap initialises a new map.
+func newCallbackMap() *callbackMap {
+	return &callbackMap{
+		callbacks: map[singleUse.TagFP]ReceiveComm{},
+	}
+}
+
+// registerCallback adds a callback function to the map that associates it with
+// its tag. The tag should be a unique string identifying the module using the
+// callback.
+func (cbm *callbackMap) registerCallback(tag string, callback ReceiveComm) {
+	cbm.Lock()
+	defer cbm.Unlock()
+
+	tagFP := singleUse.NewTagFP(tag)
+	cbm.callbacks[tagFP] = callback
+}
+
+// getCallback returns the callback registered with the given tag fingerprint.
+// An error is returned if no associated callback exists.
+func (cbm *callbackMap) getCallback(tagFP singleUse.TagFP) (ReceiveComm, error) {
+	cbm.RLock()
+	defer cbm.RUnlock()
+
+	cb, exists := cbm.callbacks[tagFP]
+	if !exists {
+		return nil, errors.Errorf("no callback registered for the tag "+
+			"fingerprint %s.", tagFP)
+	}
+
+	return cb, nil
+}
diff --git a/single/callbackMap_test.go b/single/callbackMap_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5af02c8cac5b5b64f372a391ecb031b515f52298
--- /dev/null
+++ b/single/callbackMap_test.go
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"testing"
+)
+
+// Happy path.
+func Test_callbackMap_registerCallback(t *testing.T) {
+	m := newTestManager(0, false, t)
+	callbackChan := make(chan int)
+	testCallbacks := []struct {
+		tag string
+		cb  ReceiveComm
+	}{
+		{"tag1", func([]byte, Contact) { callbackChan <- 0 }},
+		{"tag2", func([]byte, Contact) { callbackChan <- 1 }},
+		{"tag3", func([]byte, Contact) { callbackChan <- 2 }},
+	}
+
+	for _, val := range testCallbacks {
+		m.callbackMap.registerCallback(val.tag, val.cb)
+	}
+
+	for i, val := range testCallbacks {
+		go m.callbackMap.callbacks[singleUse.NewTagFP(val.tag)](nil, Contact{})
+		result := <-callbackChan
+		if result != i {
+			t.Errorf("getCallback() did not return the expected callback."+
+				"\nexpected: %d\nreceived: %d", i, result)
+		}
+	}
+}
+
+// Happy path.
+func Test_callbackMap_getCallback(t *testing.T) {
+	m := newTestManager(0, false, t)
+	callbackChan := make(chan int)
+	testCallbacks := []struct {
+		tagFP singleUse.TagFP
+		cb    ReceiveComm
+	}{
+		{singleUse.UnmarshalTagFP([]byte("tag1")), func([]byte, Contact) { callbackChan <- 0 }},
+		{singleUse.UnmarshalTagFP([]byte("tag2")), func([]byte, Contact) { callbackChan <- 1 }},
+		{singleUse.UnmarshalTagFP([]byte("tsg3")), func([]byte, Contact) { callbackChan <- 2 }},
+	}
+
+	for _, val := range testCallbacks {
+		m.callbackMap.callbacks[val.tagFP] = val.cb
+	}
+
+	cb, err := m.callbackMap.getCallback(testCallbacks[1].tagFP)
+	if err != nil {
+		t.Errorf("getCallback() returned an error: %+v", err)
+	}
+
+	go cb(nil, Contact{})
+
+	result := <-callbackChan
+	if result != 1 {
+		t.Errorf("getCallback() did not return the expected callback."+
+			"\nexpected: %d\nreceived: %d", 1, result)
+	}
+}
+
+// Error path: no callback exists for the given tag fingerprint.
+func Test_callbackMap_getCallback_NoCallbackError(t *testing.T) {
+	m := newTestManager(0, false, t)
+
+	_, err := m.callbackMap.getCallback(singleUse.UnmarshalTagFP([]byte("tag1")))
+	if err == nil {
+		t.Error("getCallback() failed to return an error for a callback that " +
+			"does not exist.")
+	}
+}
diff --git a/single/collator.go b/single/collator.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ffe34cd656b0684857e3af21da80ad9682d1afb
--- /dev/null
+++ b/single/collator.go
@@ -0,0 +1,76 @@
+package single
+
+import (
+	"bytes"
+	"github.com/pkg/errors"
+	"sync"
+)
+
+// Initial value of the collator maxNum that indicates it has yet to be set
+const unsetCollatorMax = -1
+
+// collator stores the list of payloads in the correct order.
+type collator struct {
+	payloads [][]byte // List of payloads, in order
+	maxNum   int      // Max number of messages that can be received
+	count    int      // Current number of messages received
+	sync.Mutex
+}
+
+// newCollator generates an empty list of payloads to fit the max number of
+// possible messages. maxNum is set to indicate that it is not yet set.
+func newCollator(messageCount uint64) *collator {
+	return &collator{
+		payloads: make([][]byte, messageCount),
+		maxNum:   unsetCollatorMax,
+	}
+}
+
+// collate collects message payload parts. Once all parts are received, the full
+// collated payload is returned along with true. Otherwise returns false.
+func (c *collator) collate(payloadBytes []byte) ([]byte, bool, error) {
+	payload, err := unmarshalResponseMessage(payloadBytes)
+	if err != nil {
+		return nil, false, errors.Errorf("Failed to unmarshal response "+
+			"payload: %+v", err)
+	}
+
+	c.Lock()
+	defer c.Unlock()
+
+	// If this is the first message received, then set the max number of
+	// messages expected to be received off its max number of parts.
+	if c.maxNum == unsetCollatorMax {
+		if int(payload.GetMaxParts()) > len(c.payloads) {
+			return nil, false, errors.Errorf("Max number of parts reported by "+
+				"payload %d is larger than collator expected (%d).",
+				payload.GetMaxParts(), len(c.payloads))
+		}
+		c.maxNum = int(payload.GetMaxParts())
+	}
+
+	// Make sure that the part number is within the expected number of parts
+	if int(payload.GetPartNum()) >= c.maxNum {
+		return nil, false, errors.Errorf("Payload part number (%d) greater "+
+			"than max number of expected parts (%d).",
+			payload.GetPartNum(), c.maxNum)
+	}
+
+	// Make sure no payload with the same part number exists
+	if c.payloads[payload.GetPartNum()] != nil {
+		return nil, false, errors.Errorf("A payload for the part number %d "+
+			"already exists in the list.", payload.GetPartNum())
+	}
+
+	// Add the payload to the list
+	c.payloads[payload.GetPartNum()] = payload.GetContents()
+	c.count++
+
+	// Return false if not all messages have been received
+	if c.count < c.maxNum {
+		return nil, false, nil
+	}
+
+	// Collate all the messages
+	return bytes.Join(c.payloads, nil), true, nil
+}
diff --git a/single/collator_test.go b/single/collator_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..79474123ecffa02a2cd68968579cde881bf11d6e
--- /dev/null
+++ b/single/collator_test.go
@@ -0,0 +1,142 @@
+package single
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Happy path
+func Test_newCollator(t *testing.T) {
+	messageCount := uint64(10)
+	expected := &collator{
+		payloads: make([][]byte, messageCount),
+		maxNum:   unsetCollatorMax,
+		count:    0,
+	}
+	c := newCollator(messageCount)
+
+	if !reflect.DeepEqual(expected, c) {
+		t.Errorf("newCollator() failed to generated the expected collator."+
+			"\nexepcted: %+v\nreceived: %+v", expected, c)
+	}
+}
+
+// Happy path.
+func TestCollator_collate(t *testing.T) {
+	messageCount := 16
+	msgPayloadSize := 2
+	msgParts := map[int]responseMessagePart{}
+	expectedData := make([]byte, messageCount*msgPayloadSize)
+	copy(expectedData, "This is the expected final data.")
+
+	buff := bytes.NewBuffer(expectedData)
+	for i := 0; i < messageCount; i++ {
+		msgParts[i] = newResponseMessagePart(msgPayloadSize + 4)
+		msgParts[i].SetMaxParts(uint8(messageCount))
+		msgParts[i].SetPartNum(uint8(i))
+		msgParts[i].SetContents(buff.Next(msgPayloadSize))
+	}
+
+	c := newCollator(uint64(messageCount))
+
+	i := 0
+	var fullPayload []byte
+	for j, part := range msgParts {
+		i++
+
+		var err error
+		var collated bool
+
+		fullPayload, collated, err = c.collate(part.Marshal())
+		if err != nil {
+			t.Errorf("collate() returned an error for part #%d: %+v", j, err)
+		}
+
+		if i == messageCount && (!collated || fullPayload == nil) {
+			t.Errorf("collate() failed to collate a completed payload."+
+				"\ncollated:    %v\nfullPayload: %+v", collated, fullPayload)
+		} else if i < messageCount && (collated || fullPayload != nil) {
+			t.Errorf("collate() signaled it collated an unfinished payload."+
+				"\ncollated:    %v\nfullPayload: %+v", collated, fullPayload)
+		}
+	}
+
+	if !bytes.Equal(expectedData, fullPayload) {
+		t.Errorf("collate() failed to return the correct collated data."+
+			"\nexpected: %s\nreceived: %s", expectedData, fullPayload)
+	}
+}
+
+// Error path: the byte slice cannot be unmarshaled.
+func TestCollator_collate_UnmarshalError(t *testing.T) {
+	payloadBytes := []byte{1}
+	c := newCollator(1)
+	payload, collated, err := c.collate(payloadBytes)
+
+	if err == nil || !strings.Contains(err.Error(), "unmarshal") {
+		t.Errorf("collate() failed to return an error for failing to "+
+			"unmarshal the payload.\nerror: %+v", err)
+	}
+
+	if payload != nil || collated {
+		t.Errorf("collate() signaled the payload was collated on error."+
+			"\npayload:  %+v\ncollated: %+v", payload, collated)
+	}
+}
+
+// Error path: max reported parts by payload larger then set in collator
+func TestCollator_collate_MaxPartsError(t *testing.T) {
+	payloadBytes := []byte{0xFF, 0xFF, 0xFF, 0xFF}
+	c := newCollator(1)
+	payload, collated, err := c.collate(payloadBytes)
+
+	if err == nil || !strings.Contains(err.Error(), "Max number of parts") {
+		t.Errorf("collate() failed to return an error when the max number of "+
+			"parts is larger than the payload size.\nerror: %+v", err)
+	}
+
+	if payload != nil || collated {
+		t.Errorf("collate() signaled the payload was collated on error."+
+			"\npayload:  %+v\ncollated: %+v", payload, collated)
+	}
+}
+
+// Error path: the message part number is greater than the max number of parts.
+func TestCollator_collate_PartNumTooLargeError(t *testing.T) {
+	payloadBytes := []byte{25, 5, 5, 5}
+	c := newCollator(5)
+	payload, collated, err := c.collate(payloadBytes)
+
+	if err == nil || !strings.Contains(err.Error(), "greater than max number of expected parts") {
+		t.Errorf("collate() failed to return an error when the part number is "+
+			"greater than the max number of parts.\nerror: %+v", err)
+	}
+
+	if payload != nil || collated {
+		t.Errorf("collate() signaled the payload was collated on error."+
+			"\npayload:  %+v\ncollated: %+v", payload, collated)
+	}
+}
+
+// Error path: a message with the part number already exists.
+func TestCollator_collate_PartExistsError(t *testing.T) {
+	payloadBytes := []byte{1, 5, 0, 1, 20}
+	c := newCollator(5)
+	payload, collated, err := c.collate(payloadBytes)
+	if err != nil {
+		t.Fatalf("collate() returned an error: %+v", err)
+	}
+
+	payload, collated, err = c.collate(payloadBytes)
+	if err == nil || !strings.Contains(err.Error(), "A payload for the part number") {
+		t.Errorf("collate() failed to return an error when the part number "+
+			"already exists.\nerror: %+v", err)
+	}
+
+	if payload != nil || collated {
+		t.Errorf("collate() signaled the payload was collated on error."+
+			"\npayload:  %+v\ncollated: %+v", payload, collated)
+	}
+}
diff --git a/single/contact.go b/single/contact.go
new file mode 100644
index 0000000000000000000000000000000000000000..bf0599899609261f38475e0ee5eed245898709ae
--- /dev/null
+++ b/single/contact.go
@@ -0,0 +1,67 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"fmt"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// Contact contains the information to respond to a single-use contact.
+type Contact struct {
+	partner       *id.ID          // ID of the person to respond to
+	partnerPubKey *cyclic.Int     // Public key of the partner
+	dhKey         *cyclic.Int     // DH key
+	tagFP         singleUse.TagFP // Identifies which callback to use
+	maxParts      uint8           // Max number of messages allowed in reply
+	used          *int32          // Atomic variable
+}
+
+// NewContact initialises a new Contact with the specified fields.
+func NewContact(partner *id.ID, partnerPubKey, dhKey *cyclic.Int,
+	tagFP singleUse.TagFP, maxParts uint8) Contact {
+	used := int32(0)
+	return Contact{
+		partner:       partner,
+		partnerPubKey: partnerPubKey,
+		dhKey:         dhKey,
+		tagFP:         tagFP,
+		maxParts:      maxParts,
+		used:          &used,
+	}
+}
+
+// GetMaxParts returns the maximum number of message parts that can be sent in a
+// reply.
+func (c Contact) GetMaxParts() uint8 {
+	return c.maxParts
+}
+
+// GetPartner returns a copy of the partner ID.
+func (c Contact) GetPartner() *id.ID {
+	return c.partner.DeepCopy()
+}
+
+// String returns a string of the Contact structure.
+func (c Contact) String() string {
+	format := "Contact{partner:%s  partnerPubKey:%s  dhKey:%s  tagFP:%s  maxParts:%d  used:%d}"
+	return fmt.Sprintf(format, c.partner, c.partnerPubKey.Text(10),
+		c.dhKey.Text(10), c.tagFP, c.maxParts, *c.used)
+}
+
+// Equal determines if c and b have equal field values.
+func (c Contact) Equal(b Contact) bool {
+	return c.partner.Cmp(b.partner) &&
+		c.partnerPubKey.Cmp(b.partnerPubKey) == 0 &&
+		c.dhKey.Cmp(b.dhKey) == 0 &&
+		c.tagFP == b.tagFP &&
+		c.maxParts == b.maxParts &&
+		*c.used == *b.used
+}
diff --git a/single/contact_test.go b/single/contact_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..533e5600f935f1feaf36aad48735e47917466357
--- /dev/null
+++ b/single/contact_test.go
@@ -0,0 +1,102 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"testing"
+)
+
+// Happy path.
+func TestNewContact(t *testing.T) {
+	grp := getGroup()
+	used := int32(0)
+	expected := Contact{
+		partner:       id.NewIdFromString("sender ID", id.User, t),
+		partnerPubKey: grp.NewInt(99),
+		dhKey:         grp.NewInt(42),
+		tagFP:         singleUse.UnmarshalTagFP([]byte("test tagFP")),
+		maxParts:      uint8(rand.Uint64()),
+		used:          &used,
+	}
+
+	testC := NewContact(expected.partner, expected.partnerPubKey,
+		expected.dhKey, expected.tagFP, expected.maxParts)
+
+	if !expected.Equal(testC) {
+		t.Errorf("NewContact() did not return the expected Contact."+
+			"\nexpected: %s\nrecieved: %s", expected, testC)
+	}
+}
+
+// Happy path.
+func TestContact_GetMaxParts(t *testing.T) {
+	grp := getGroup()
+	maxParts := uint8(rand.Uint64())
+	c := NewContact(id.NewIdFromString("sender ID", id.User, t), grp.NewInt(99),
+		grp.NewInt(42), singleUse.TagFP{}, maxParts)
+
+	if maxParts != c.GetMaxParts() {
+		t.Errorf("GetMaxParts() failed to return the expected maxParts."+
+			"\nexpected %d\nreceived: %d", maxParts, c.GetMaxParts())
+	}
+}
+
+// Happy path.
+func TestContact_GetPartner(t *testing.T) {
+	grp := getGroup()
+	senderID := id.NewIdFromString("sender ID", id.User, t)
+	c := NewContact(senderID, grp.NewInt(99), grp.NewInt(42), singleUse.TagFP{},
+		uint8(rand.Uint64()))
+
+	if !senderID.Cmp(c.GetPartner()) {
+		t.Errorf("GetPartner() failed to return the expected sender ID."+
+			"\nexpected %s\nreceived: %s", senderID, c.GetPartner())
+	}
+
+}
+
+// Happy path.
+func TestContact_String(t *testing.T) {
+	grp := getGroup()
+	a := NewContact(id.NewIdFromString("sender ID 1", id.User, t), grp.NewInt(99),
+		grp.NewInt(42), singleUse.UnmarshalTagFP([]byte("test tagFP 1")), uint8(rand.Uint64()))
+	b := NewContact(id.NewIdFromString("sender ID 2", id.User, t),
+		grp.NewInt(98), grp.NewInt(43), singleUse.UnmarshalTagFP([]byte("test tagFP 2")), uint8(rand.Uint64()))
+	c := NewContact(a.GetPartner(), a.partnerPubKey.DeepCopy(), a.dhKey.DeepCopy(), a.tagFP, a.maxParts)
+
+	if a.String() == b.String() {
+		t.Errorf("String() did not return the expected string."+
+			"\na: %s\nb: %s", a, b)
+	}
+	if a.String() != c.String() {
+		t.Errorf("String() did not return the expected string."+
+			"\na: %s\nc: %s", a, c)
+	}
+}
+
+// Happy path.
+func TestContact_Equal(t *testing.T) {
+	grp := getGroup()
+	a := NewContact(id.NewIdFromString("sender ID 1", id.User, t),
+		grp.NewInt(99), grp.NewInt(42), singleUse.UnmarshalTagFP([]byte("test tagFP 1")), uint8(rand.Uint64()))
+	b := NewContact(id.NewIdFromString("sender ID 2", id.User, t),
+		grp.NewInt(98), grp.NewInt(43), singleUse.UnmarshalTagFP([]byte("test tagFP 2")), uint8(rand.Uint64()))
+	c := NewContact(a.GetPartner(), a.partnerPubKey.DeepCopy(), a.dhKey.DeepCopy(), a.tagFP, a.maxParts)
+
+	if a.Equal(b) {
+		t.Errorf("Equal() found two different Contacts as equal."+
+			"\na: %s\nb: %s", a, b)
+	}
+	if !a.Equal(c) {
+		t.Errorf("Equal() found two equal Contacts as not equal."+
+			"\na: %s\nc: %s", a, c)
+	}
+}
diff --git a/single/fingerprintMap.go b/single/fingerprintMap.go
new file mode 100644
index 0000000000000000000000000000000000000000..27f7b54f1bb9b990097cc6cd4b36cb2ecd16f658
--- /dev/null
+++ b/single/fingerprintMap.go
@@ -0,0 +1,55 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"sync"
+)
+
+// fingerprintMap stores a map of fingerprint to key numbers.
+type fingerprintMap struct {
+	fps map[format.Fingerprint]uint64
+	sync.Mutex
+}
+
+// newFingerprintMap returns a map of fingerprints generated from the provided
+// key that is messageCount long.
+func newFingerprintMap(dhKey *cyclic.Int, messageCount uint64) *fingerprintMap {
+	fpm := &fingerprintMap{
+		fps: make(map[format.Fingerprint]uint64, messageCount),
+	}
+
+	for i := uint64(0); i < messageCount; i++ {
+		fp := singleUse.NewResponseFingerprint(dhKey, i)
+		fpm.fps[fp] = i
+	}
+
+	return fpm
+}
+
+// getKey returns true and the corresponding key of the fingerprint exists in
+// the map and returns false otherwise. If the fingerprint exists, then it is
+// deleted prior to returning the key.
+func (fpm *fingerprintMap) getKey(dhKey *cyclic.Int, fp format.Fingerprint) ([]byte, bool) {
+	fpm.Lock()
+	defer fpm.Unlock()
+
+	num, exists := fpm.fps[fp]
+	if !exists {
+		return nil, false
+	}
+
+	// delete found fingerprint
+	delete(fpm.fps, fp)
+
+	// Generate and return the key
+	return singleUse.NewResponseKey(dhKey, num), true
+}
diff --git a/single/fingerprintMap_test.go b/single/fingerprintMap_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2be4e3f8d447beaa6385770bedfca87864a5ca97
--- /dev/null
+++ b/single/fingerprintMap_test.go
@@ -0,0 +1,73 @@
+package single
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"reflect"
+	"testing"
+)
+
+// Happy path.
+func Test_newFingerprintMap(t *testing.T) {
+	baseKey := getGroup().NewInt(42)
+	messageCount := 3
+	expected := &fingerprintMap{
+		fps: map[format.Fingerprint]uint64{
+			singleUse.NewResponseFingerprint(baseKey, 0): 0,
+			singleUse.NewResponseFingerprint(baseKey, 1): 1,
+			singleUse.NewResponseFingerprint(baseKey, 2): 2,
+		},
+	}
+
+	fpm := newFingerprintMap(baseKey, uint64(messageCount))
+
+	if !reflect.DeepEqual(expected, fpm) {
+		t.Errorf("newFingerprintMap() did not generate the expected map."+
+			"\nexpected: %+v\nreceived: %+v", expected, fpm)
+	}
+}
+
+// Happy path.
+func TestFingerprintMap_getKey(t *testing.T) {
+	baseKey := getGroup().NewInt(42)
+	fpm := newFingerprintMap(baseKey, 5)
+	fp := singleUse.NewResponseFingerprint(baseKey, 3)
+	expectedKey := singleUse.NewResponseKey(baseKey, 3)
+
+	testKey, exists := fpm.getKey(baseKey, fp)
+	if !exists {
+		t.Errorf("getKey() failed to find key that exists in map."+
+			"\nfingerprint: %+v", fp)
+	}
+
+	if !bytes.Equal(expectedKey, testKey) {
+		t.Errorf("getKey() returned the wrong key.\nexpected: %+v\nreceived: %+v",
+			expectedKey, testKey)
+	}
+
+	testFP, exists := fpm.fps[fp]
+	if exists {
+		t.Errorf("getKey() failed to delete the found fingerprint."+
+			"\nfingerprint: %+v", testFP)
+	}
+}
+
+// Error path: fingerprint does not exist in map.
+func TestFingerprintMap_getKey_FingerprintNotInMap(t *testing.T) {
+	baseKey := getGroup().NewInt(42)
+	fpm := newFingerprintMap(baseKey, 5)
+	fp := singleUse.NewResponseFingerprint(baseKey, 30)
+
+	key, exists := fpm.getKey(baseKey, fp)
+	if exists {
+		t.Errorf("getKey() found a fingerprint in the map that should not exist."+
+			"\nfingerprint: %+v\nkey:         %+v", fp, key)
+	}
+
+	// Ensure no fingerprints were deleted
+	if len(fpm.fps) != 5 {
+		t.Errorf("getKey() deleted fingerprint."+
+			"\nexpected size: %d\nreceived size: %d", 5, len(fpm.fps))
+	}
+}
diff --git a/single/manager.go b/single/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..78481829180017f242ca45e43b2bacc4175cf4dd
--- /dev/null
+++ b/single/manager.go
@@ -0,0 +1,97 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/api"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+const (
+	rawMessageBuffSize           = 100
+	singleUseTransmission        = "SingleUseTransmission"
+	singleUseReceiveTransmission = "SingleUseReceiveTransmission"
+	singleUseResponse            = "SingleUseResponse"
+	singleUseReceiveResponse     = "SingleUseReceiveResponse"
+	singleUseStop                = "SingleUse"
+)
+
+// Manager handles the transmission and reception of single-use communication.
+type Manager struct {
+	// Client and its field
+	client    *api.Client
+	store     *storage.Session
+	reception *reception.Store
+	swb       interfaces.Switchboard
+	net       interfaces.NetworkManager
+	rng       *fastRNG.StreamGenerator
+
+	// Holds the information needed to manage each pending communication. A
+	// state is created when a transmission is started and is removed on
+	// response or timeout.
+	p *pending
+
+	// List of callbacks that can be called when a transmission is received. For
+	// an entity to receive a message, it must register a callback in this map
+	// with the same tag used to send the message.
+	callbackMap *callbackMap
+}
+
+// NewManager creates a new single-use communication manager.
+func NewManager(client *api.Client) *Manager {
+	return newManager(client, client.GetStorage().Reception())
+}
+
+func newManager(client *api.Client, reception *reception.Store) *Manager {
+	return &Manager{
+		client:      client,
+		store:       client.GetStorage(),
+		reception:   reception,
+		swb:         client.GetSwitchboard(),
+		net:         client.GetNetworkInterface(),
+		rng:         client.GetRng(),
+		p:           newPending(),
+		callbackMap: newCallbackMap(),
+	}
+}
+
+// StartProcesses starts the process of receiving single-use transmissions and
+// replies.
+func (m *Manager) StartProcesses() stoppable.Stoppable {
+	// Start waiting for single-use transmission
+	transmissionStop := stoppable.NewSingle(singleUseTransmission)
+	transmissionChan := make(chan message.Receive, rawMessageBuffSize)
+	m.swb.RegisterChannel(singleUseReceiveTransmission, &id.ID{}, message.Raw, transmissionChan)
+	go m.receiveTransmissionHandler(transmissionChan, transmissionStop.Quit())
+
+	// Start waiting for single-use response
+	responseStop := stoppable.NewSingle(singleUseResponse)
+	responseChan := make(chan message.Receive, rawMessageBuffSize)
+	m.swb.RegisterChannel(singleUseReceiveResponse, &id.ID{}, message.Raw, responseChan)
+	go m.receiveResponseHandler(responseChan, responseStop.Quit())
+
+	// Create a multi stoppable
+	singleUseMulti := stoppable.NewMulti(singleUseStop)
+	singleUseMulti.Add(transmissionStop)
+	singleUseMulti.Add(responseStop)
+
+	return singleUseMulti
+}
+
+// RegisterCallback registers a callback for received messages.
+func (m *Manager) RegisterCallback(tag string, callback ReceiveComm) {
+	jww.DEBUG.Printf("Registering single-use callback with tag %s.", tag)
+	m.callbackMap.registerCallback(tag, callback)
+}
diff --git a/single/manager_test.go b/single/manager_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ff09e8ecd8ed9faa67f3339c283ea365c7899c5c
--- /dev/null
+++ b/single/manager_test.go
@@ -0,0 +1,362 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	"errors"
+	"gitlab.com/elixxir/client/api"
+	"gitlab.com/elixxir/client/interfaces"
+	contact2 "gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/client/switchboard"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"gitlab.com/xx_network/primitives/ndf"
+	"math/rand"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func Test_newManager(t *testing.T) {
+	client := &api.Client{}
+	e := &Manager{
+		client: client,
+		p:      newPending(),
+	}
+	m := newManager(client, &reception.Store{})
+
+	if e.client != m.client || e.store != m.store || e.net != m.net ||
+		e.rng != m.rng || !reflect.DeepEqual(e.p, m.p) {
+		t.Errorf("NewManager() did not return the expected new Manager."+
+			"\nexpected: %+v\nreceived: %+v", e, m)
+	}
+}
+
+// Happy path.
+func TestManager_StartProcesses(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReceiveComm()
+
+	transmitMsg, _, rid, _, err := m.makeTransmitCmixMessage(partner, payload,
+		tag, 8, 32, 30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create tranmission CMIX message: %+v", err)
+	}
+
+	_, sender, err := m.processTransmission(transmitMsg, singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey()))
+
+	replyMsgs, err := m.makeReplyCmixMessages(sender, payload)
+	if err != nil {
+		t.Fatalf("Failed to generate reply CMIX messages: %+v", err)
+	}
+
+	receiveMsg := message.Receive{
+		Payload:     transmitMsg.Marshal(),
+		MessageType: message.Raw,
+		Sender:      rid,
+		RecipientID: partner.ID,
+	}
+
+	m.callbackMap.registerCallback(tag, callback)
+
+	_ = m.StartProcesses()
+	m.swb.(*switchboard.Switchboard).Speak(receiveMsg)
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		if !bytes.Equal(results.payload, payload) {
+			t.Errorf("Callback received wrong payload."+
+				"\nexpected: %+v\nreceived: %+v", payload, results.payload)
+		}
+	case <-timer.C:
+		t.Errorf("Callback failed to be called.")
+	}
+
+	callbackFunc, callbackFuncChan := createReplyComm()
+	m.p.Lock()
+	m.p.singleUse[*rid] = newState(sender.dhKey, sender.maxParts, callbackFunc)
+	m.p.Unlock()
+	eid, _, _, _ := ephemeral.GetId(partner.ID, id.ArrIDLen, time.Now().UnixNano())
+	replyMsg := message.Receive{
+		Payload:     replyMsgs[0].Marshal(),
+		MessageType: message.Raw,
+		Sender:      partner.ID,
+		RecipientID: rid,
+		EphemeralID: eid,
+	}
+
+	go func() {
+		timer := time.NewTimer(50 * time.Millisecond)
+		select {
+		case <-timer.C:
+			t.Errorf("quitChan never set.")
+		case <-m.p.singleUse[*rid].quitChan:
+		}
+	}()
+
+	m.swb.(*switchboard.Switchboard).Speak(replyMsg)
+
+	timer = time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackFuncChan:
+		if !bytes.Equal(results.payload, payload) {
+			t.Errorf("Callback received wrong payload."+
+				"\nexpected: %+v\nreceived: %+v", payload, results.payload)
+		}
+	case <-timer.C:
+		t.Errorf("Callback failed to be called.")
+	}
+}
+
+// Happy path: tests that the stoppable stops both routines.
+func TestManager_StartProcesses_Stop(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReceiveComm()
+
+	transmitMsg, _, rid, _, err := m.makeTransmitCmixMessage(partner, payload,
+		tag, 8, 32, 30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create tranmission CMIX message: %+v", err)
+	}
+
+	_, sender, err := m.processTransmission(transmitMsg, singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey()))
+
+	replyMsgs, err := m.makeReplyCmixMessages(sender, payload)
+	if err != nil {
+		t.Fatalf("Failed to generate reply CMIX messages: %+v", err)
+	}
+
+	receiveMsg := message.Receive{
+		Payload:     transmitMsg.Marshal(),
+		MessageType: message.Raw,
+		Sender:      rid,
+		RecipientID: partner.ID,
+	}
+
+	m.callbackMap.registerCallback(tag, callback)
+
+	stop := m.StartProcesses()
+	if !stop.IsRunning() {
+		t.Error("Stoppable is not running.")
+	}
+
+	err = stop.Close(1 * time.Millisecond)
+	if err != nil {
+		t.Errorf("Failed to close: %+v", err)
+	}
+
+	m.swb.(*switchboard.Switchboard).Speak(receiveMsg)
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when the thread should have stopped."+
+			"\npayload: %+v\ncontact: %+v", results.payload, results.c)
+	case <-timer.C:
+	}
+
+	callbackFunc, callbackFuncChan := createReplyComm()
+	m.p.Lock()
+	m.p.singleUse[*rid] = newState(sender.dhKey, sender.maxParts, callbackFunc)
+	m.p.Unlock()
+	eid, _, _, _ := ephemeral.GetId(partner.ID, id.ArrIDLen, time.Now().UnixNano())
+	replyMsg := message.Receive{
+		Payload:     replyMsgs[0].Marshal(),
+		MessageType: message.Raw,
+		Sender:      partner.ID,
+		RecipientID: rid,
+		EphemeralID: eid,
+	}
+
+	m.swb.(*switchboard.Switchboard).Speak(replyMsg)
+
+	timer = time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackFuncChan:
+		t.Errorf("Callback called when the thread should have stopped."+
+			"\npayload: %+v\nerror: %+v", results.payload, results.err)
+	case <-timer.C:
+	}
+}
+
+type receiveCommData struct {
+	payload []byte
+	c       Contact
+}
+
+func createReceiveComm() (ReceiveComm, chan receiveCommData) {
+	callbackChan := make(chan receiveCommData)
+
+	callback := func(payload []byte, c Contact) {
+		callbackChan <- receiveCommData{payload: payload, c: c}
+	}
+	return callback, callbackChan
+}
+
+func newTestManager(timeout time.Duration, cmixErr bool, t *testing.T) *Manager {
+	return &Manager{
+		client:      nil,
+		store:       storage.InitTestingSession(t),
+		reception:   reception.NewStore(versioned.NewKV(make(ekv.Memstore))),
+		swb:         switchboard.New(),
+		net:         newTestNetworkManager(timeout, cmixErr, t),
+		rng:         fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG),
+		p:           newPending(),
+		callbackMap: newCallbackMap(),
+	}
+}
+
+func newTestNetworkManager(timeout time.Duration, cmixErr bool, 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,
+		msgs:        []format.Message{},
+		cmixTimeout: timeout,
+		cmixErr:     cmixErr,
+	}
+}
+
+// testNetworkManager is a test implementation of NetworkManager interface.
+type testNetworkManager struct {
+	instance    *network.Instance
+	msgs        []format.Message
+	cmixTimeout time.Duration
+	cmixErr     bool
+	sync.RWMutex
+}
+
+func (tnm *testNetworkManager) GetMsg(i int) format.Message {
+	tnm.RLock()
+	defer tnm.RUnlock()
+	return tnm.msgs[i]
+}
+
+func (tnm *testNetworkManager) SendE2E(_ message.Send, _ params.E2E) ([]id.Round, e2e.MessageID, error) {
+	return nil, [32]byte{}, nil
+}
+
+func (tnm *testNetworkManager) SendUnsafe(_ message.Send, _ params.Unsafe) ([]id.Round, error) {
+	return []id.Round{}, nil
+}
+
+func (tnm *testNetworkManager) SendCMIX(msg format.Message, _ *id.ID, _ params.CMIX) (id.Round, ephemeral.Id, error) {
+	if tnm.cmixTimeout != 0 {
+		time.Sleep(tnm.cmixTimeout)
+	} else if tnm.cmixErr {
+		return 0, ephemeral.Id{}, errors.New("sendCMIX error")
+	}
+
+	tnm.Lock()
+	defer tnm.Unlock()
+
+	tnm.msgs = append(tnm.msgs, msg)
+
+	return id.Round(rand.Uint64()), ephemeral.Id{}, nil
+}
+
+func (tnm *testNetworkManager) GetInstance() *network.Instance {
+	return tnm.instance
+}
+
+func (tnm *testNetworkManager) GetHealthTracker() interfaces.HealthTracker {
+	return nil
+}
+
+func (tnm *testNetworkManager) Follow(report interfaces.ClientErrorReport) (stoppable.Stoppable, error) {
+	return nil, nil
+}
+
+func (tnm *testNetworkManager) CheckGarbledMessages() {}
+
+func (tnm *testNetworkManager) InProgressRegistrations() int {
+	return 0
+}
+
+func getNDF() *ndf.NetworkDefinition {
+	return &ndf.NetworkDefinition{
+		E2E: ndf.Group{
+			Prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B" +
+				"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE" +
+				"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F" +
+				"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041" +
+				"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45" +
+				"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209" +
+				"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29" +
+				"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E" +
+				"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2" +
+				"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696" +
+				"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E" +
+				"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873" +
+				"847AEF49F66E43873",
+			Generator: "2",
+		},
+		CMIX: ndf.Group{
+			Prime: "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
+				"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
+				"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
+				"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
+				"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
+				"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
+				"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
+				"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B",
+			Generator: "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
+				"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
+				"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
+				"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
+				"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
+				"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
+				"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
+				"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7",
+		},
+	}
+}
diff --git a/single/receiveResponse.go b/single/receiveResponse.go
new file mode 100644
index 0000000000000000000000000000000000000000..d25880dae86dee70d882463aeba08d0310daa08b
--- /dev/null
+++ b/single/receiveResponse.go
@@ -0,0 +1,108 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+)
+
+// receiveResponseHandler handles the reception of single-use response messages.
+func (m *Manager) receiveResponseHandler(rawMessages chan message.Receive,
+	quitChan <-chan struct{}) {
+	jww.DEBUG.Print("Waiting to receive single-use response messages.")
+	for {
+		select {
+		case <-quitChan:
+			jww.DEBUG.Printf("Stopping waiting to receive single-use " +
+				"response message.")
+			return
+		case msg := <-rawMessages:
+			jww.DEBUG.Printf("Received CMIX message; checking if it is a " +
+				"single-use response.")
+
+			// Process CMIX message
+			err := m.processesResponse(msg.RecipientID, msg.EphemeralID, msg.Payload)
+			if err != nil {
+				jww.WARN.Printf("Failed to read single-use CMIX message "+
+					"response: %+v", err)
+			}
+		}
+	}
+}
+
+// processesResponse processes the CMIX message and collates its payload. If the
+// message is invalid, an error is returned.
+func (m *Manager) processesResponse(rid *id.ID, ephID ephemeral.Id,
+	msgBytes []byte) error {
+
+	// Get the state from the map
+	m.p.RLock()
+	state, exists := m.p.singleUse[*rid]
+	m.p.RUnlock()
+
+	// Check that the state exists
+	if !exists {
+		return errors.Errorf("no state exists for the reception ID %s.", rid)
+	}
+
+	// Unmarshal CMIX message
+	cmixMsg := format.Unmarshal(msgBytes)
+
+	// Ensure the fingerprints match
+	fp := cmixMsg.GetKeyFP()
+	key, exists := state.fpMap.getKey(state.dhKey, fp)
+	if !exists {
+		return errors.New("message fingerprint does not correspond to the " +
+			"expected fingerprint.")
+	}
+
+	// Verify the CMIX message MAC
+	if !singleUse.VerifyMAC(key, cmixMsg.GetContents(), cmixMsg.GetMac()) {
+		return errors.New("failed to verify the CMIX message MAC.")
+	}
+
+	// Denote that the message is not garbled
+	jww.DEBUG.Print("Received single-use response message.")
+	m.store.GetGarbledMessages().Remove(cmixMsg)
+
+	// Decrypt and collate the payload
+	decryptedPayload := auth.Crypt(key, fp[:24], cmixMsg.GetContents())
+	collatedPayload, collated, err := state.c.collate(decryptedPayload)
+	if err != nil {
+		return errors.Errorf("failed to collate payload: %+v", err)
+	}
+	jww.DEBUG.Print("Successfully processed single-use response message part.")
+
+	// Once all message parts have been received delete and close everything
+	if collated {
+		jww.DEBUG.Print("Received all parts of single-use response message.")
+		// Exit the timeout handler
+		state.quitChan <- struct{}{}
+
+		// Remove identity
+		m.reception.RemoveIdentity(ephID)
+
+		// Remove state from map
+		m.p.Lock()
+		delete(m.p.singleUse, *rid)
+		m.p.Unlock()
+
+		// Call in separate routine to prevent blocking
+		jww.DEBUG.Print("Calling single-use response message callback.")
+		go state.callback(collatedPayload, nil)
+	}
+
+	return nil
+}
diff --git a/single/receiveResponse_test.go b/single/receiveResponse_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2782bc6fc1ad6d74eab2c0c2d51bf9988066734d
--- /dev/null
+++ b/single/receiveResponse_test.go
@@ -0,0 +1,324 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"math/rand"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_ReceiveResponseHandler(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	partner := NewContact(id.NewIdFromString("recipientID", id.User, t),
+		m.store.E2e().GetGroup().NewInt(43), m.store.E2e().GetGroup().NewInt(42),
+		singleUse.TagFP{}, 8)
+	ephID, _, _, err := ephemeral.GetId(partner.partner, id.ArrIDLen, time.Now().UnixNano())
+	payload := make([]byte, 2000)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReplyComm()
+	rid := id.NewIdFromString("rid", id.User, t)
+
+	m.p.singleUse[*rid] = newState(partner.dhKey, partner.maxParts, callback)
+
+	msgs, err := m.makeReplyCmixMessages(partner, payload)
+	if err != nil {
+		t.Fatalf("Failed to generate CMIX messages: %+v", err)
+	}
+
+	go func() {
+		timer := time.NewTimer(50 * time.Millisecond)
+		select {
+		case <-timer.C:
+			t.Errorf("quitChan never set.")
+		case <-m.p.singleUse[*rid].quitChan:
+		}
+	}()
+
+	go m.receiveResponseHandler(rawMessages, quitChan)
+
+	for _, msg := range msgs {
+		rawMessages <- message.Receive{
+			Payload:     msg.Marshal(),
+			Sender:      partner.partner,
+			RecipientID: rid,
+			EphemeralID: ephID,
+		}
+	}
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		if !bytes.Equal(results.payload, payload) {
+			t.Errorf("Callback received wrong payload."+
+				"\nexpected: %+v\nreceived: %+v", payload, results.payload)
+		}
+		if results.err != nil {
+			t.Errorf("Callback received an error: %+v", results.err)
+		}
+	case <-timer.C:
+		t.Errorf("Callback failed to be called.")
+	}
+
+	quitChan <- struct{}{}
+}
+
+// Error path: invalid CMIX message.
+func TestManager_ReceiveResponseHandler_CmixMessageError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	partner := NewContact(id.NewIdFromString("recipientID", id.User, t),
+		m.store.E2e().GetGroup().NewInt(43), m.store.E2e().GetGroup().NewInt(42),
+		singleUse.TagFP{}, 8)
+	ephID, _, _, _ := ephemeral.GetId(partner.partner, id.ArrIDLen, time.Now().UnixNano())
+	payload := make([]byte, 2000)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReplyComm()
+	rid := id.NewIdFromString("rid", id.User, t)
+
+	m.p.singleUse[*rid] = newState(partner.dhKey, partner.maxParts, callback)
+
+	go func() {
+		timer := time.NewTimer(50 * time.Millisecond)
+		select {
+		case <-timer.C:
+		case <-m.p.singleUse[*rid].quitChan:
+			t.Error("quitChan called on error.")
+		}
+	}()
+
+	go m.receiveResponseHandler(rawMessages, quitChan)
+
+	rawMessages <- message.Receive{
+		Payload:     make([]byte, format.MinimumPrimeSize*2),
+		Sender:      partner.partner,
+		RecipientID: rid,
+		EphemeralID: ephID,
+	}
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when it should not have been."+
+			"payload: %+v\nerror: %+v", results.payload, results.err)
+	case <-timer.C:
+	}
+
+	quitChan <- struct{}{}
+}
+
+// Happy path.
+func TestManager_processesResponse(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rid := id.NewIdFromString("test RID", id.User, t)
+	ephID, _, _, err := ephemeral.GetId(rid, id.ArrIDLen, time.Now().UnixNano())
+	if err != nil {
+		t.Fatalf("Failed to create ephemeral ID: %+v", err)
+	}
+	dhKey := getGroup().NewInt(5)
+	maxMsgs := uint8(6)
+	timeout := 5 * time.Millisecond
+	callback, callbackChan := createReplyComm()
+
+	m.p.singleUse[*rid] = newState(dhKey, maxMsgs, callback)
+
+	expectedData := []string{"This i", "s the ", "expect", "ed fin", "al dat", "a."}
+	var msgs []format.Message
+	for fp, i := range m.p.singleUse[*rid].fpMap.fps {
+		newMsg := format.NewMessage(format.MinimumPrimeSize)
+		part := newResponseMessagePart(newMsg.ContentsSize())
+		part.SetContents([]byte(expectedData[i]))
+		part.SetPartNum(uint8(i))
+		part.SetMaxParts(maxMsgs)
+
+		key := singleUse.NewResponseKey(dhKey, i)
+		encryptedPayload := auth.Crypt(key, fp[:24], part.Marshal())
+
+		newMsg.SetKeyFP(fp)
+		newMsg.SetMac(singleUse.MakeMAC(key, encryptedPayload))
+		newMsg.SetContents(encryptedPayload)
+		msgs = append(msgs, newMsg)
+	}
+
+	go func() {
+		timer := time.NewTimer(timeout)
+		select {
+		case <-timer.C:
+			t.Errorf("quitChan never set.")
+		case <-m.p.singleUse[*rid].quitChan:
+		}
+	}()
+
+	for i, msg := range msgs {
+		err := m.processesResponse(rid, ephID, msg.Marshal())
+		if err != nil {
+			t.Errorf("processesResponse() returned an error (%d): %+v", i, err)
+		}
+	}
+
+	timer := time.NewTimer(timeout)
+	select {
+	case <-timer.C:
+		t.Errorf("Callback never called.")
+	case results := <-callbackChan:
+		if results.err != nil {
+			t.Errorf("Callback returned an error: %+v", err)
+		}
+		if !bytes.Equal([]byte(strings.Join(expectedData, "")), results.payload) {
+			t.Errorf("Callback returned incorrect data."+
+				"\nexpected: %s\nreceived: %s", expectedData, results.payload)
+		}
+	}
+
+	if _, exists := m.p.singleUse[*rid]; exists {
+		t.Error("Failed to delete the state after collation is complete.")
+	}
+}
+
+// Error path: no state in map.
+func TestManager_processesResponse_NoStateError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rid := id.NewIdFromString("test RID", id.User, t)
+
+	err := m.processesResponse(rid, ephemeral.Id{}, []byte{})
+	if !check(err, "no state exists for the reception ID") {
+		t.Errorf("processesResponse() did not return an error when the state "+
+			"is not in the map: %+v", err)
+	}
+}
+
+// Error path: failed to verify MAC.
+func TestManager_processesResponse_MacVerificationError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	timeout := 5 * time.Millisecond
+	callback := func(payload []byte, err error) {}
+
+	quitChan, _, err := m.p.addState(rid, dhKey, 1, callback, timeout)
+	if err != nil {
+		t.Fatalf("Failed to add state: %+v", err)
+	}
+	quitChan <- struct{}{}
+
+	newMsg := format.NewMessage(format.MinimumPrimeSize)
+	part := newResponseMessagePart(newMsg.ContentsSize())
+	part.SetContents([]byte("payload data"))
+	part.SetPartNum(0)
+	part.SetMaxParts(1)
+	newMsg.SetMac(singleUse.MakeMAC(dhKey.Bytes(), []byte("some data")))
+	newMsg.SetContents([]byte("payload data"))
+
+	var fp format.Fingerprint
+	for fpt, i := range m.p.singleUse[*rid].fpMap.fps {
+		if i == 0 {
+			fp = fpt
+		}
+	}
+	newMsg.SetKeyFP(fp)
+
+	err = m.processesResponse(rid, ephemeral.Id{}, newMsg.Marshal())
+	if !check(err, "MAC") {
+		t.Errorf("processesResponse() did not return an error when MAC "+
+			"verification should have failed: %+v", err)
+	}
+}
+
+// Error path: CMIX message fingerprint does not match fingerprints in map.
+func TestManager_processesResponse_FingerprintError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	timeout := 5 * time.Millisecond
+	callback := func(payload []byte, err error) {}
+
+	quitChan, _, err := m.p.addState(rid, dhKey, 1, callback, timeout)
+	if err != nil {
+		t.Fatalf("Failed to add state: %+v", err)
+	}
+	quitChan <- struct{}{}
+
+	var fp format.Fingerprint
+	for fpt, i := range m.p.singleUse[*rid].fpMap.fps {
+		if i == 0 {
+			fp = fpt
+		}
+	}
+	newMsg := format.NewMessage(format.MinimumPrimeSize)
+	part := newResponseMessagePart(newMsg.ContentsSize())
+	part.SetContents([]byte("payload data"))
+	part.SetPartNum(0)
+	part.SetMaxParts(1)
+
+	key := singleUse.NewResponseKey(dhKey, 0)
+	encryptedPayload := auth.Crypt(key, fp[:24], part.Marshal())
+
+	newMsg.SetKeyFP(format.NewFingerprint([]byte("Invalid Fingerprint")))
+	newMsg.SetMac(singleUse.MakeMAC(key, encryptedPayload))
+	newMsg.SetContents(encryptedPayload)
+
+	err = m.processesResponse(rid, ephemeral.Id{}, newMsg.Marshal())
+	if !check(err, "fingerprint") {
+		t.Errorf("processesResponse() did not return an error when "+
+			"fingerprint was wrong: %+v", err)
+	}
+}
+
+// Error path: collator fails because part number is wrong.
+func TestManager_processesResponse_CollatorError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	timeout := 5 * time.Millisecond
+	callback := func(payload []byte, err error) {}
+
+	quitChan, _, err := m.p.addState(rid, dhKey, 1, callback, timeout)
+	if err != nil {
+		t.Fatalf("Failed to add state: %+v", err)
+	}
+	quitChan <- struct{}{}
+
+	var fp format.Fingerprint
+	for fpt, i := range m.p.singleUse[*rid].fpMap.fps {
+		if i == 0 {
+			fp = fpt
+		}
+	}
+	newMsg := format.NewMessage(format.MinimumPrimeSize)
+	part := newResponseMessagePart(newMsg.ContentsSize())
+	part.SetContents([]byte("payload data"))
+	part.SetPartNum(5)
+	part.SetMaxParts(1)
+
+	key := singleUse.NewResponseKey(dhKey, 0)
+	encryptedPayload := auth.Crypt(key, fp[:24], part.Marshal())
+
+	newMsg.SetKeyFP(fp)
+	newMsg.SetMac(singleUse.MakeMAC(key, encryptedPayload))
+	newMsg.SetContents(encryptedPayload)
+
+	err = m.processesResponse(rid, ephemeral.Id{}, newMsg.Marshal())
+	if !check(err, "collate") {
+		t.Errorf("processesResponse() did not return an error when "+
+			"collation should have failed: %+v", err)
+	}
+}
diff --git a/single/reception.go b/single/reception.go
new file mode 100644
index 0000000000000000000000000000000000000000..53b6c12eda52386a3544b547bee0b54b91bc1d2a
--- /dev/null
+++ b/single/reception.go
@@ -0,0 +1,110 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+)
+
+// receiveTransmissionHandler waits to receive single-use transmissions. When
+// a message is received, its is returned via its registered callback.
+func (m *Manager) receiveTransmissionHandler(rawMessages chan message.Receive,
+	quitChan <-chan struct{}) {
+	fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey())
+	jww.DEBUG.Print("Waiting to receive single-use transmission messages.")
+	for {
+		select {
+		case <-quitChan:
+			jww.DEBUG.Printf("Stopping waiting to receive single-use " +
+				"transmission message.")
+			return
+		case msg := <-rawMessages:
+			jww.DEBUG.Printf("Received CMIX message; checking if it is a " +
+				"single-use transmission.")
+
+			// Check if message is a single-use transmit message
+			cmixMsg := format.Unmarshal(msg.Payload)
+			if fp != cmixMsg.GetKeyFP() {
+				// If the verification fails, then ignore the message as it is
+				// likely garbled or for a different protocol
+				jww.INFO.Print("Failed to read single-use CMIX message: " +
+					"fingerprint verification failed.")
+				continue
+			}
+
+			// Denote that the message is not garbled
+			jww.DEBUG.Printf("Received single-use transmission message.")
+			m.store.GetGarbledMessages().Remove(cmixMsg)
+
+			// Handle message
+			payload, c, err := m.processTransmission(cmixMsg, fp)
+			if err != nil {
+				jww.WARN.Printf("Failed to read single-use CMIX message: %+v",
+					err)
+				continue
+			}
+			jww.DEBUG.Printf("Successfully processed single-use transmission message.")
+
+			// Lookup the registered callback for the message's tag fingerprint
+			callback, err := m.callbackMap.getCallback(c.tagFP)
+			if err != nil {
+				jww.WARN.Printf("Failed to find module to pass single-use "+
+					"payload: %+v", err)
+				continue
+			}
+
+			jww.DEBUG.Printf("Calling single-use callback with tag "+
+				"fingerprint %s.", c.tagFP)
+
+			go callback(payload, c)
+		}
+	}
+}
+
+// processTransmission unmarshalls and decrypts the message payload and
+// returns the decrypted payload and the contact information for the sender.
+func (m *Manager) processTransmission(msg format.Message,
+	fp format.Fingerprint) ([]byte, Contact, error) {
+	grp := m.store.E2e().GetGroup()
+	dhPrivKey := m.store.E2e().GetDHPrivateKey()
+
+	// Unmarshal the CMIX message contents to a transmission message
+	transmitMsg, err := unmarshalTransmitMessage(msg.GetContents(),
+		grp.GetP().ByteLen())
+	if err != nil {
+		return nil, Contact{}, errors.Errorf("failed to unmarshal contents: %+v", err)
+	}
+
+	// Generate DH key and symmetric key
+	dhKey := grp.Exp(transmitMsg.GetPubKey(grp), dhPrivKey, grp.NewInt(1))
+	key := singleUse.NewTransmitKey(dhKey)
+
+	// Verify the MAC
+	if !singleUse.VerifyMAC(key, transmitMsg.GetPayload(), msg.GetMac()) {
+		return nil, Contact{}, errors.New("failed to verify MAC.")
+	}
+
+	// Decrypt the transmission message payload
+	decryptedPayload := cAuth.Crypt(key, fp[:24], transmitMsg.GetPayload())
+
+	// Unmarshal the decrypted payload to a transmission message payload
+	payload, err := unmarshalTransmitMessagePayload(decryptedPayload)
+	if err != nil {
+		return nil, Contact{}, errors.Errorf("failed to unmarshal payload: %+v", err)
+	}
+
+	c := NewContact(payload.GetRID(transmitMsg.GetPubKey(grp)),
+		transmitMsg.GetPubKey(grp), dhKey, payload.GetTagFP(), payload.GetMaxParts())
+
+	return payload.GetContents(), c, nil
+}
diff --git a/single/reception_test.go b/single/reception_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6989406621f2300e2f78c057defaf55d2e5858e1
--- /dev/null
+++ b/single/reception_test.go
@@ -0,0 +1,256 @@
+package single
+
+import (
+	"bytes"
+	contact2 "gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_receiveTransmissionHandler(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReceiveComm()
+
+	msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32,
+		30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create tranmission CMIX message: %+v", err)
+	}
+
+	m.callbackMap.registerCallback(tag, callback)
+
+	go m.receiveTransmissionHandler(rawMessages, quitChan)
+	rawMessages <- message.Receive{
+		Payload: msg.Marshal(),
+	}
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		if !bytes.Equal(results.payload, payload) {
+			t.Errorf("Callback received wrong payload."+
+				"\nexpected: %+v\nreceived: %+v", payload, results.payload)
+		}
+	case <-timer.C:
+		t.Errorf("Callback failed to be called.")
+	}
+}
+
+// Happy path: quit channel.
+func TestManager_receiveTransmissionHandler_QuitChan(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReceiveComm()
+
+	m.callbackMap.registerCallback(tag, callback)
+
+	go m.receiveTransmissionHandler(rawMessages, quitChan)
+	quitChan <- struct{}{}
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when the message should not have been processed."+
+			"\npayload: %+v\ncontact: %+v", results.payload, results.c)
+	case <-timer.C:
+	}
+}
+
+// Error path: CMIX message fingerprint does not match.
+func TestManager_receiveTransmissionHandler_FingerPrintError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(42),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReceiveComm()
+
+	msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32,
+		30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create tranmission CMIX message: %+v", err)
+	}
+
+	m.callbackMap.registerCallback(tag, callback)
+
+	go m.receiveTransmissionHandler(rawMessages, quitChan)
+	rawMessages <- message.Receive{
+		Payload: msg.Marshal(),
+	}
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when the fingerprints do not match."+
+			"\npayload: %+v\ncontact: %+v", results.payload, results.c)
+	case <-timer.C:
+	}
+}
+
+// Error path: cannot process transmission message.
+func TestManager_receiveTransmissionHandler_ProcessMessageError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReceiveComm()
+
+	msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32,
+		30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create tranmission CMIX message: %+v", err)
+	}
+
+	msg.SetMac(make([]byte, format.MacLen))
+
+	m.callbackMap.registerCallback(tag, callback)
+
+	go m.receiveTransmissionHandler(rawMessages, quitChan)
+	rawMessages <- message.Receive{
+		Payload: msg.Marshal(),
+	}
+
+	timer := time.NewTimer(50 * time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when the message should not have been processed."+
+			"\npayload: %+v\ncontact: %+v", results.payload, results.c)
+	case <-timer.C:
+	}
+}
+
+// Error path: tag fingerprint does not match.
+func TestManager_receiveTransmissionHandler_TagFpError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	rawMessages := make(chan message.Receive, rawMessageBuffSize)
+	quitChan := make(chan struct{})
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+
+	msg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload, tag, 8, 32,
+		30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create tranmission CMIX message: %+v", err)
+	}
+
+	go m.receiveTransmissionHandler(rawMessages, quitChan)
+	rawMessages <- message.Receive{
+		Payload: msg.Marshal(),
+	}
+}
+
+// Happy path.
+func TestManager_processTransmission(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("partnerID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	tag := "test tag"
+	payload := []byte("This is the payload.")
+	maxMsgs := uint8(6)
+	cmixMsg, dhKey, rid, _, err := m.makeTransmitCmixMessage(partner, payload,
+		tag, maxMsgs, 32, 30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to generate expected CMIX message: %+v", err)
+	}
+
+	tMsg, err := unmarshalTransmitMessage(cmixMsg.GetContents(), m.store.E2e().GetGroup().GetP().ByteLen())
+	if err != nil {
+		t.Fatalf("Failed to make transmitMessage: %+v", err)
+	}
+
+	expectedC := NewContact(rid, tMsg.GetPubKey(m.store.E2e().GetGroup()),
+		dhKey, singleUse.NewTagFP(tag), maxMsgs)
+
+	fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey())
+	content, testC, err := m.processTransmission(cmixMsg, fp)
+	if err != nil {
+		t.Errorf("processTransmission() produced an error: %+v", err)
+	}
+
+	if !expectedC.Equal(testC) {
+		t.Errorf("processTransmission() did not return the expected values."+
+			"\nexpected: %+v\nrecieved: %+v", expectedC, testC)
+	}
+
+	if !bytes.Equal(payload, content) {
+		t.Errorf("processTransmission() returned the wrong payload."+
+			"\nexpected: %+v\nreceived: %+v", payload, content)
+	}
+}
+
+// Error path: fails to unmarshal transmitMessage.
+func TestManager_processTransmission_TransmitMessageUnmarshalError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	cmixMsg := format.NewMessage(format.MinimumPrimeSize)
+
+	fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey())
+	_, _, err := m.processTransmission(cmixMsg, fp)
+	if !check(err, "failed to unmarshal contents") {
+		t.Errorf("processTransmission() did not produce an error when "+
+			"the transmitMessage failed to unmarshal: %+v", err)
+	}
+}
+
+// Error path: MAC fails to verify.
+func TestManager_processTransmission_MacVerifyError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("partnerID", id.User, t),
+		DhPubKey: m.store.E2e().GetDHPublicKey(),
+	}
+	cmixMsg, _, _, _, err := m.makeTransmitCmixMessage(partner, []byte{}, "", 6,
+		32, 30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to generate expected CMIX message: %+v", err)
+	}
+
+	cmixMsg.SetMac(make([]byte, 32))
+
+	fp := singleUse.NewTransmitFingerprint(m.store.E2e().GetDHPublicKey())
+	_, _, err = m.processTransmission(cmixMsg, fp)
+	if !check(err, "failed to verify MAC") {
+		t.Errorf("processTransmission() did not produce an error when "+
+			"the MAC failed to verify: %+v", err)
+	}
+}
diff --git a/single/response.go b/single/response.go
new file mode 100644
index 0000000000000000000000000000000000000000..e847847b0f5f8206952550dc84d39b05c7e2be24
--- /dev/null
+++ b/single/response.go
@@ -0,0 +1,189 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+// GetMaxResponsePayloadSize returns the maximum payload size for a response
+// message.
+func (m *Manager) GetMaxResponsePayloadSize() int {
+	// Generate empty messages to determine the available space for the payload
+	cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen())
+	responseMsg := newResponseMessagePart(cmixMsg.ContentsSize())
+
+	return responseMsg.GetMaxContentsSize()
+}
+
+// RespondSingleUse creates the single-use response messages with the given
+// payload and sends them to the given partner.
+func (m *Manager) RespondSingleUse(partner Contact, payload []byte,
+	timeout time.Duration) error {
+	return m.respondSingleUse(partner, payload, timeout,
+		m.net.GetInstance().GetRoundEvents())
+}
+
+// respondSingleUse allows for easier testing.
+func (m *Manager) respondSingleUse(partner Contact, payload []byte,
+	timeout time.Duration, roundEvents roundEvents) error {
+	// Ensure that only a single reply can be sent in response
+	firstUse := atomic.CompareAndSwapInt32(partner.used, 0, 1)
+	if !firstUse {
+		return errors.Errorf("cannot send to single-use contact that has " +
+			"already been sent to.")
+	}
+
+	// Generate messages from payload
+	msgs, err := m.makeReplyCmixMessages(partner, payload)
+	if err != nil {
+		return errors.Errorf("failed to create new CMIX messages: %+v", err)
+	}
+
+	jww.DEBUG.Printf("Created %d single-use response CMIX message parts.", len(msgs))
+
+	// Tracks the numbers of rounds that messages are sent on
+	rounds := make([]id.Round, len(msgs))
+
+	sendResults := make(chan ds.EventReturn, len(msgs))
+
+	// Send CMIX messages
+	wg := sync.WaitGroup{}
+	for i, cmixMsg := range msgs {
+		wg.Add(1)
+		cmixMsgFunc := cmixMsg
+		j := i
+		go func() {
+			defer wg.Done()
+			// 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 "+
+					"message part %d: %+v", j, err)
+			}
+			jww.DEBUG.Printf("Sending single-use response CMIX message part "+
+				"%d on round %d to ephemeral ID %d.", j, round, ephID.Int64())
+			rounds[j] = round
+
+			roundEvents.AddRoundEventChan(round, sendResults, timeout,
+				states.COMPLETED, states.FAILED)
+		}()
+	}
+
+	// Wait for all go routines to finish
+	wg.Wait()
+	jww.DEBUG.Printf("Sent %d single-use response CMIX messages to %s.", len(msgs), partner.partner)
+
+	// Count the number of rounds
+	roundMap := map[id.Round]struct{}{}
+	for _, roundID := range rounds {
+		roundMap[roundID] = struct{}{}
+	}
+
+	// Wait until the result tracking responds
+	success, numRoundFail, numTimeOut := utility.TrackResults(sendResults, len(roundMap))
+	if !success {
+		return errors.Errorf("tracking results of %d rounds: %d round "+
+			"failures, %d round event time outs; the send cannot be retried.",
+			len(rounds), numRoundFail, numTimeOut)
+	}
+	jww.DEBUG.Printf("Tracked %d single-use response message round(s).", len(roundMap))
+
+	return nil
+}
+
+// makeReplyCmixMessages
+func (m *Manager) makeReplyCmixMessages(partner Contact, payload []byte) ([]format.Message, error) {
+	// Generate internal payloads based off key size to determine if the passed
+	// in payload is too large to fit in the available contents
+	cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen())
+	responseMsg := newResponseMessagePart(cmixMsg.ContentsSize())
+
+	// Maximum payload size is the maximum amount of room in each message
+	// multiplied by the number of messages
+	maxPayloadSize := responseMsg.GetMaxContentsSize() * int(partner.GetMaxParts())
+
+	if maxPayloadSize < len(payload) {
+		return nil, errors.Errorf("length of provided payload (%d) too long "+
+			"for message payload capacity (%d = %d byte payload * %d messages).",
+			len(payload), maxPayloadSize, responseMsg.GetMaxContentsSize(),
+			partner.GetMaxParts())
+	}
+
+	// Split payloads
+	payloadParts := m.splitPayload(payload, responseMsg.GetMaxContentsSize(),
+		int(partner.GetMaxParts()))
+
+	// Create CMIX messages
+	cmixMsgs := make([]format.Message, len(payloadParts))
+	wg := sync.WaitGroup{}
+	for i, contents := range payloadParts {
+		wg.Add(1)
+		go func(partner Contact, contents []byte, i uint8) {
+			defer wg.Done()
+			cmixMsgs[i] = m.makeMessagePart(partner, contents, uint8(len(payloadParts)), i)
+		}(partner, contents, uint8(i))
+	}
+
+	// Wait for all go routines to finish
+	wg.Wait()
+
+	return cmixMsgs, nil
+}
+
+// makeMessagePart generates a CMIX message containing a responseMessagePart.
+func (m *Manager) makeMessagePart(partner Contact, contents []byte, maxPart, i uint8) format.Message {
+	cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen())
+	responseMsg := newResponseMessagePart(cmixMsg.ContentsSize())
+
+	// Compose response message
+	responseMsg.SetMaxParts(maxPart)
+	responseMsg.SetPartNum(i)
+	responseMsg.SetContents(contents)
+
+	// Encrypt payload
+	fp := singleUse.NewResponseFingerprint(partner.dhKey, uint64(i))
+	key := singleUse.NewResponseKey(partner.dhKey, uint64(i))
+	encryptedPayload := cAuth.Crypt(key, fp[:24], responseMsg.Marshal())
+
+	// Generate CMIX message MAC
+	mac := singleUse.MakeMAC(key, encryptedPayload)
+
+	// Compose CMIX message contents
+	cmixMsg.SetContents(encryptedPayload)
+	cmixMsg.SetKeyFP(fp)
+	cmixMsg.SetMac(mac)
+
+	return cmixMsg
+}
+
+// splitPayload splits the given payload into separate payload parts and returns
+// them in a slice. Each part's size is less than or equal to maxSize. Any extra
+// data in the payload is not used if it is longer than the maximum capacity.
+func (m *Manager) splitPayload(payload []byte, maxSize, maxParts int) [][]byte {
+	var parts [][]byte
+	buff := bytes.NewBuffer(payload)
+
+	for i := 0; i < maxParts && buff.Len() > 0; i++ {
+		parts = append(parts, buff.Next(maxSize))
+	}
+	return parts
+}
diff --git a/single/responseMessage.go b/single/responseMessage.go
new file mode 100644
index 0000000000000000000000000000000000000000..28aec540b40c2386842c0e4eb79701d82ee78e41
--- /dev/null
+++ b/single/responseMessage.go
@@ -0,0 +1,127 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"encoding/binary"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+const (
+	partNumLen      = 1
+	maxPartsLen     = 1
+	responseMinSize = partNumLen + maxPartsLen + sizeSize
+)
+
+/*
++-----------------------------------------+
+|          CMIX Message Contents          |
++---------+----------+---------+----------+
+| partNum | maxParts |  size   | contents |
+| 1 bytes |  1 byte  | 2 bytes | variable |
++------------+----------+---------+-------+
+*/
+
+type responseMessagePart struct {
+	data     []byte // Serial of all contents
+	partNum  []byte // Index of message in a series of messages
+	maxParts []byte // The number of parts in this message.
+	size     []byte // Size of the contents
+	contents []byte // The encrypted contents
+}
+
+// newResponseMessagePart generates a new response message part of the specified
+// size.
+func newResponseMessagePart(externalPayloadSize int) responseMessagePart {
+	if externalPayloadSize < responseMinSize {
+		jww.FATAL.Panicf("Failed to create new single-use response message "+
+			"part: size of external payload (%d) is too small to contain the "+
+			"message part number and max parts (%d)",
+			externalPayloadSize, responseMinSize)
+	}
+
+	return mapResponseMessagePart(make([]byte, externalPayloadSize))
+}
+
+// mapResponseMessagePart builds a message part mapped to the passed in data.
+// It is mapped by reference; a copy is not made.
+func mapResponseMessagePart(data []byte) responseMessagePart {
+	return responseMessagePart{
+		data:     data,
+		partNum:  data[:partNumLen],
+		maxParts: data[partNumLen : maxPartsLen+partNumLen],
+		size:     data[maxPartsLen+partNumLen : responseMinSize],
+		contents: data[responseMinSize:],
+	}
+}
+
+// unmarshalResponseMessage converts a byte buffer into a response message part.
+func unmarshalResponseMessage(b []byte) (responseMessagePart, error) {
+	if len(b) < responseMinSize {
+		return responseMessagePart{}, errors.Errorf("Size of passed in bytes "+
+			"(%d) is too small to contain the message part number and max "+
+			"parts (%d).", len(b), responseMinSize)
+	}
+	return mapResponseMessagePart(b), nil
+}
+
+// Marshal returns the bytes of the message part.
+func (m responseMessagePart) Marshal() []byte {
+	return m.data
+}
+
+// GetPartNum returns the index of this part in the message.
+func (m responseMessagePart) GetPartNum() uint8 {
+	return m.partNum[0]
+}
+
+// SetPartNum sets the part number of the message.
+func (m responseMessagePart) SetPartNum(num uint8) {
+	copy(m.partNum, []byte{num})
+}
+
+// GetMaxParts returns the number of parts in the message.
+func (m responseMessagePart) GetMaxParts() uint8 {
+	return m.maxParts[0]
+}
+
+// SetMaxParts sets the number of parts in the message.
+func (m responseMessagePart) SetMaxParts(max uint8) {
+	copy(m.maxParts, []byte{max})
+}
+
+// GetContents returns the contents of the message part.
+func (m responseMessagePart) GetContents() []byte {
+	return m.contents[:binary.BigEndian.Uint16(m.size)]
+}
+
+// GetContentsSize returns the length of the contents.
+func (m responseMessagePart) GetContentsSize() int {
+	return int(binary.BigEndian.Uint16(m.size))
+}
+
+// GetMaxContentsSize returns the max capacity of the contents.
+func (m responseMessagePart) GetMaxContentsSize() int {
+	return len(m.contents)
+}
+
+// SetContents sets the contents of the message part. Does not zero out previous
+// contents.
+func (m responseMessagePart) SetContents(contents []byte) {
+	if len(contents) > len(m.contents) {
+		jww.FATAL.Panicf("Failed to set contents of single-use response "+
+			"message part: max size of message contents (%d) is smaller than "+
+			"the size of the supplied contents (%d).",
+			len(m.contents), len(contents))
+	}
+
+	binary.BigEndian.PutUint16(m.size, uint16(len(contents)))
+
+	copy(m.contents, contents)
+}
diff --git a/single/responseMessage_test.go b/single/responseMessage_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8871fdc898efa1b00f84c27a914dc6850bcef25c
--- /dev/null
+++ b/single/responseMessage_test.go
@@ -0,0 +1,181 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	"math/rand"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Happy path.
+func Test_newResponseMessagePart(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	payloadSize := prng.Intn(2000)
+	expected := responseMessagePart{
+		data:     make([]byte, payloadSize),
+		partNum:  make([]byte, partNumLen),
+		maxParts: make([]byte, maxPartsLen),
+		size:     make([]byte, sizeSize),
+		contents: make([]byte, payloadSize-partNumLen-maxPartsLen-sizeSize),
+	}
+
+	rmp := newResponseMessagePart(payloadSize)
+
+	if !reflect.DeepEqual(expected, rmp) {
+		t.Errorf("newResponseMessagePart() did not return the expected "+
+			"responseMessagePart.\nexpected: %+v\nreceived: %+v", expected, rmp)
+	}
+}
+
+// Error path: provided contents size is not large enough.
+func Test_newResponseMessagePart_PayloadSizeError(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil || !strings.Contains(r.(string), "size of external payload") {
+			t.Error("newResponseMessagePart() did not panic when the size of " +
+				"the payload is smaller than the required size.")
+		}
+	}()
+
+	_ = newResponseMessagePart(1)
+}
+
+// Happy path.
+func Test_mapResponseMessagePart(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	expectedPartNum := uint8(prng.Uint32())
+	expectedMaxParts := uint8(prng.Uint32())
+	size := []byte{uint8(prng.Uint64()), uint8(prng.Uint64())}
+	expectedContents := make([]byte, prng.Intn(2000))
+	prng.Read(expectedContents)
+	var data []byte
+	data = append(data, expectedPartNum, expectedMaxParts)
+	data = append(data, size...)
+	data = append(data, expectedContents...)
+
+	rmp := mapResponseMessagePart(data)
+
+	if expectedPartNum != rmp.partNum[0] {
+		t.Errorf("mapResponseMessagePart() did not correctly map partNum."+
+			"\nexpected: %d\nreceived: %d", expectedPartNum, rmp.partNum[0])
+	}
+
+	if expectedMaxParts != rmp.maxParts[0] {
+		t.Errorf("mapResponseMessagePart() did not correctly map maxParts."+
+			"\nexpected: %d\nreceived: %d", expectedMaxParts, rmp.maxParts[0])
+	}
+
+	if !bytes.Equal(expectedContents, rmp.contents) {
+		t.Errorf("mapResponseMessagePart() did not correctly map contents."+
+			"\nexpected: %+v\nreceived: %+v", expectedContents, rmp.contents)
+	}
+
+	if !bytes.Equal(data, rmp.data) {
+		t.Errorf("mapResponseMessagePart() did not save the data correctly."+
+			"\nexpected: %+v\nreceived: %+v", data, rmp.data)
+	}
+}
+
+// Happy path.
+func TestResponseMessagePart_Marshal_Unmarshal(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	payload := make([]byte, prng.Intn(2000))
+	prng.Read(payload)
+	rmp := newResponseMessagePart(prng.Intn(2000))
+
+	data := rmp.Marshal()
+
+	newRmp, err := unmarshalResponseMessage(data)
+	if err != nil {
+		t.Errorf("unmarshalResponseMessage() produced an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(rmp, newRmp) {
+		t.Errorf("Failed to Marshal() and unmarshal() the responseMessagePart."+
+			"\nexpected: %+v\nrecieved: %+v", rmp, newRmp)
+	}
+}
+
+// Error path: provided bytes are too small.
+func Test_unmarshalResponseMessage(t *testing.T) {
+	_, err := unmarshalResponseMessage([]byte{1})
+	if err == nil {
+		t.Error("unmarshalResponseMessage() did not produce an error when the " +
+			"byte slice is smaller required.")
+	}
+}
+
+// Happy path.
+func TestResponseMessagePart_SetPartNum_GetPartNum(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	expectedPartNum := uint8(prng.Uint32())
+	rmp := newResponseMessagePart(prng.Intn(2000))
+
+	rmp.SetPartNum(expectedPartNum)
+
+	if expectedPartNum != rmp.GetPartNum() {
+		t.Errorf("GetPartNum() failed to return the expected part number."+
+			"\nexpected: %d\nrecieved: %d", expectedPartNum, rmp.GetPartNum())
+	}
+}
+
+// Happy path.
+func TestResponseMessagePart_SetMaxParts_GetMaxParts(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	expectedMaxParts := uint8(prng.Uint32())
+	rmp := newResponseMessagePart(prng.Intn(2000))
+
+	rmp.SetMaxParts(expectedMaxParts)
+
+	if expectedMaxParts != rmp.GetMaxParts() {
+		t.Errorf("GetMaxParts() failed to return the expected max parts."+
+			"\nexpected: %d\nrecieved: %d", expectedMaxParts, rmp.GetMaxParts())
+	}
+}
+
+// Happy path.
+func TestResponseMessagePart_SetContents_GetContents_GetContentsSize_GetMaxContentsSize(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	externalPayloadSize := prng.Intn(2000)
+	contentSize := externalPayloadSize - responseMinSize - 10
+	expectedContents := make([]byte, contentSize)
+	prng.Read(expectedContents)
+	rmp := newResponseMessagePart(externalPayloadSize)
+	rmp.SetContents(expectedContents)
+
+	if !bytes.Equal(expectedContents, rmp.GetContents()) {
+		t.Errorf("GetContents() failed to return the expected contents."+
+			"\nexpected: %+v\nrecieved: %+v", expectedContents, rmp.GetContents())
+	}
+
+	if contentSize != rmp.GetContentsSize() {
+		t.Errorf("GetContentsSize() failed to return the expected contents size."+
+			"\nexpected: %d\nrecieved: %d", contentSize, rmp.GetContentsSize())
+	}
+
+	if externalPayloadSize-responseMinSize != rmp.GetMaxContentsSize() {
+		t.Errorf("GetMaxContentsSize() failed to return the expected max contents size."+
+			"\nexpected: %d\nrecieved: %d",
+			externalPayloadSize-responseMinSize, rmp.GetMaxContentsSize())
+	}
+}
+
+// Error path: size of supplied contents does not match message contents size.
+func TestResponseMessagePart_SetContents_ContentsSizeError(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil || !strings.Contains(r.(string), "max size of message contents") {
+			t.Error("SetContents() did not panic when the size of the supplied " +
+				"bytes is larger than the content size.")
+		}
+	}()
+
+	rmp := newResponseMessagePart(255)
+	rmp.SetContents(make([]byte, 500))
+}
diff --git a/single/response_test.go b/single/response_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c19f472bafa42e122aea75f3f1085f3fc2f522cb
--- /dev/null
+++ b/single/response_test.go
@@ -0,0 +1,260 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_GetMaxResponsePayloadSize(t *testing.T) {
+	m := newTestManager(0, false, t)
+	cmixPrimeSize := m.store.Cmix().GetGroup().GetP().ByteLen()
+	expectedSize := 2*cmixPrimeSize - format.KeyFPLen - format.MacLen - format.RecipientIDLen - responseMinSize
+	testSize := m.GetMaxResponsePayloadSize()
+
+	if expectedSize != testSize {
+		t.Errorf("GetMaxResponsePayloadSize() failed to return the expected size."+
+			"\nexpected: %d\nreceived: %d", expectedSize, testSize)
+	}
+}
+
+// Happy path.
+func TestManager_respondSingleUse(t *testing.T) {
+	m := newTestManager(0, false, t)
+	used := int32(0)
+	partner := Contact{
+		partner:       id.NewIdFromString("partner ID:", id.User, t),
+		partnerPubKey: m.store.E2e().GetGroup().NewInt(42),
+		dhKey:         m.store.E2e().GetGroup().NewInt(99),
+		tagFP:         singleUse.NewTagFP("tag"),
+		maxParts:      10,
+		used:          &used,
+	}
+	payload := make([]byte, 400*int(partner.maxParts))
+	rand.New(rand.NewSource(42)).Read(payload)
+	re := newTestRoundEvents(false)
+
+	expectedMsgs, err := m.makeReplyCmixMessages(partner, payload)
+	if err != nil {
+		t.Fatalf("Failed to created expected messages: %+v", err)
+	}
+
+	err = m.respondSingleUse(partner, payload, 10*time.Millisecond, re)
+	if err != nil {
+		t.Errorf("respondSingleUse() produced an error: %+v", err)
+	}
+
+	// Check that all messages are expected and received
+	if len(m.net.(*testNetworkManager).msgs) != int(partner.GetMaxParts()) {
+		t.Errorf("Recieved incorrect number of messages."+
+			"\nexpected: %d\nreceived: %d", int(partner.GetMaxParts()),
+			len(m.net.(*testNetworkManager).msgs))
+	}
+
+	// Check that all received messages were expected
+	var exists bool
+	for _, received := range m.net.(*testNetworkManager).msgs {
+		exists = false
+		for _, msg := range expectedMsgs {
+			if reflect.DeepEqual(msg, received) {
+				exists = true
+			}
+		}
+		if !exists {
+			t.Errorf("Unexpected message: %+v", received)
+		}
+	}
+}
+
+// Error path: response has already been sent.
+func TestManager_respondSingleUse_ResponseUsedError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	used := int32(1)
+	partner := Contact{
+		partner:       id.NewIdFromString("partner ID:", id.User, t),
+		partnerPubKey: m.store.E2e().GetGroup().NewInt(42),
+		dhKey:         m.store.E2e().GetGroup().NewInt(99),
+		tagFP:         singleUse.NewTagFP("tag"),
+		maxParts:      10,
+		used:          &used,
+	}
+
+	err := m.respondSingleUse(partner, []byte{}, 10*time.Millisecond, newTestRoundEvents(false))
+	if !check(err, "cannot send to single-use contact that has already been sent to") {
+		t.Errorf("respondSingleUse() did not produce the expected error when "+
+			"the contact has been used: %+v", err)
+	}
+}
+
+// Error path: cannot create CMIX message when payload is too large.
+func TestManager_respondSingleUse_MakeCmixMessageError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	used := int32(0)
+	partner := Contact{
+		partner:       id.NewIdFromString("partner ID:", id.User, t),
+		partnerPubKey: m.store.E2e().GetGroup().NewInt(42),
+		dhKey:         m.store.E2e().GetGroup().NewInt(99),
+		tagFP:         singleUse.NewTagFP("tag"),
+		maxParts:      10,
+		used:          &used,
+	}
+	payload := make([]byte, 500*int(partner.maxParts))
+	rand.New(rand.NewSource(42)).Read(payload)
+
+	err := m.respondSingleUse(partner, payload, 10*time.Millisecond, newTestRoundEvents(false))
+	if !check(err, "failed to create new CMIX messages") {
+		t.Errorf("respondSingleUse() did not produce the expected error when "+
+			"the CMIX message creation failed: %+v", err)
+	}
+}
+
+// Error path: TrackResults returns an error.
+func TestManager_respondSingleUse_TrackResultsError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	used := int32(0)
+	partner := Contact{
+		partner:       id.NewIdFromString("partner ID:", id.User, t),
+		partnerPubKey: m.store.E2e().GetGroup().NewInt(42),
+		dhKey:         m.store.E2e().GetGroup().NewInt(99),
+		tagFP:         singleUse.NewTagFP("tag"),
+		maxParts:      10,
+		used:          &used,
+	}
+	payload := make([]byte, 400*int(partner.maxParts))
+	rand.New(rand.NewSource(42)).Read(payload)
+
+	err := m.respondSingleUse(partner, payload, 10*time.Millisecond, newTestRoundEvents(true))
+	if !check(err, "tracking results of") {
+		t.Errorf("respondSingleUse() did not produce the expected error when "+
+			"the CMIX message creation failed: %+v", err)
+	}
+}
+
+// Happy path.
+func TestManager_makeReplyCmixMessages(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := Contact{
+		partner:       id.NewIdFromString("partner ID:", id.User, t),
+		partnerPubKey: m.store.E2e().GetGroup().NewInt(42),
+		dhKey:         m.store.E2e().GetGroup().NewInt(99),
+		tagFP:         singleUse.NewTagFP("tag"),
+		maxParts:      255,
+	}
+	payload := make([]byte, 400*int(partner.maxParts))
+	rand.New(rand.NewSource(42)).Read(payload)
+
+	msgs, err := m.makeReplyCmixMessages(partner, payload)
+	if err != nil {
+		t.Errorf("makeReplyCmixMessages() returned an error: %+v", err)
+	}
+
+	buff := bytes.NewBuffer(payload)
+	for i, msg := range msgs {
+		checkReplyCmixMessage(partner,
+			buff.Next(len(msgs[0].GetContents())-responseMinSize), msg, len(msgs), i, t)
+	}
+}
+
+// Error path: size of payload too large.
+func TestManager_makeReplyCmixMessages_PayloadSizeError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := Contact{
+		partner:       id.NewIdFromString("partner ID:", id.User, t),
+		partnerPubKey: m.store.E2e().GetGroup().NewInt(42),
+		dhKey:         m.store.E2e().GetGroup().NewInt(99),
+		tagFP:         singleUse.NewTagFP("tag"),
+		maxParts:      255,
+	}
+	payload := make([]byte, 500*int(partner.maxParts))
+	rand.New(rand.NewSource(42)).Read(payload)
+
+	_, err := m.makeReplyCmixMessages(partner, payload)
+	if err == nil {
+		t.Error("makeReplyCmixMessages() did not return an error when the " +
+			"payload is too large.")
+	}
+}
+
+func checkReplyCmixMessage(c Contact, payload []byte, msg format.Message, maxParts, i int, t *testing.T) {
+	expectedFP := singleUse.NewResponseFingerprint(c.dhKey, uint64(i))
+	key := singleUse.NewResponseKey(c.dhKey, uint64(i))
+	expectedMac := singleUse.MakeMAC(key, msg.GetContents())
+
+	// Check CMIX message
+	if expectedFP != msg.GetKeyFP() {
+		t.Errorf("CMIX message #%d had incorrect fingerprint."+
+			"\nexpected: %s\nrecieved: %s", i, expectedFP, msg.GetKeyFP())
+	}
+
+	if !singleUse.VerifyMAC(key, msg.GetContents(), msg.GetMac()) {
+		t.Errorf("CMIX message #%d had incorrect MAC."+
+			"\nexpected: %+v\nrecieved: %+v", i, expectedMac, msg.GetMac())
+	}
+
+	// Decrypt payload
+	decryptedPayload := cAuth.Crypt(key, expectedFP[:24], msg.GetContents())
+	responseMsg, err := unmarshalResponseMessage(decryptedPayload)
+	if err != nil {
+		t.Errorf("Failed to unmarshal pay load of CMIX message #%d: %+v", i, err)
+	}
+
+	if !bytes.Equal(payload, responseMsg.GetContents()) {
+		t.Errorf("Response message #%d had incorrect contents."+
+			"\nexpected: %+v\nrecieved: %+v",
+			i, payload, responseMsg.GetContents())
+	}
+
+	if uint8(maxParts) != responseMsg.GetMaxParts() {
+		t.Errorf("Response message #%d had incorrect max parts."+
+			"\nexpected: %+v\nrecieved: %+v",
+			i, maxParts, responseMsg.GetMaxParts())
+	}
+
+	if i != int(responseMsg.GetPartNum()) {
+		t.Errorf("Response message #%d had incorrect part number."+
+			"\nexpected: %+v\nrecieved: %+v",
+			i, i, responseMsg.GetPartNum())
+	}
+}
+
+// Happy path.
+func TestManager_splitPayload(t *testing.T) {
+	m := newTestManager(0, false, t)
+	maxSize := 5
+	maxParts := 10
+	payload := []byte("0123456789012345678901234567890123456789012345678901234" +
+		"5678901234567890123456789012345678901234567890123456789")
+	expectedParts := [][]byte{
+		payload[:maxSize],
+		payload[maxSize : 2*maxSize],
+		payload[2*maxSize : 3*maxSize],
+		payload[3*maxSize : 4*maxSize],
+		payload[4*maxSize : 5*maxSize],
+		payload[5*maxSize : 6*maxSize],
+		payload[6*maxSize : 7*maxSize],
+		payload[7*maxSize : 8*maxSize],
+		payload[8*maxSize : 9*maxSize],
+		payload[9*maxSize : 10*maxSize],
+	}
+
+	testParts := m.splitPayload(payload, maxSize, maxParts)
+
+	if !reflect.DeepEqual(expectedParts, testParts) {
+		t.Errorf("splitPayload() failed to correctly split the payload."+
+			"\nexpected: %s\nreceived: %s", expectedParts, testParts)
+	}
+}
diff --git a/single/singleUseMap.go b/single/singleUseMap.go
new file mode 100644
index 0000000000000000000000000000000000000000..01e1cca5b6b5472dc75063cf79f20b25e3cb65da
--- /dev/null
+++ b/single/singleUseMap.go
@@ -0,0 +1,123 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+// pending contains a map of all pending single-use states.
+type pending struct {
+	singleUse map[id.ID]*state
+	sync.RWMutex
+}
+
+type ReplyComm func(payload []byte, err error)
+
+// state contains the information and state of each single-use message that is
+// being transmitted.
+type state struct {
+	dhKey    *cyclic.Int
+	fpMap    *fingerprintMap // List of fingerprints for each response part
+	c        *collator       // Collects all response message parts
+	callback ReplyComm       // Returns the error status of the communication
+	quitChan chan struct{}   // Sending on channel kills the timeout handler
+}
+
+// newPending creates a pending object with an empty map.
+func newPending() *pending {
+	return &pending{
+		singleUse: map[id.ID]*state{},
+	}
+}
+
+// newState generates a new state object with the fingerprint map and collator
+// initialised.
+func newState(dhKey *cyclic.Int, messageCount uint8, callback ReplyComm) *state {
+	return &state{
+		dhKey:    dhKey,
+		fpMap:    newFingerprintMap(dhKey, uint64(messageCount)),
+		c:        newCollator(uint64(messageCount)),
+		callback: callback,
+		quitChan: make(chan struct{}),
+	}
+}
+
+// addState adds a new state to the map and starts a thread waiting for all the
+// message parts or for the timeout to occur.
+func (p *pending) addState(rid *id.ID, dhKey *cyclic.Int, maxMsgs uint8,
+	callback ReplyComm, timeout time.Duration) (chan struct{}, *int32, error) {
+	p.Lock()
+
+	// Check if the state already exists
+	if _, exists := p.singleUse[*rid]; exists {
+		return nil, nil, errors.Errorf("a state already exists in the map with "+
+			"the ID %s.", rid)
+	}
+
+	jww.DEBUG.Printf("Successfully added single-use state with the ID %s to "+
+		"the map.", rid)
+
+	// Add the state
+	p.singleUse[*rid] = newState(dhKey, maxMsgs, callback)
+	quitChan := p.singleUse[*rid].quitChan
+	p.Unlock()
+
+	// Create atomic which is set when the timeoutHandler thread is killed
+	quit := int32(0)
+
+	go p.timeoutHandler(rid, callback, timeout, quitChan, &quit)
+
+	return quitChan, &quit, nil
+}
+
+// timeoutHandler waits for the signal to complete or times out and deletes the
+// state.
+func (p *pending) timeoutHandler(rid *id.ID, callback ReplyComm,
+	timeout time.Duration, quitChan chan struct{}, quit *int32) {
+	jww.DEBUG.Printf("Starting handler for sending single-use transmission "+
+		"that will timeout after %s.", timeout)
+
+	timer := time.NewTimer(timeout)
+
+	// Signal on the atomic when this thread quits
+	defer func() {
+		atomic.StoreInt32(quit, 1)
+	}()
+
+	select {
+	case <-quitChan:
+		jww.DEBUG.Print("Single-use transmission timeout handler quitting.")
+		return
+	case <-timer.C:
+		jww.WARN.Printf("Single-use transmission timeout handler timed out "+
+			"after %s.", timeout)
+
+		p.Lock()
+		if _, exists := p.singleUse[*rid]; !exists {
+			p.Unlock()
+			return
+		}
+		delete(p.singleUse, *rid)
+
+		p.Unlock()
+
+		err := errors.Errorf("waiting for response to single-use transmission "+
+			"timed out after %s.", timeout.String())
+		jww.DEBUG.Printf("Deleted single-use from map. Calling callback with "+
+			"error: %+v", err)
+
+		callback(nil, err)
+	}
+}
diff --git a/single/singleUseMap_test.go b/single/singleUseMap_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b4a18171640935735b5d3b28042141c77ded768
--- /dev/null
+++ b/single/singleUseMap_test.go
@@ -0,0 +1,206 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"gitlab.com/xx_network/primitives/id"
+	"reflect"
+	"strings"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+// Happy path: trigger the exit channel.
+func Test_pending_addState_ExitChan(t *testing.T) {
+	p := newPending()
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	maxMsgs := uint8(6)
+	timeout := 500 * time.Millisecond
+	callback, callbackChan := createReplyComm()
+
+	quitChan, quit, err := p.addState(rid, dhKey, maxMsgs, callback, timeout)
+	if err != nil {
+		t.Errorf("addState() returned an error: %+v", err)
+	}
+
+	hasQuit := atomic.CompareAndSwapInt32(quit, 0, 1)
+	if !hasQuit {
+		t.Error("Quit atomic called.")
+	}
+
+	expectedState := newState(dhKey, maxMsgs, callback)
+
+	quitChan <- struct{}{}
+
+	timer := time.NewTimer(timeout + 1*time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when the quit channel was used."+
+			"\npayload: %+v\nerror:   %+v", results.payload, results.err)
+	case <-timer.C:
+	}
+
+	state, exists := p.singleUse[*rid]
+	if !exists {
+		t.Error("State not found in map.")
+	}
+	if !equalState(*expectedState, *state, t) {
+		t.Errorf("State in map is incorrect.\nexpected: %+v\nreceived: %+v",
+			*expectedState, *state)
+	}
+
+	hasQuit = atomic.CompareAndSwapInt32(quit, 0, 1)
+	if hasQuit {
+		t.Error("Quit atomic not called.")
+	}
+}
+
+// Happy path: state is removed before deletion can occur.
+func Test_pending_addState_StateRemoved(t *testing.T) {
+	p := newPending()
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	maxMsgs := uint8(6)
+	timeout := 5 * time.Millisecond
+	callback, callbackChan := createReplyComm()
+
+	_, _, err := p.addState(rid, dhKey, maxMsgs, callback, timeout)
+	if err != nil {
+		t.Errorf("addState() returned an error: %+v", err)
+	}
+
+	p.Lock()
+	delete(p.singleUse, *rid)
+	p.Unlock()
+
+	timer := time.NewTimer(timeout + 1*time.Millisecond)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback should not have been called.\npayload: %+v\nerror:   %+v",
+			results.payload, results.err)
+	case <-timer.C:
+	}
+}
+
+// Error path: timeout occurs and deletes the entry from the map.
+func Test_pending_addState_TimeoutError(t *testing.T) {
+	p := newPending()
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	maxMsgs := uint8(6)
+	timeout := 5 * time.Millisecond
+	callback, callbackChan := createReplyComm()
+
+	_, _, err := p.addState(rid, dhKey, maxMsgs, callback, timeout)
+	if err != nil {
+		t.Errorf("addState() returned an error: %+v", err)
+	}
+
+	expectedState := newState(dhKey, maxMsgs, callback)
+	p.Lock()
+	state, exists := p.singleUse[*rid]
+	p.Unlock()
+	if !exists {
+		t.Error("State not found in map.")
+	}
+	if !equalState(*expectedState, *state, t) {
+		t.Errorf("State in map is incorrect.\nexpected: %+v\nreceived: %+v",
+			*expectedState, *state)
+	}
+
+	timer := time.NewTimer(timeout * 2)
+
+	select {
+	case results := <-callbackChan:
+		state, exists = p.singleUse[*rid]
+		if exists {
+			t.Errorf("State found in map when it should have been deleted."+
+				"\nstate: %+v", state)
+		}
+		if results.payload != nil {
+			t.Errorf("Payload not nil on timeout.\npayload: %+v", results.payload)
+		}
+		if results.err == nil || !strings.Contains(results.err.Error(), "timed out") {
+			t.Errorf("Callback did not return a time out error on return: %+v", results.err)
+		}
+	case <-timer.C:
+		t.Error("Failed to time out.")
+	}
+}
+
+// Error path: state already exists.
+func Test_pending_addState_StateExistsError(t *testing.T) {
+	p := newPending()
+	rid := id.NewIdFromString("test RID", id.User, t)
+	dhKey := getGroup().NewInt(5)
+	maxMsgs := uint8(6)
+	timeout := 5 * time.Millisecond
+	callback, _ := createReplyComm()
+
+	quitChan, _, err := p.addState(rid, dhKey, maxMsgs, callback, timeout)
+	if err != nil {
+		t.Errorf("addState() returned an error: %+v", err)
+	}
+	quitChan <- struct{}{}
+
+	quitChan, _, err = p.addState(rid, dhKey, maxMsgs, callback, timeout)
+	if !check(err, "a state already exists in the map") {
+		t.Errorf("addState() did not return an error when the state already "+
+			"exists: %+v", err)
+	}
+}
+
+type replyCommData struct {
+	payload []byte
+	err     error
+}
+
+func createReplyComm() (func(payload []byte, err error), chan replyCommData) {
+	callbackChan := make(chan replyCommData)
+	callback := func(payload []byte, err error) {
+		callbackChan <- replyCommData{
+			payload: payload,
+			err:     err,
+		}
+	}
+	return callback, callbackChan
+}
+
+// equalState determines if the two states have equal values.
+func equalState(a, b state, t *testing.T) bool {
+	if a.dhKey.Cmp(b.dhKey) != 0 {
+		t.Errorf("DH Keys differ.\nexpected: %s\nreceived: %s",
+			a.dhKey.Text(10), b.dhKey.Text(10))
+		return false
+	}
+	if !reflect.DeepEqual(a.fpMap.fps, b.fpMap.fps) {
+		t.Errorf("Fingerprint maps differ.\nexpected: %+v\nreceived: %+v",
+			a.fpMap.fps, b.fpMap.fps)
+		return false
+	}
+	if !reflect.DeepEqual(b.c, b.c) {
+		t.Errorf("collators differ.\nexpected: %+v\nreceived: %+v",
+			a.c, b.c)
+		return false
+	}
+	if reflect.ValueOf(a.callback).Pointer() != reflect.ValueOf(b.callback).Pointer() {
+		t.Errorf("callbackFuncs differ.\nexpected: %p\nreceived: %p",
+			a.callback, b.callback)
+		return false
+	}
+	return true
+}
+
+// check returns true if the error is not nil and contains the substring.
+func check(err error, subStr string) bool {
+	return err != nil && strings.Contains(err.Error(), subStr)
+}
diff --git a/single/transmission.go b/single/transmission.go
new file mode 100644
index 0000000000000000000000000000000000000000..13abe66dbd753c5f1b2e6a3036c0c6efa8f0c9b4
--- /dev/null
+++ b/single/transmission.go
@@ -0,0 +1,288 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	contact2 "gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/interfaces/utility"
+	"gitlab.com/elixxir/client/storage/reception"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"io"
+	"sync/atomic"
+	"time"
+)
+
+// GetMaxTransmissionPayloadSize returns the maximum payload size for a
+// transmission message.
+func (m *Manager) GetMaxTransmissionPayloadSize() int {
+	// Generate empty messages to determine the available space for the payload
+	cmixPrimeSize := m.store.Cmix().GetGroup().GetP().ByteLen()
+	e2ePrimeSize := m.store.E2e().GetGroup().GetP().ByteLen()
+	cmixMsg := format.NewMessage(cmixPrimeSize)
+	transmitMsg := newTransmitMessage(cmixMsg.ContentsSize(), e2ePrimeSize)
+	msgPayload := newTransmitMessagePayload(transmitMsg.GetPayloadSize())
+
+	return msgPayload.GetMaxContentsSize()
+}
+
+// TransmitSingleUse creates a CMIX message, sends it, and waits for delivery.
+func (m *Manager) TransmitSingleUse(partner contact2.Contact, payload []byte,
+	tag string, maxMsgs uint8, callback ReplyComm, timeout time.Duration) error {
+
+	rngReader := m.rng.GetStream()
+	defer rngReader.Close()
+
+	return m.transmitSingleUse(partner, payload, tag, maxMsgs, rngReader,
+		callback, timeout, m.net.GetInstance().GetRoundEvents())
+}
+
+// roundEvents interface allows custom round events to be passed in for testing.
+type roundEvents interface {
+	AddRoundEventChan(id.Round, chan ds.EventReturn, time.Duration,
+		...states.Round) *ds.EventCallback
+}
+
+// transmitSingleUse has the fields passed in for easier testing.
+func (m *Manager) transmitSingleUse(partner contact2.Contact, payload []byte,
+	tag string, MaxMsgs uint8, rng io.Reader, callback ReplyComm, timeout time.Duration, roundEvents roundEvents) error {
+
+	// Get ephemeral ID address size; this will block until the client knows the
+	// address size if it is currently unknown
+	if m.store.Reception().IsIdSizeDefault() {
+		m.store.Reception().WaitForIdSizeUpdate()
+	}
+	addressSize := m.store.Reception().GetIDSize()
+
+	// Create new CMIX message containing the transmission payload
+	cmixMsg, dhKey, rid, ephID, err := m.makeTransmitCmixMessage(partner,
+		payload, tag, MaxMsgs, addressSize, timeout, time.Now(), rng)
+	if err != nil {
+		return errors.Errorf("failed to create new CMIX message: %+v", err)
+	}
+
+	jww.DEBUG.Printf("Created single-use transmission CMIX message with new ID "+
+		"%s and ephemeral ID %d", rid, ephID.Int64())
+
+	timeStart := time.Now()
+
+	// Add message state to map
+	quitChan, quit, err := m.p.addState(rid, dhKey, MaxMsgs, callback, timeout)
+	if err != nil {
+		return errors.Errorf("failed to add pending state: %+v", err)
+	}
+
+	// Add identity for newly generated ID
+	err = m.reception.AddIdentity(reception.Identity{
+		EphId:       ephID,
+		Source:      rid,
+		End:         timeStart.Add(2 * timeout),
+		ExtraChecks: 10,
+		StartValid:  timeStart.Add(-2 * timeout),
+		EndValid:    timeStart.Add(2 * timeout),
+		RequestMask: 48*time.Hour - timeout,
+		Ephemeral:   true,
+	})
+	if err != nil {
+		errorString := fmt.Sprintf("failed to add new identity to "+
+			"reception storage for single-use: %+v", err)
+		jww.ERROR.Print(errorString)
+
+		// Exit the state timeout handler, delete the state from map, and
+		// return an error on the callback
+		quitChan <- struct{}{}
+		m.p.Lock()
+		delete(m.p.singleUse, *rid)
+		m.p.Unlock()
+		go callback(nil, errors.New(errorString))
+	}
+
+	go func() {
+		// Send Message
+		jww.DEBUG.Printf("Sending single-use transmission CMIX message to %s.", partner.ID)
+		round, _, err := m.net.SendCMIX(cmixMsg, partner.ID, params.GetDefaultCMIX())
+		if err != nil {
+			errorString := fmt.Sprintf("failed to send single-use transmission "+
+				"CMIX message: %+v", err)
+			jww.ERROR.Print(errorString)
+
+			// Exit the state timeout handler, delete the state from map, and
+			// return an error on the callback
+			quitChan <- struct{}{}
+			m.p.Lock()
+			delete(m.p.singleUse, *rid)
+			m.p.Unlock()
+			go callback(nil, errors.New(errorString))
+		}
+
+		// Check if the state timeout handler has quit
+		if atomic.LoadInt32(quit) == 1 {
+			jww.ERROR.Print("Stopping to send single-use transmission CMIX " +
+				"message because the timeout handler quit.")
+			return
+		}
+
+		// Update the timeout for the elapsed time
+		roundEventTimeout := timeout - time.Now().Sub(timeStart) - time.Millisecond
+
+		// Check message delivery
+		sendResults := make(chan ds.EventReturn, 1)
+		roundEvents.AddRoundEventChan(round, sendResults, roundEventTimeout,
+			states.COMPLETED, states.FAILED)
+
+		jww.DEBUG.Printf("Sent single-use transmission CMIX message to %s and "+
+			"ephemeral ID %d on round %d.", partner.ID, ephID.Int64(), round)
+
+		// Wait until the result tracking responds
+		success, numRoundFail, numTimeOut := utility.TrackResults(sendResults, 1)
+		if !success {
+			errorString := fmt.Sprintf("failed to send single-use transmission "+
+				"message: %d round failures, %d round event time outs.",
+				numRoundFail, numTimeOut)
+			jww.ERROR.Print(errorString)
+
+			// Exit the state timeout handler, delete the state from map, and
+			// return an error on the callback
+			quitChan <- struct{}{}
+			m.p.Lock()
+			delete(m.p.singleUse, *rid)
+			m.p.Unlock()
+			go callback(nil, errors.New(errorString))
+		}
+		jww.DEBUG.Print("Tracked single-use transmission message round.")
+	}()
+
+	return nil
+}
+
+// makeTransmitCmixMessage generates a CMIX message containing the transmission message,
+// which contains the encrypted payload.
+func (m *Manager) makeTransmitCmixMessage(partner contact2.Contact,
+	payload []byte, tag string, maxMsgs uint8, addressSize uint,
+	timeout time.Duration, timeNow time.Time, rng io.Reader) (format.Message,
+	*cyclic.Int, *id.ID, ephemeral.Id, error) {
+	e2eGrp := m.store.E2e().GetGroup()
+
+	// Generate internal payloads based off key size to determine if the passed
+	// in payload is too large to fit in the available contents
+	cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen())
+	transmitMsg := newTransmitMessage(cmixMsg.ContentsSize(), e2eGrp.GetP().ByteLen())
+	msgPayload := newTransmitMessagePayload(transmitMsg.GetPayloadSize())
+
+	if msgPayload.GetMaxContentsSize() < len(payload) {
+		return format.Message{}, nil, nil, ephemeral.Id{},
+			errors.Errorf("length of provided payload (%d) too long for message "+
+				"payload capacity (%d).", len(payload), len(msgPayload.contents))
+	}
+
+	// Generate DH key and public key
+	dhKey, publicKey, err := generateDhKeys(e2eGrp, partner.DhPubKey, rng)
+	if err != nil {
+		return format.Message{}, nil, nil, ephemeral.Id{}, err
+	}
+
+	// Compose payload
+	msgPayload.SetTagFP(singleUse.NewTagFP(tag))
+	msgPayload.SetMaxParts(maxMsgs)
+	msgPayload.SetContents(payload)
+
+	// Generate new user ID and ephemeral ID
+	rid, ephID, err := makeIDs(&msgPayload, publicKey, addressSize, timeout,
+		timeNow, rng)
+	if err != nil {
+		return format.Message{}, nil, nil, ephemeral.Id{},
+			errors.Errorf("failed to generate IDs: %+v", err)
+	}
+
+	// Encrypt payload
+	fp := singleUse.NewTransmitFingerprint(partner.DhPubKey)
+	key := singleUse.NewTransmitKey(dhKey)
+	encryptedPayload := auth.Crypt(key, fp[:24], msgPayload.Marshal())
+
+	// Generate CMIX message MAC
+	mac := singleUse.MakeMAC(key, encryptedPayload)
+
+	// Compose transmission message
+	transmitMsg.SetPubKey(publicKey)
+	transmitMsg.SetPayload(encryptedPayload)
+
+	// Compose CMIX message contents
+	cmixMsg.SetContents(transmitMsg.Marshal())
+	cmixMsg.SetKeyFP(fp)
+	cmixMsg.SetMac(mac)
+
+	return cmixMsg, dhKey, rid, ephID, nil
+}
+
+// generateDhKeys generates a new public key and DH key.
+func generateDhKeys(grp *cyclic.Group, dhPubKey *cyclic.Int,
+	rng io.Reader) (*cyclic.Int, *cyclic.Int, error) {
+	// Generate private key
+	privKeyBytes, err := csprng.GenerateInGroup(grp.GetP().Bytes(),
+		grp.GetP().ByteLen(), rng)
+	if err != nil {
+		return nil, nil, errors.Errorf("failed to generate key in group: %+v",
+			err)
+	}
+	privKey := grp.NewIntFromBytes(privKeyBytes)
+
+	// Generate public key and DH key
+	publicKey := grp.ExpG(privKey, grp.NewInt(1))
+	dhKey := grp.Exp(dhPubKey, privKey, grp.NewInt(1))
+
+	return dhKey, publicKey, nil
+}
+
+// makeIDs generates a new user ID and ephemeral ID with a start and end within
+// the given timout. The ID is generated from the unencrypted msg payload, which
+// contains a nonce. If the generated ephemeral ID has a window that is not
+// within +/- the given 2*timeout from now, then the IDs are generated again
+// using a new nonce.
+func makeIDs(msg *transmitMessagePayload, publicKey *cyclic.Int, addressSize uint,
+	timeout time.Duration, timeNow time.Time, rng io.Reader) (*id.ID, ephemeral.Id, error) {
+	var rid *id.ID
+	var ephID ephemeral.Id
+
+	// Generate acceptable window for the ephemeral ID to exist in
+	windowStart, windowEnd := timeNow.Add(-2*timeout), timeNow.Add(2*timeout)
+	start, end := timeNow, timeNow
+
+	// Loop until the ephemeral ID's start and end are within bounds
+	for windowStart.Before(start) || windowEnd.After(end) {
+		// Generate new nonce
+		err := msg.SetNonce(rng)
+		if err != nil {
+			return nil, ephemeral.Id{},
+				errors.Errorf("failed to generate nonce: %+v", err)
+		}
+
+		// Generate ID from unencrypted payload
+		rid = msg.GetRID(publicKey)
+
+		// Generate the ephemeral ID
+		ephID, start, end, err = ephemeral.GetId(rid, addressSize, timeNow.UnixNano())
+		if err != nil {
+			return nil, ephemeral.Id{}, errors.Errorf("failed to generate "+
+				"ephemeral ID from newly generated ID: %+v", err)
+		}
+		jww.DEBUG.Printf("ephemeral.GetId(%s, %d, %d) = %d", rid, addressSize, timeNow.UnixNano(), ephID.Int64())
+	}
+
+	return rid, ephID, nil
+}
diff --git a/single/transmission_test.go b/single/transmission_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe6df60ba188ef5abe88e06ab4434c0d289618b5
--- /dev/null
+++ b/single/transmission_test.go
@@ -0,0 +1,446 @@
+package single
+
+import (
+	"bytes"
+	contact2 "gitlab.com/elixxir/client/interfaces/contact"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	ds "gitlab.com/elixxir/comms/network/dataStructures"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"math/rand"
+	"reflect"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_GetMaxTransmissionPayloadSize(t *testing.T) {
+	m := newTestManager(0, false, t)
+	cmixPrimeSize := m.store.Cmix().GetGroup().GetP().ByteLen()
+	e2ePrimeSize := m.store.E2e().GetGroup().GetP().ByteLen()
+	expectedSize := 2*cmixPrimeSize - e2ePrimeSize - format.KeyFPLen - format.MacLen - format.RecipientIDLen - transmitPlMinSize
+	testSize := m.GetMaxTransmissionPayloadSize()
+
+	if expectedSize != testSize {
+		t.Errorf("GetMaxTransmissionPayloadSize() failed to return the expected size."+
+			"\nexpected: %d\nreceived: %d", expectedSize, testSize)
+	}
+}
+
+// Happy path.
+func TestManager_transmitSingleUse(t *testing.T) {
+	m := newTestManager(0, false, t)
+	prng := rand.New(rand.NewSource(42))
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("Contact ID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
+	}
+	payload := make([]byte, 95)
+	rand.New(rand.NewSource(42)).Read(payload)
+	tag := "testTag"
+	maxMsgs := uint8(8)
+	callback, callbackChan := createReplyComm()
+	timeout := 15 * time.Millisecond
+
+	err := m.transmitSingleUse(partner, payload, tag, maxMsgs, prng,
+		callback, timeout, newTestRoundEvents(false))
+	if err != nil {
+		t.Errorf("transmitSingleUse() returned an error: %+v", err)
+	}
+
+	for _, state := range m.p.singleUse {
+		state.quitChan <- struct{}{}
+	}
+
+	expectedMsg, _, _, _, err := m.makeTransmitCmixMessage(partner, payload,
+		tag, maxMsgs, 32, 30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to make expected message: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).GetMsg(0)) {
+		t.Errorf("transmitSingleUse() failed to send the correct CMIX message."+
+			"\nexpected: %+v\nreceived: %+v",
+			expectedMsg, m.net.(*testNetworkManager).GetMsg(0))
+	}
+
+	timer := time.NewTimer(timeout * 2)
+
+	select {
+	case results := <-callbackChan:
+		t.Errorf("Callback called when the thread should have quit."+
+			"\npayload: %+v\nerror:   %+v", results.payload, results.err)
+	case <-timer.C:
+	}
+}
+
+// Error path: function quits early if the timoutHandler quit.
+func TestManager_transmitSingleUse_QuitChanError(t *testing.T) {
+	m := newTestManager(10*time.Millisecond, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("Contact ID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
+	}
+	callback, callbackChan := createReplyComm()
+	timeout := 15 * time.Millisecond
+
+	err := m.transmitSingleUse(partner, []byte{}, "testTag", 9,
+		rand.New(rand.NewSource(42)), callback, timeout, newTestRoundEvents(false))
+	if err != nil {
+		t.Errorf("transmitSingleUse() returned an error: %+v", err)
+	}
+
+	for _, state := range m.p.singleUse {
+		state.quitChan <- struct{}{}
+	}
+
+	timer := time.NewTimer(2 * timeout)
+
+	select {
+	case results := <-callbackChan:
+		if results.payload != nil || results.err != nil {
+			t.Errorf("Callback called when the timeout thread should have quit."+
+				"\npayload: %+v\nerror:   %+v", results.payload, results.err)
+		}
+	case <-timer.C:
+	}
+}
+
+// Error path: fails to add a new identity.
+func TestManager_transmitSingleUse_AddIdentityError(t *testing.T) {
+	timeout := 15 * time.Millisecond
+	m := newTestManager(timeout, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("Contact ID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
+	}
+	callback, callbackChan := createReplyComm()
+
+	err := m.transmitSingleUse(partner, []byte{}, "testTag", 9,
+		rand.New(rand.NewSource(42)), callback, timeout, newTestRoundEvents(false))
+	if err != nil {
+		t.Errorf("transmitSingleUse() returned an error: %+v", err)
+	}
+
+	for _, state := range m.p.singleUse {
+		state.quitChan <- struct{}{}
+	}
+
+	timer := time.NewTimer(2 * timeout)
+
+	select {
+	case results := <-callbackChan:
+		if results.payload != nil || !check(results.err, "Failed to add new identity") {
+			t.Errorf("Callback did not return the correct error when the "+
+				"routine quit early.\npayload: %+v\nerror:   %+v",
+				results.payload, results.err)
+		}
+	case <-timer.C:
+	}
+}
+
+// Error path: SendCMIX fails to send message.
+func TestManager_transmitSingleUse_SendCMIXError(t *testing.T) {
+	m := newTestManager(0, true, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("Contact ID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
+	}
+	callback, callbackChan := createReplyComm()
+	timeout := 15 * time.Millisecond
+
+	err := m.transmitSingleUse(partner, []byte{}, "testTag", 9,
+		rand.New(rand.NewSource(42)), callback, timeout, newTestRoundEvents(false))
+	if err != nil {
+		t.Errorf("transmitSingleUse() returned an error: %+v", err)
+	}
+
+	timer := time.NewTimer(timeout * 2)
+
+	select {
+	case results := <-callbackChan:
+		if results.payload != nil || !check(results.err, "failed to send single-use transmission CMIX message") {
+			t.Errorf("Callback did not return the correct error when the "+
+				"routine quit early.\npayload: %+v\nerror:   %+v",
+				results.payload, results.err)
+		}
+	case <-timer.C:
+	}
+}
+
+// Error path: failed to create CMIX message because the payload is too large.
+func TestManager_transmitSingleUse_MakeTransmitCmixMessageError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	prng := rand.New(rand.NewSource(42))
+	payload := make([]byte, m.store.Cmix().GetGroup().GetP().ByteLen())
+
+	err := m.transmitSingleUse(contact2.Contact{}, payload, "", 0, prng, nil, 0, nil)
+	if err == nil {
+		t.Error("transmitSingleUse() did not return an error when the payload " +
+			"is too large.")
+	}
+}
+
+// Error path: failed to add pending state because is already exists.
+func TestManager_transmitSingleUse_AddStateError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("Contact ID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
+	}
+	payload := make([]byte, 95)
+	rand.New(rand.NewSource(42)).Read(payload)
+	tag := "testTag"
+	maxMsgs := uint8(8)
+	callback, _ := createReplyComm()
+	timeout := 15 * time.Millisecond
+
+	// Create new CMIX and add a state
+	_, dhKey, rid, _, err := m.makeTransmitCmixMessage(partner, payload, tag,
+		maxMsgs, 32, 30*time.Second, time.Now(), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to create new CMIX message: %+v", err)
+	}
+	m.p.singleUse[*rid] = newState(dhKey, maxMsgs, nil)
+
+	err = m.transmitSingleUse(partner, payload, tag, maxMsgs,
+		rand.New(rand.NewSource(42)), callback, timeout, nil)
+	if !check(err, "failed to add pending state") {
+		t.Errorf("transmitSingleUse() failed to error when on adding state "+
+			"when the state already exists: %+v", err)
+	}
+}
+
+// Error path: timeout occurs on tracking results of round.
+func TestManager_transmitSingleUse_RoundTimeoutError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	prng := rand.New(rand.NewSource(42))
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("Contact ID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
+	}
+	payload := make([]byte, 95)
+	rand.New(rand.NewSource(42)).Read(payload)
+	callback, callbackChan := createReplyComm()
+	timeout := 15 * time.Millisecond
+
+	err := m.transmitSingleUse(partner, payload, "testTag", 8, prng, callback,
+		timeout, newTestRoundEvents(true))
+	if err != nil {
+		t.Errorf("transmitSingleUse() returned an error: %+v", err)
+	}
+
+	timer := time.NewTimer(timeout * 2)
+
+	select {
+	case results := <-callbackChan:
+		if results.payload != nil || !check(results.err, "round failures") {
+			t.Errorf("Callback did not return the correct error when it "+
+				"should have timed out.\npayload: %+v\nerror:   %+v",
+				results.payload, results.err)
+		}
+	case <-timer.C:
+	}
+}
+
+// Happy path
+func TestManager_makeTransmitCmixMessage(t *testing.T) {
+	m := newTestManager(0, false, t)
+	prng := rand.New(rand.NewSource(42))
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(42),
+	}
+	tag := "Test tag"
+	payload := make([]byte, 132)
+	rand.New(rand.NewSource(42)).Read(payload)
+	maxMsgs := uint8(8)
+	timeNow := time.Now()
+
+	msg, dhKey, rid, _, err := m.makeTransmitCmixMessage(partner, payload,
+		tag, maxMsgs, 32, 30*time.Second, timeNow, prng)
+
+	if err != nil {
+		t.Errorf("makeTransmitCmixMessage() produced an error: %+v", err)
+	}
+
+	fp := singleUse.NewTransmitFingerprint(partner.DhPubKey)
+	key := singleUse.NewTransmitKey(dhKey)
+
+	encPayload, err := unmarshalTransmitMessage(msg.GetContents(),
+		m.store.E2e().GetGroup().GetP().ByteLen())
+	if err != nil {
+		t.Errorf("Failed to unmarshal contents: %+v", err)
+	}
+
+	decryptedPayload, err := unmarshalTransmitMessagePayload(auth.Crypt(key,
+		fp[:24], encPayload.GetPayload()))
+	if err != nil {
+		t.Errorf("Failed to unmarshal payload: %+v", err)
+	}
+
+	if !bytes.Equal(payload, decryptedPayload.GetContents()) {
+		t.Errorf("Failed to decrypt payload.\nexpected: %+v\nreceived: %+v",
+			payload, decryptedPayload.GetContents())
+	}
+
+	if !singleUse.VerifyMAC(key, encPayload.GetPayload(), msg.GetMac()) {
+		t.Error("Failed to verify the message MAC.")
+	}
+
+	if fp != msg.GetKeyFP() {
+		t.Errorf("Failed to verify the CMIX message fingperprint."+
+			"\nexpected: %s\nreceived: %s", fp, msg.GetKeyFP())
+	}
+
+	if maxMsgs != decryptedPayload.GetMaxParts() {
+		t.Errorf("Incorrect maxMsgs.\nexpected: %d\nreceived: %d",
+			maxMsgs, decryptedPayload.GetMaxParts())
+	}
+
+	expectedTagFP := singleUse.NewTagFP(tag)
+	if decryptedPayload.GetTagFP() != expectedTagFP {
+		t.Errorf("Incorrect TagFP.\nexpected: %s\nreceived: %s",
+			expectedTagFP, decryptedPayload.GetTagFP())
+	}
+
+	if !rid.Cmp(decryptedPayload.GetRID(encPayload.GetPubKey(m.store.E2e().GetGroup()))) {
+		t.Errorf("Returned incorrect recipient ID.\nexpected: %s\nreceived: %s",
+			decryptedPayload.GetRID(encPayload.GetPubKey(m.store.E2e().GetGroup())), rid)
+	}
+}
+
+// Error path: supplied payload to large for message.
+func TestManager_makeTransmitCmixMessage_PayloadTooLargeError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	prng := rand.New(rand.NewSource(42))
+	payload := make([]byte, 1000)
+	rand.New(rand.NewSource(42)).Read(payload)
+
+	_, _, _, _, err := m.makeTransmitCmixMessage(contact2.Contact{}, payload, "", 8, 32,
+		30*time.Second, time.Now(), prng)
+
+	if !check(err, "too long for message payload capacity") {
+		t.Errorf("makeTransmitCmixMessage() failed to error when the payload is too "+
+			"large: %+v", err)
+	}
+}
+
+// Error path: key generation fails.
+func TestManager_makeTransmitCmixMessage_KeyGenerationError(t *testing.T) {
+	m := newTestManager(0, false, t)
+	prng := strings.NewReader("a")
+	partner := contact2.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: m.store.E2e().GetGroup().NewInt(42),
+	}
+
+	_, _, _, _, err := m.makeTransmitCmixMessage(partner, nil, "", 8, 32,
+		30*time.Second, time.Now(), prng)
+
+	if !check(err, "failed to generate key in group") {
+		t.Errorf("makeTransmitCmixMessage() failed to error when key "+
+			"generation failed: %+v", err)
+	}
+}
+
+// Happy path: test for consistency.
+func Test_makeIDs_Consistency(t *testing.T) {
+	m := newTestManager(0, false, t)
+	cmixMsg := format.NewMessage(m.store.Cmix().GetGroup().GetP().ByteLen())
+	transmitMsg := newTransmitMessage(cmixMsg.ContentsSize(), m.store.E2e().GetGroup().GetP().ByteLen())
+	msgPayload := newTransmitMessagePayload(transmitMsg.GetPayloadSize())
+	msgPayload.SetTagFP(singleUse.NewTagFP("tag"))
+	msgPayload.SetMaxParts(8)
+	msgPayload.SetContents([]byte("payload"))
+	_, publicKey, err := generateDhKeys(m.store.E2e().GetGroup(),
+		m.store.E2e().GetGroup().NewInt(42), rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to generate public key: %+v", err)
+	}
+	addressSize := uint(32)
+
+	expectedPayload, err := unmarshalTransmitMessagePayload(msgPayload.Marshal())
+	if err != nil {
+		t.Fatalf("Failed to copy payload: %+v", err)
+	}
+
+	err = expectedPayload.SetNonce(rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Fatalf("Failed to set nonce: %+v", err)
+	}
+
+	timeNow := time.Now()
+
+	rid, ephID, err := makeIDs(&msgPayload, publicKey, addressSize,
+		30*time.Second, timeNow, rand.New(rand.NewSource(42)))
+	if err != nil {
+		t.Errorf("makeIDs() returned an error: %+v", err)
+	}
+
+	if expectedPayload.GetNonce() != msgPayload.GetNonce() {
+		t.Errorf("makeIDs() failed to set the expected nonce."+
+			"\nexpected: %d\nreceived: %d", expectedPayload.GetNonce(), msgPayload.GetNonce())
+	}
+
+	if !expectedPayload.GetRID(publicKey).Cmp(rid) {
+		t.Errorf("makeIDs() did not return the expected ID."+
+			"\nexpected: %s\nreceived: %s", expectedPayload.GetRID(publicKey), rid)
+	}
+
+	expectedEphID, _, _, err := ephemeral.GetId(expectedPayload.GetRID(publicKey),
+		addressSize, timeNow.UnixNano())
+	if err != nil {
+		t.Fatalf("Failed to generate expected ephemeral ID: %+v", err)
+	}
+
+	if expectedEphID != ephID {
+		t.Errorf("makeIDs() did not return the expected ephemeral ID."+
+			"\nexpected: %d\nreceived: %d", expectedEphID.Int64(), ephID.Int64())
+	}
+}
+
+// Error path: failed to generate nonce.
+func Test_makeIDs_NonceError(t *testing.T) {
+	msgPayload := newTransmitMessagePayload(transmitPlMinSize)
+
+	_, _, err := makeIDs(&msgPayload, &cyclic.Int{}, 32, 30*time.Second,
+		time.Now(), strings.NewReader(""))
+	if !check(err, "failed to generate nonce") {
+		t.Errorf("makeIDs() did not return an error when failing to make nonce: %+v", err)
+	}
+}
+
+type testRoundEvents struct {
+	callbacks    map[id.Round][states.NUM_STATES]map[*ds.EventCallback]*ds.EventCallback
+	timeoutError bool
+	mux          sync.RWMutex
+}
+
+func newTestRoundEvents(timeoutError bool) *testRoundEvents {
+	return &testRoundEvents{
+		callbacks:    make(map[id.Round][states.NUM_STATES]map[*ds.EventCallback]*ds.EventCallback),
+		timeoutError: timeoutError,
+	}
+}
+
+func (r *testRoundEvents) AddRoundEventChan(_ id.Round,
+	eventChan chan ds.EventReturn, _ time.Duration, _ ...states.Round) *ds.EventCallback {
+
+	eventChan <- struct {
+		RoundInfo *pb.RoundInfo
+		TimedOut  bool
+	}{
+		RoundInfo: &pb.RoundInfo{State: uint32(states.COMPLETED)},
+		TimedOut:  r.timeoutError,
+	}
+
+	return nil
+}
diff --git a/single/transmitMessage.go b/single/transmitMessage.go
new file mode 100644
index 0000000000000000000000000000000000000000..768c47e2697dfe38c731ae0fbd2ffb7759442755
--- /dev/null
+++ b/single/transmitMessage.go
@@ -0,0 +1,248 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"encoding/binary"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/xx_network/primitives/id"
+	"io"
+)
+
+/*
++-----------------------------------------------------------------+
+|                      CMIX Message Contents                      |
++------------+----------------------------------------------------+
+|   pubKey   |          payload (transmitMessagePayload)          |
+| pubKeySize |          externalPayloadSize - pubKeySize          |
++------------+----------+---------+----------+---------+----------+
+             |  Tag FP  |  nonce  | maxParts |  size   | contents |
+             | 16 bytes | 8 bytes |  1 byte  | 2 bytes | variable |
+             +----------+---------+----------+---------+----------+
+*/
+
+type transmitMessage struct {
+	data    []byte // Serial of all contents
+	pubKey  []byte
+	payload []byte // The encrypted payload containing reception ID and contents
+}
+
+// newTransmitMessage generates a new empty message for transmission that is the
+// size of the specified external payload.
+func newTransmitMessage(externalPayloadSize, pubKeySize int) transmitMessage {
+	if externalPayloadSize < pubKeySize {
+		jww.FATAL.Panicf("Payload size of single-use transmission message "+
+			"(%d) too small to contain the public key (%d).",
+			externalPayloadSize, pubKeySize)
+	}
+
+	return mapTransmitMessage(make([]byte, externalPayloadSize), pubKeySize)
+}
+
+// mapTransmitMessage builds a message mapped to the passed in data. It is
+// mapped by reference; a copy is not made.
+func mapTransmitMessage(data []byte, pubKeySize int) transmitMessage {
+	return transmitMessage{
+		data:    data,
+		pubKey:  data[:pubKeySize],
+		payload: data[pubKeySize:],
+	}
+}
+
+// unmarshalTransmitMessage unmarshalls a byte slice into a transmitMessage. An
+// error is returned if the slice is not large enough for the public key size.
+func unmarshalTransmitMessage(b []byte, pubKeySize int) (transmitMessage, error) {
+	if len(b) < pubKeySize {
+		return transmitMessage{}, errors.Errorf("Length of marshaled bytes "+
+			"(%d) too small to contain public key (%d).", len(b), pubKeySize)
+	}
+
+	return mapTransmitMessage(b, pubKeySize), nil
+}
+
+// Marshal returns the serialised data of a transmitMessage.
+func (m transmitMessage) Marshal() []byte {
+	return m.data
+}
+
+// GetPubKey returns the public key that is part of the given group.
+func (m transmitMessage) GetPubKey(grp *cyclic.Group) *cyclic.Int {
+	return grp.NewIntFromBytes(m.pubKey)
+}
+
+// GetPubKeySize returns the length of the public key.
+func (m transmitMessage) GetPubKeySize() int {
+	return len(m.pubKey)
+}
+
+// SetPubKey saves the public key to the message as bytes.
+func (m transmitMessage) SetPubKey(pubKey *cyclic.Int) {
+	copy(m.pubKey, pubKey.LeftpadBytes(uint64(len(m.pubKey))))
+}
+
+// GetPayload returns the encrypted payload of the message.
+func (m transmitMessage) GetPayload() []byte {
+	return m.payload
+}
+
+// GetPayloadSize returns the length of the encrypted payload.
+func (m transmitMessage) GetPayloadSize() int {
+	return len(m.payload)
+}
+
+// SetPayload saves the supplied bytes as the payload of the message, if the
+// size is correct.
+func (m transmitMessage) SetPayload(b []byte) {
+	if len(b) != len(m.payload) {
+		jww.FATAL.Panicf("Size of payload of single-use transmission message "+
+			"(%d) is not the same as the size of the supplied payload (%d).",
+			len(m.payload), len(b))
+	}
+
+	copy(m.payload, b)
+}
+
+const (
+	tagFPSize         = singleUse.TagFpSize
+	nonceSize         = 8
+	maxPartsSize      = 1
+	sizeSize          = 2
+	transmitPlMinSize = tagFPSize + nonceSize + maxPartsSize + sizeSize
+)
+
+// transmitMessagePayload is the structure of transmitMessage's payload.
+type transmitMessagePayload struct {
+	data     []byte // Serial of all contents
+	tagFP    []byte // Tag fingerprint identifies the type of message
+	nonce    []byte
+	maxParts []byte // Max number of messages expected in response
+	size     []byte // Size of the contents
+	contents []byte
+}
+
+// newTransmitMessage generates a new empty message for transmission that is the
+// size of the specified payload, which should match the size of the payload in
+// the corresponding transmitMessage.
+func newTransmitMessagePayload(payloadSize int) transmitMessagePayload {
+	if payloadSize < transmitPlMinSize {
+		jww.FATAL.Panicf("Size of single-use transmission message payload "+
+			"(%d) too small to contain the necessary data (%d).",
+			payloadSize, transmitPlMinSize)
+	}
+
+	// Map fields to data
+	mp := mapTransmitMessagePayload(make([]byte, payloadSize))
+
+	return mp
+}
+
+// mapTransmitMessagePayload builds a message payload mapped to the passed in
+// data. It is mapped by reference; a copy is not made.
+func mapTransmitMessagePayload(data []byte) transmitMessagePayload {
+	mp := transmitMessagePayload{
+		data:     data,
+		tagFP:    data[:tagFPSize],
+		nonce:    data[tagFPSize : tagFPSize+nonceSize],
+		maxParts: data[tagFPSize+nonceSize : tagFPSize+nonceSize+maxPartsSize],
+		size:     data[tagFPSize+nonceSize+maxPartsSize : transmitPlMinSize],
+		contents: data[transmitPlMinSize:],
+	}
+
+	return mp
+}
+
+// unmarshalTransmitMessagePayload unmarshalls a byte slice into a
+// transmitMessagePayload. An error is returned if the slice is not large enough
+// for the reception ID and message count.
+func unmarshalTransmitMessagePayload(b []byte) (transmitMessagePayload, error) {
+	if len(b) < transmitPlMinSize {
+		return transmitMessagePayload{}, errors.Errorf("Length of marshaled "+
+			"bytes(%d) too small to contain the necessary data (%d).",
+			len(b), transmitPlMinSize)
+	}
+
+	return mapTransmitMessagePayload(b), nil
+}
+
+// Marshal returns the serialised data of a transmitMessagePayload.
+func (mp transmitMessagePayload) Marshal() []byte {
+	return mp.data
+}
+
+// GetRID generates the reception ID from the bytes of the payload.
+func (mp transmitMessagePayload) GetRID(pubKey *cyclic.Int) *id.ID {
+	return singleUse.NewRecipientID(pubKey, mp.Marshal())
+}
+
+// GetTagFP returns the tag fingerprint.
+func (mp transmitMessagePayload) GetTagFP() singleUse.TagFP {
+	return singleUse.UnmarshalTagFP(mp.tagFP)
+}
+
+// SetTagFP sets the tag fingerprint.
+func (mp transmitMessagePayload) SetTagFP(tagFP singleUse.TagFP) {
+	copy(mp.tagFP, tagFP.Bytes())
+}
+
+// GetNonce returns the nonce as a uint64.
+func (mp transmitMessagePayload) GetNonce() uint64 {
+	return binary.BigEndian.Uint64(mp.nonce)
+}
+
+// SetNonce generates a random nonce from the RNG. An error is returned if the
+// reader fails.
+func (mp transmitMessagePayload) SetNonce(rng io.Reader) error {
+	if _, err := rng.Read(mp.nonce); err != nil {
+		return errors.Errorf("failed to generate nonce: %+v", err)
+	}
+
+	return nil
+}
+
+// GetMaxParts returns the number of messages expected in response.
+func (mp transmitMessagePayload) GetMaxParts() uint8 {
+	return mp.maxParts[0]
+}
+
+// SetMaxParts sets the number of expected messages.
+func (mp transmitMessagePayload) SetMaxParts(num uint8) {
+	copy(mp.maxParts, []byte{num})
+}
+
+// GetContents returns the payload's contents.
+func (mp transmitMessagePayload) GetContents() []byte {
+	return mp.contents[:binary.BigEndian.Uint16(mp.size)]
+}
+
+// GetContentsSize returns the length of payload's contents.
+func (mp transmitMessagePayload) GetContentsSize() int {
+	return int(binary.BigEndian.Uint16(mp.size))
+}
+
+// GetMaxContentsSize returns the max capacity of the contents.
+func (mp transmitMessagePayload) GetMaxContentsSize() int {
+	return len(mp.contents)
+}
+
+// SetContents saves the contents to the payload, if the size is correct. Does
+// not zero out previous content.
+func (mp transmitMessagePayload) SetContents(contents []byte) {
+	if len(contents) > len(mp.contents) {
+		jww.FATAL.Panicf("Failed to set contents of single-use transmission "+
+			"message: max size of message content (%d) is smaller than the "+
+			"size of the supplied contents (%d).",
+			len(mp.contents), len(contents))
+	}
+
+	binary.BigEndian.PutUint16(mp.size, uint16(len(contents)))
+
+	copy(mp.contents, contents)
+}
diff --git a/single/transmitMessage_test.go b/single/transmitMessage_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..833fd5f4b91530547684e8a4f117ab22f628da43
--- /dev/null
+++ b/single/transmitMessage_test.go
@@ -0,0 +1,397 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	"encoding/binary"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/large"
+	"math/rand"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Happy path.
+func Test_newTransmitMessage(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	externalPayloadSize := prng.Intn(2000)
+	pubKeySize := prng.Intn(externalPayloadSize)
+	expected := transmitMessage{
+		data:    make([]byte, externalPayloadSize),
+		pubKey:  make([]byte, pubKeySize),
+		payload: make([]byte, externalPayloadSize-pubKeySize),
+	}
+
+	m := newTransmitMessage(externalPayloadSize, pubKeySize)
+
+	if !reflect.DeepEqual(expected, m) {
+		t.Errorf("newTransmitMessage() did not produce the expected transmitMessage."+
+			"\nexpected: %#v\nreceived: %#v", expected, m)
+	}
+}
+
+// Error path: public key size is larger than external payload size.
+func Test_newTransmitMessage_PubKeySizeError(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil || !strings.Contains(r.(string), "Payload size") {
+			t.Error("newTransmitMessage() did not panic when the size of " +
+				"the payload is smaller than the size of the public key.")
+		}
+	}()
+
+	_ = newTransmitMessage(5, 10)
+}
+
+// Happy path.
+func Test_mapTransmitMessage(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	externalPayloadSize := prng.Intn(2000)
+	pubKeySize := prng.Intn(externalPayloadSize)
+	pubKey := make([]byte, pubKeySize)
+	prng.Read(pubKey)
+	payload := make([]byte, externalPayloadSize-pubKeySize)
+	prng.Read(payload)
+	var data []byte
+	data = append(data, pubKey...)
+	data = append(data, payload...)
+	m := mapTransmitMessage(data, pubKeySize)
+
+	if !bytes.Equal(data, m.data) {
+		t.Errorf("mapTransmitMessage() failed to map the correct bytes for data."+
+			"\nexpected: %+v\nreceived: %+v", data, m.data)
+	}
+
+	if !bytes.Equal(pubKey, m.pubKey) {
+		t.Errorf("mapTransmitMessage() failed to map the correct bytes for pubKey."+
+			"\nexpected: %+v\nreceived: %+v", pubKey, m.pubKey)
+	}
+
+	if !bytes.Equal(payload, m.payload) {
+		t.Errorf("mapTransmitMessage() failed to map the correct bytes for payload."+
+			"\nexpected: %+v\nreceived: %+v", payload, m.payload)
+	}
+}
+
+// Happy path.
+func TestTransmitMessage_Marshal_Unmarshal(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	externalPayloadSize := prng.Intn(2000)
+	pubKeySize := prng.Intn(externalPayloadSize)
+	data := make([]byte, externalPayloadSize)
+	prng.Read(data)
+	m := mapTransmitMessage(data, pubKeySize)
+
+	msgBytes := m.Marshal()
+
+	newMsg, err := unmarshalTransmitMessage(msgBytes, pubKeySize)
+	if err != nil {
+		t.Errorf("unmarshalTransmitMessage produced an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(m, newMsg) {
+		t.Errorf("Failed to marshal/unmarshal message."+
+			"\nexpected: %+v\nreceived: %+v", m, newMsg)
+	}
+}
+
+// Error path: public key size is larger than byte slice.
+func Test_unmarshalTransmitMessage_PubKeySizeError(t *testing.T) {
+	_, err := unmarshalTransmitMessage([]byte{1, 2, 3}, 5)
+	if err == nil {
+		t.Error("unmarshalTransmitMessage() did not produce an error when the " +
+			"byte slice is smaller than the public key size.")
+	}
+}
+
+// Happy path.
+func TestTransmitMessage_SetPubKey_GetPubKey_GetPubKeySize(t *testing.T) {
+	grp := getGroup()
+	pubKey := grp.NewInt(5)
+	pubKeySize := 10
+	m := newTransmitMessage(255, pubKeySize)
+
+	m.SetPubKey(pubKey)
+	testPubKey := m.GetPubKey(grp)
+
+	if pubKey.Cmp(testPubKey) != 0 {
+		t.Errorf("GetPubKey() failed to get correct public key."+
+			"\nexpected: %s\nreceived: %s", pubKey.Text(10), testPubKey.Text(10))
+	}
+
+	if pubKeySize != m.GetPubKeySize() {
+		t.Errorf("GetPubKeySize() failed to return the correct size."+
+			"\nexpected: %d\nreceived: %d", pubKeySize, m.GetPubKeySize())
+	}
+}
+
+func TestTransmitMessage_SetPayload_GetPayload_GetPayloadSize(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	externalPayloadSize := prng.Intn(2000)
+	pubKeySize := prng.Intn(externalPayloadSize)
+	payload := make([]byte, externalPayloadSize-pubKeySize)
+	prng.Read(payload)
+	m := newTransmitMessage(externalPayloadSize, pubKeySize)
+
+	m.SetPayload(payload)
+	testPayload := m.GetPayload()
+
+	if !bytes.Equal(payload, testPayload) {
+		t.Errorf("GetContents() returned incorrect payload."+
+			"\nexpected: %+v\nreceived: %+v", payload, testPayload)
+	}
+
+	payloadSize := externalPayloadSize - pubKeySize
+	if payloadSize != m.GetPayloadSize() {
+		t.Errorf("GetContentsSize() returned incorrect content size."+
+			"\nexpected: %d\nreceived: %d", payloadSize, m.GetPayloadSize())
+	}
+}
+
+// Error path: supplied payload is not the same size as message payload.
+func TestTransmitMessage_SetPayload_PayloadSizeError(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil || !strings.Contains(r.(string), "is not the same as the size") {
+			t.Error("SetContents() did not panic when the size of supplied " +
+				"contents is not the same size as message contents.")
+		}
+	}()
+
+	m := newTransmitMessage(255, 10)
+	m.SetPayload([]byte{5})
+}
+
+// Happy path.
+func Test_newTransmitMessagePayload(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	payloadSize := prng.Intn(2000)
+	expected := transmitMessagePayload{
+		data:     make([]byte, payloadSize),
+		tagFP:    make([]byte, tagFPSize),
+		nonce:    make([]byte, nonceSize),
+		maxParts: make([]byte, maxPartsSize),
+		size:     make([]byte, sizeSize),
+		contents: make([]byte, payloadSize-transmitPlMinSize),
+	}
+
+	mp := newTransmitMessagePayload(payloadSize)
+
+	if !reflect.DeepEqual(expected, mp) {
+		t.Errorf("newTransmitMessagePayload() did not produce the expected "+
+			"transmitMessagePayload.\nexpected: %+v\nreceived: %+v", expected, mp)
+	}
+}
+
+// Error path: payload size is smaller than than rid size + maxParts size.
+func Test_newTransmitMessagePayload_PayloadSizeError(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil || !strings.Contains(r.(string), "Size of single-use transmission message payload") {
+			t.Error("newTransmitMessagePayload() did not panic when the size " +
+				"of the payload is smaller than the size of the reception ID " +
+				"+ the message count.")
+		}
+	}()
+
+	_ = newTransmitMessagePayload(10)
+}
+
+// Happy path.
+func Test_mapTransmitMessagePayload(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	tagFP := singleUse.NewTagFP("Tag")
+	nonceBytes := make([]byte, nonceSize)
+	num := uint8(prng.Uint64())
+	size := []byte{uint8(prng.Uint64()), uint8(prng.Uint64())}
+	contents := make([]byte, prng.Intn(1000))
+	prng.Read(contents)
+	var data []byte
+	data = append(data, tagFP.Bytes()...)
+	data = append(data, nonceBytes...)
+	data = append(data, num)
+	data = append(data, size...)
+	data = append(data, contents...)
+	mp := mapTransmitMessagePayload(data)
+
+	if !bytes.Equal(data, mp.data) {
+		t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+
+			"for data.\nexpected: %+v\nreceived: %+v", data, mp.data)
+	}
+
+	if !bytes.Equal(tagFP.Bytes(), mp.tagFP) {
+		t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+
+			"for tagFP.\nexpected: %+v\nreceived: %+v", tagFP.Bytes(), mp.tagFP)
+	}
+
+	if !bytes.Equal(nonceBytes, mp.nonce) {
+		t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+
+			"for the nonce.\nexpected: %s\nreceived: %s", nonceBytes, mp.nonce)
+	}
+
+	if num != mp.maxParts[0] {
+		t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+
+			"for maxParts.\nexpected: %d\nreceived: %d", num, mp.maxParts[0])
+	}
+
+	if !bytes.Equal(size, mp.size) {
+		t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+
+			"for size.\nexpected: %+v\nreceived: %+v", size, mp.size)
+	}
+
+	if !bytes.Equal(contents, mp.contents) {
+		t.Errorf("mapTransmitMessagePayload() failed to map the correct bytes "+
+			"for contents.\nexpected: %+v\nreceived: %+v", contents, mp.contents)
+	}
+}
+
+// Happy path.
+func TestTransmitMessagePayload_Marshal_Unmarshal(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	data := make([]byte, prng.Intn(1000))
+	prng.Read(data)
+	mp := mapTransmitMessagePayload(data)
+
+	payloadBytes := mp.Marshal()
+
+	testPayload, err := unmarshalTransmitMessagePayload(payloadBytes)
+	if err != nil {
+		t.Errorf("unmarshalTransmitMessagePayload() produced an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(mp, testPayload) {
+		t.Errorf("Failed to marshal and unmarshal payload."+
+			"\nexpected: %+v\nreceived: %+v", mp, testPayload)
+	}
+}
+
+// Error path: supplied byte slice is too small for the ID and message count.
+func Test_unmarshalTransmitMessagePayload(t *testing.T) {
+	_, err := unmarshalTransmitMessagePayload([]byte{6})
+	if err == nil {
+		t.Error("unmarshalTransmitMessagePayload() did not return an error " +
+			"when the supplied byte slice was too small.")
+	}
+}
+
+// Happy path.
+func TestTransmitMessagePayload_GetRID(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	mp := newTransmitMessagePayload(prng.Intn(2000))
+	expectedRID := singleUse.NewRecipientID(getGroup().NewInt(42), mp.Marshal())
+
+	testRID := mp.GetRID(getGroup().NewInt(42))
+
+	if !expectedRID.Cmp(testRID) {
+		t.Errorf("GetRID() did not return the expected ID."+
+			"\nexpected: %s\nreceived: %s", expectedRID, testRID)
+	}
+}
+
+// Happy path.
+func Test_transmitMessagePayload_SetNonce_GetNonce(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	mp := newTransmitMessagePayload(prng.Intn(2000))
+
+	expectedNonce := prng.Uint64()
+	expectedNonceBytes := make([]byte, 8)
+	binary.BigEndian.PutUint64(expectedNonceBytes, expectedNonce)
+	err := mp.SetNonce(strings.NewReader(string(expectedNonceBytes)))
+	if err != nil {
+		t.Errorf("SetNonce() produced an error: %+v", err)
+	}
+
+	if expectedNonce != mp.GetNonce() {
+		t.Errorf("GetNonce() did not return the expected nonce."+
+			"\nexpected: %d\nreceived: %d", expectedNonce, mp.GetNonce())
+	}
+}
+
+// Error path: RNG return an error.
+func Test_transmitMessagePayload_SetNonce_RngError(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	mp := newTransmitMessagePayload(prng.Intn(2000))
+	err := mp.SetNonce(strings.NewReader(""))
+	if !check(err, "failed to generate nonce") {
+		t.Errorf("SetNonce() did not return an error when nonce generation "+
+			"fails: %+v", err)
+	}
+}
+
+// Happy path.
+func TestTransmitMessagePayload_SetMaxParts_GetMaxParts(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	mp := newTransmitMessagePayload(prng.Intn(2000))
+	count := uint8(prng.Uint64())
+
+	mp.SetMaxParts(count)
+	testCount := mp.GetMaxParts()
+
+	if count != testCount {
+		t.Errorf("GetMaxParts() did not return the expected count."+
+			"\nexpected: %d\nreceived: %d", count, testCount)
+	}
+}
+
+// Happy path.
+func TestTransmitMessagePayload_SetContents_GetContents_GetContentsSize_GetMaxContentsSize(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	mp := newTransmitMessagePayload(format.MinimumPrimeSize)
+	contentsSize := (format.MinimumPrimeSize - transmitPlMinSize) / 2
+	contents := make([]byte, contentsSize)
+	prng.Read(contents)
+
+	mp.SetContents(contents)
+	testContents := mp.GetContents()
+	if !bytes.Equal(contents, testContents) {
+		t.Errorf("GetContents() did not return the expected contents."+
+			"\nexpected: %+v\nreceived: %+v", contents, testContents)
+	}
+
+	if contentsSize != mp.GetContentsSize() {
+		t.Errorf("GetContentsSize() did not return the expected size."+
+			"\nexpected: %d\nreceived: %d", contentsSize, mp.GetContentsSize())
+	}
+
+	if format.MinimumPrimeSize-transmitPlMinSize != mp.GetMaxContentsSize() {
+		t.Errorf("GetMaxContentsSize() did not return the expected size."+
+			"\nexpected: %d\nreceived: %d", format.MinimumPrimeSize-transmitPlMinSize, mp.GetMaxContentsSize())
+	}
+}
+
+// Error path: supplied bytes are smaller than payload contents.
+func TestTransmitMessagePayload_SetContents(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil || !strings.Contains(r.(string), "max size of message content") {
+			t.Error("SetContents() did not panic when the size of the " +
+				"supplied bytes is not the same as the payload content size.")
+		}
+	}()
+
+	mp := newTransmitMessagePayload(format.MinimumPrimeSize)
+	mp.SetContents(make([]byte, format.MinimumPrimeSize+1))
+}
+
+func getGroup() *cyclic.Group {
+	return cyclic.NewGroup(
+		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D4941"+
+			"3394C049B7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688"+
+			"B55B3DD2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861"+
+			"575E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC"+
+			"718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FF"+
+			"B1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBC"+
+			"A23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD"+
+			"161C7738F32BF29A841698978825B4111B4BC3E1E198455095958333D776D8B2B"+
+			"EEED3A1A1A221A6E37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C"+
+			"4F50D7D7803D2D4F278DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F"+
+			"1390B5D3FEACAF1696015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F"+
+			"96789C38E89D796138E6319BE62E35D87B1048CA28BE389B575E994DCA7554715"+
+			"84A09EC723742DC35873847AEF49F66E43873", 16),
+		large.NewIntFromString("2", 16))
+}
diff --git a/stoppable/bindings.go b/stoppable/bindings.go
new file mode 100644
index 0000000000000000000000000000000000000000..55784d6522bcb0567d8eb86b282bb9895302ab57
--- /dev/null
+++ b/stoppable/bindings.go
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import "time"
+
+type Bindings interface {
+	Close(timeoutMS int) error
+	IsRunning() bool
+	Name() string
+}
+
+func WrapForBindings(s Stoppable) Bindings {
+	return &bindingsStoppable{s: s}
+}
+
+type bindingsStoppable struct {
+	s Stoppable
+}
+
+func (bs *bindingsStoppable) Close(timeoutMS int) error {
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	return bs.s.Close(timeout)
+}
+
+func (bs *bindingsStoppable) IsRunning() bool {
+	return bs.s.IsRunning()
+}
+
+func (bs *bindingsStoppable) Name() string {
+	return bs.s.Name()
+}
diff --git a/stoppable/cleanup.go b/stoppable/cleanup.go
new file mode 100644
index 0000000000000000000000000000000000000000..c721f3605aa6d5ef1323a9e01dc2b80b7cb3684c
--- /dev/null
+++ b/stoppable/cleanup.go
@@ -0,0 +1,89 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import (
+	"github.com/pkg/errors"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+// Cleanup wraps any stoppable and runs a callback after to stop for cleanup
+// behavior. The cleanup is run under the remainder of the timeout but will not
+// be canceled if the timeout runs out. The cleanup function does not run if the
+// thread does not stop.
+type Cleanup struct {
+	stop Stoppable
+	// the clean function receives how long it has to run before the timeout,
+	// this is nto expected to be used in most cases
+	clean   func(duration time.Duration) error
+	running uint32
+	once    sync.Once
+}
+
+// NewCleanup creates a new Cleanup from the passed stoppable and function.
+func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Cleanup {
+	return &Cleanup{
+		stop:    stop,
+		clean:   clean,
+		running: 0,
+	}
+}
+
+// IsRunning returns true if the thread is still running and its cleanup has
+// completed.
+func (c *Cleanup) IsRunning() bool {
+	return atomic.LoadUint32(&c.running) == 1
+}
+
+// Name returns the name of the stoppable denoting it has cleanup.
+func (c *Cleanup) Name() string {
+	return c.stop.Name() + " with cleanup"
+}
+
+// Close stops the contained stoppable and runs the cleanup function after. The
+// cleanup function does not run if the thread does not stop.
+func (c *Cleanup) Close(timeout time.Duration) error {
+	var err error
+
+	c.once.Do(
+		func() {
+			defer atomic.StoreUint32(&c.running, 0)
+			start := time.Now()
+
+			// Run the stoppable
+			if err := c.stop.Close(timeout); err != nil {
+				err = errors.WithMessagef(err, "Cleanup for %s not executed",
+					c.stop.Name())
+				return
+			}
+
+			// Run the cleanup function with the remaining time as a timeout
+			elapsed := time.Since(start)
+
+			complete := make(chan error, 1)
+			go func() {
+				complete <- c.clean(elapsed)
+			}()
+
+			timer := time.NewTimer(elapsed)
+
+			select {
+			case err := <-complete:
+				if err != nil {
+					err = errors.WithMessagef(err, "Cleanup for %s failed",
+						c.stop.Name())
+				}
+			case <-timer.C:
+				err = errors.Errorf("Clean up for %s timeout", c.stop.Name())
+			}
+		})
+
+	return err
+}
diff --git a/stoppable/cleanup_test.go b/stoppable/cleanup_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8bc7fe0be09e69b0809cbd97194dbbe58902918f
--- /dev/null
+++ b/stoppable/cleanup_test.go
@@ -0,0 +1,62 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import (
+	"testing"
+)
+
+// Tests happy path of NewCleanup().
+func TestNewCleanup(t *testing.T) {
+	single := NewSingle("test name")
+	cleanup := NewCleanup(single, single.Close)
+
+	if cleanup.stop != single || cleanup.running != 0 {
+		t.Errorf("NewCleanup() returned Single with incorrect values."+
+			"\n\texpected:  stop: %v  running: %d\n\treceived:  stop: %v  running: %d",
+			single, cleanup.stop, 0, cleanup.running)
+	}
+}
+
+// Tests happy path of Cleanup.IsRunning().
+func TestCleanup_IsRunning(t *testing.T) {
+	single := NewSingle("test name")
+	cleanup := NewCleanup(single, single.Close)
+
+	if cleanup.IsRunning() {
+		t.Errorf("IsRunning() returned false when it should be running.")
+	}
+
+	cleanup.running = 1
+	if !cleanup.IsRunning() {
+		t.Errorf("IsRunning() returned true when it should not be running.")
+	}
+}
+
+// Tests happy path of Cleanup.Name().
+func TestCleanup_Name(t *testing.T) {
+	name := "test name"
+	single := NewSingle(name)
+	cleanup := NewCleanup(single, single.Close)
+
+	if name+" with cleanup" != cleanup.Name() {
+		t.Errorf("Name() returned the incorrect string."+
+			"\n\texpected: %s\n\treceived: %s", name+" with cleanup", cleanup.Name())
+	}
+}
+
+// Tests happy path of Cleanup.Close().
+func TestCleanup_Close(t *testing.T) {
+	single := NewSingle("test name")
+	cleanup := NewCleanup(single, single.Close)
+
+	err := cleanup.Close(0)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+}
diff --git a/stoppable/multi.go b/stoppable/multi.go
new file mode 100644
index 0000000000000000000000000000000000000000..0636b84fa6e4a3d17e5a74431e42e796d84de45b
--- /dev/null
+++ b/stoppable/multi.go
@@ -0,0 +1,98 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+type Multi struct {
+	stoppables []Stoppable
+	name       string
+	running    uint32
+	mux        sync.RWMutex
+	once       sync.Once
+}
+
+// NewMulti returns a new multi stoppable.
+func NewMulti(name string) *Multi {
+	return &Multi{
+		name:    name,
+		running: 1,
+	}
+}
+
+// IsRunning returns true if the thread is still running.
+func (m *Multi) IsRunning() bool {
+	return atomic.LoadUint32(&m.running) == 1
+}
+
+// Add adds the given stoppable to the list of stoppables.
+func (m *Multi) Add(stoppable Stoppable) {
+	m.mux.Lock()
+	m.stoppables = append(m.stoppables, stoppable)
+	m.mux.Unlock()
+}
+
+// Name returns the name of the multi stoppable and the names of all stoppables
+// it contains.
+func (m *Multi) Name() string {
+	m.mux.RLock()
+	names := m.name + ": {"
+	for _, s := range m.stoppables {
+		names += s.Name() + ", "
+	}
+	if len(m.stoppables) > 0 {
+		names = names[:len(names)-2]
+	}
+	names += "}"
+	m.mux.RUnlock()
+
+	return names
+}
+
+// Close closes all child stoppers. It does not return their errors and assumes
+// they print them to the log.
+func (m *Multi) Close(timeout time.Duration) error {
+	var err error
+	m.once.Do(
+		func() {
+			atomic.StoreUint32(&m.running, 0)
+
+			numErrors := uint32(0)
+			wg := &sync.WaitGroup{}
+
+			m.mux.Lock()
+			for _, stoppable := range m.stoppables {
+				wg.Add(1)
+				go func(stoppable Stoppable) {
+					if stoppable.Close(timeout) != nil {
+						atomic.AddUint32(&numErrors, 1)
+					}
+					wg.Done()
+				}(stoppable)
+			}
+			m.mux.Unlock()
+
+			wg.Wait()
+
+			if numErrors > 0 {
+				errStr := fmt.Sprintf("MultiStopper %s failed to close "+
+					"%v/%v stoppers", m.name, numErrors, len(m.stoppables))
+				jww.ERROR.Println(errStr)
+				err = errors.New(errStr)
+			}
+		})
+
+	return err
+}
diff --git a/stoppable/multi_test.go b/stoppable/multi_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5999f838ae44d0b64a96cd27c0fc1d8e28ee49fd
--- /dev/null
+++ b/stoppable/multi_test.go
@@ -0,0 +1,122 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import (
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests happy path of NewMulti().
+func TestNewMulti(t *testing.T) {
+	name := "test name"
+	multi := NewMulti(name)
+
+	if multi.name != name || multi.running != 1 {
+		t.Errorf("NewMulti() returned Multi with incorrect values."+
+			"\n\texpected:  name: %s  running: %d\n\treceived:  name: %s  running: %d",
+			name, 1, multi.name, multi.running)
+	}
+}
+
+// Tests happy path of Multi.IsRunning().
+func TestMulti_IsRunning(t *testing.T) {
+	multi := NewMulti("name")
+
+	if !multi.IsRunning() {
+		t.Errorf("IsRunning() returned false when it should be running.")
+	}
+
+	multi.running = 0
+	if multi.IsRunning() {
+		t.Errorf("IsRunning() returned true when it should not be running.")
+	}
+}
+
+// Tests happy path of Multi.Add().
+func TestMulti_Add(t *testing.T) {
+	multi := NewMulti("multi name")
+	singles := []*Single{
+		NewSingle("single name 1"),
+		NewSingle("single name 2"),
+		NewSingle("single name 3"),
+	}
+
+	for _, single := range singles {
+		multi.Add(single)
+	}
+
+	for i, single := range singles {
+		if !reflect.DeepEqual(single, multi.stoppables[i]) {
+			t.Errorf("Add() did not add the correct Stoppables."+
+				"\n\texpected: %#v\n\treceived: %#v", single, multi.stoppables[i])
+		}
+	}
+}
+
+// Tests happy path of Multi.Name().
+func TestMulti_Name(t *testing.T) {
+	name := "test name"
+	multi := NewMulti(name)
+	singles := []*Single{
+		NewSingle("single name 1"),
+		NewSingle("single name 2"),
+		NewSingle("single name 3"),
+	}
+	expectedNames := []string{
+		name + ": {}",
+		name + ": {" + singles[0].name + "}",
+		name + ": {" + singles[0].name + ", " + singles[1].name + "}",
+		name + ": {" + singles[0].name + ", " + singles[1].name + ", " + singles[2].name + "}",
+	}
+
+	for i, single := range singles {
+		if expectedNames[i] != multi.Name() {
+			t.Errorf("Name() returned the incorrect string."+
+				"\n\texpected: %s\n\treceived: %s", expectedNames[0], multi.Name())
+		}
+		multi.Add(single)
+	}
+}
+
+// Tests happy path of Multi.Close().
+func TestMulti_Close(t *testing.T) {
+	// Create new Multi and add Singles to it
+	multi := NewMulti("name")
+	singles := []*Single{
+		NewSingle("single name 1"),
+		NewSingle("single name 2"),
+		NewSingle("single name 3"),
+	}
+	for _, single := range singles {
+		multi.Add(single)
+	}
+
+	go func() {
+		select {
+		case <-singles[0].quit:
+		}
+		select {
+		case <-singles[1].quit:
+		}
+		select {
+		case <-singles[2].quit:
+		}
+	}()
+
+	err := multi.Close(5 * time.Millisecond)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+
+	err = multi.Close(0)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+}
diff --git a/stoppable/single.go b/stoppable/single.go
new file mode 100644
index 0000000000000000000000000000000000000000..8821db88eb4934a06db4f96484ef700b2c6beeff
--- /dev/null
+++ b/stoppable/single.go
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+// Single allows stopping a single goroutine using a channel.
+// It adheres to the stoppable interface.
+type Single struct {
+	name    string
+	quit    chan struct{}
+	running uint32
+	once    sync.Once
+}
+
+// NewSingle returns a new single stoppable.
+func NewSingle(name string) *Single {
+	return &Single{
+		name:    name,
+		quit:    make(chan struct{}),
+		running: 1,
+	}
+}
+
+// IsRunning returns true if the thread is still running.
+func (s *Single) IsRunning() bool {
+	return atomic.LoadUint32(&s.running) == 1
+}
+
+// Quit returns the read only channel it will send the stop signal on.
+func (s *Single) Quit() <-chan struct{} {
+	return s.quit
+}
+
+// Name returns the name of the thread. This is designed to be
+func (s *Single) Name() string {
+	return s.name
+}
+
+// Close signals the thread to time out and closes if it is still running.
+func (s *Single) Close(timeout time.Duration) error {
+	var err error
+	s.once.Do(func() {
+		atomic.StoreUint32(&s.running, 0)
+		timer := time.NewTimer(timeout)
+		select {
+		case <-timer.C:
+			jww.ERROR.Printf("Stopper for %s failed to stop after "+
+				"timeout of %s", s.name, timeout)
+			err = errors.Errorf("%s failed to close", s.name)
+		case s.quit <- struct{}{}:
+		}
+	})
+	return err
+}
diff --git a/stoppable/single_test.go b/stoppable/single_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ceb5a9ecf6235a5de1884ed4d469f0a46aa0c0f4
--- /dev/null
+++ b/stoppable/single_test.go
@@ -0,0 +1,103 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import (
+	"testing"
+	"time"
+)
+
+// Tests happy path of NewSingle().
+func TestNewSingle(t *testing.T) {
+	name := "test name"
+	single := NewSingle(name)
+
+	if single.name != name || single.running != 1 {
+		t.Errorf("NewSingle() returned Single with incorrect values."+
+			"\n\texpected:  name: %s  running: %d\n\treceived:  name: %s  running: %d",
+			name, 1, single.name, single.running)
+	}
+}
+
+// Tests happy path of Single.IsRunning().
+func TestSingle_IsRunning(t *testing.T) {
+	single := NewSingle("name")
+
+	if !single.IsRunning() {
+		t.Errorf("IsRunning() returned false when it should be running.")
+	}
+
+	single.running = 0
+	if single.IsRunning() {
+		t.Errorf("IsRunning() returned true when it should not be running.")
+	}
+}
+
+// Tests happy path of Single.Quit().
+func TestSingle_Quit(t *testing.T) {
+	single := NewSingle("name")
+
+	go func() {
+		time.Sleep(150 * time.Nanosecond)
+		single.quit <- struct{}{}
+	}()
+
+	timer := time.NewTimer(2 * time.Millisecond)
+	select {
+	case <-timer.C:
+		t.Errorf("Quit signal not received.")
+	case <-single.quit:
+	}
+}
+
+// Tests happy path of Single.Name().
+func TestSingle_Name(t *testing.T) {
+	name := "test name"
+	single := NewSingle(name)
+
+	if name != single.Name() {
+		t.Errorf("Name() returned the incorrect string."+
+			"\n\texpected: %s\n\treceived: %s", name, single.Name())
+	}
+}
+
+// Test happy path of Single.Close().
+func TestSingle_Close(t *testing.T) {
+	single := NewSingle("name")
+
+	go func() {
+		time.Sleep(150 * time.Nanosecond)
+		select {
+		case <-single.quit:
+		}
+	}()
+
+	err := single.Close(5 * time.Millisecond)
+	if err != nil {
+		t.Errorf("Close() returned an error: %v", err)
+	}
+}
+
+// Tests that Single.Close() returns an error when the timeout is reached.
+func TestSingle_Close_Error(t *testing.T) {
+	single := NewSingle("name")
+	expectedErr := single.name + " failed to close"
+
+	go func() {
+		time.Sleep(3 * time.Millisecond)
+		select {
+		case <-single.quit:
+		}
+	}()
+
+	err := single.Close(2 * time.Millisecond)
+	if err == nil {
+		t.Errorf("Close() did not return the expected error."+
+			"\n\texpected: %v\n\treceived: %v", expectedErr, err)
+	}
+}
diff --git a/stoppable/stoppable.go b/stoppable/stoppable.go
new file mode 100644
index 0000000000000000000000000000000000000000..06947eb3bf5ff5ae3523b927e8fb7792848fd762
--- /dev/null
+++ b/stoppable/stoppable.go
@@ -0,0 +1,17 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package stoppable
+
+import "time"
+
+// Interface for stopping a goroutine.
+type Stoppable interface {
+	Close(timeout time.Duration) error
+	IsRunning() bool
+	Name() string
+}
diff --git a/storage/auth/fingerprint.go b/storage/auth/fingerprint.go
new file mode 100644
index 0000000000000000000000000000000000000000..04d0571c852f0ca359e303d69f41e1085f57bd14
--- /dev/null
+++ b/storage/auth/fingerprint.go
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import "gitlab.com/elixxir/crypto/cyclic"
+
+type FingerprintType uint
+
+const (
+	General  FingerprintType = 1
+	Specific FingerprintType = 2
+)
+
+type fingerprint struct {
+	Type FingerprintType
+
+	// Only populated if it is general
+	PrivKey *cyclic.Int
+
+	// Only populated if it is specific
+	Request *request
+}
diff --git a/storage/auth/request.go b/storage/auth/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..c33d9b02cbe2477725438903052102fdf18d11ff
--- /dev/null
+++ b/storage/auth/request.go
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"sync"
+)
+
+type RequestType uint
+
+const (
+	Sent    RequestType = 1
+	Receive RequestType = 2
+)
+
+type request struct {
+	rt RequestType
+
+	// Data if sent
+	sent *SentRequest
+
+	// Data if receive
+	receive *contact.Contact
+
+	// mux to ensure there is not concurrent access
+	mux sync.Mutex
+}
+
+type requestDisk struct {
+	T  uint
+	ID []byte
+}
diff --git a/storage/auth/sentRequest.go b/storage/auth/sentRequest.go
new file mode 100644
index 0000000000000000000000000000000000000000..09251401cbe1433d917a6d3912ab68aaf2873002
--- /dev/null
+++ b/storage/auth/sentRequest.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                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const currentSentRequestVersion = 0
+
+type SentRequest struct {
+	kv *versioned.KV
+
+	partner                 *id.ID
+	partnerHistoricalPubKey *cyclic.Int
+	myPrivKey               *cyclic.Int
+	myPubKey                *cyclic.Int
+	fingerprint             format.Fingerprint
+}
+
+type sentRequestDisk struct {
+	PartnerHistoricalPubKey []byte
+	MyPrivKey               []byte
+	MyPubKey                []byte
+	Fingerprint             []byte
+}
+
+func loadSentRequest(kv *versioned.KV, partner *id.ID, grp *cyclic.Group) (*SentRequest, error) {
+	obj, err := kv.Get(versioned.MakePartnerPrefix(partner),
+		currentSentRequestVersion)
+	if err != nil {
+		return nil, errors.WithMessagef(err, "Failed to Load "+
+			"SentRequest Auth with %s", partner)
+	}
+
+	srd := &sentRequestDisk{}
+
+	if err := json.Unmarshal(obj.Data, srd); err != nil {
+		return nil, errors.WithMessagef(err, "Failed to Unmarshal "+
+			"SentRequest Auth with %s", partner)
+	}
+
+	historicalPrivKey := grp.NewInt(1)
+	if err = historicalPrivKey.GobDecode(srd.PartnerHistoricalPubKey); err != nil {
+		return nil, errors.WithMessagef(err, "Failed to decode historical "+
+			"private key with %s for SentRequest Auth", partner)
+	}
+
+	myPrivKey := grp.NewInt(1)
+	if err = myPrivKey.GobDecode(srd.MyPrivKey); err != nil {
+		return nil, errors.WithMessagef(err, "Failed to decode private key "+
+			"with %s for SentRequest Auth", partner)
+	}
+
+	myPubKey := grp.NewInt(1)
+	if err = myPubKey.GobDecode(srd.MyPubKey); err != nil {
+		return nil, errors.WithMessagef(err, "Failed to decode public "+
+			"key with %s for SentRequest Auth", partner)
+	}
+
+	fp := format.Fingerprint{}
+	copy(fp[:], srd.Fingerprint)
+
+	return &SentRequest{
+		kv:                      kv,
+		partner:                 partner,
+		partnerHistoricalPubKey: historicalPrivKey,
+		myPrivKey:               myPrivKey,
+		myPubKey:                myPubKey,
+		fingerprint:             fp,
+	}, nil
+}
+
+func (sr *SentRequest) save() error {
+	privKey, err := sr.myPrivKey.GobEncode()
+	if err != nil {
+		return err
+	}
+
+	pubKey, err := sr.myPubKey.GobEncode()
+	if err != nil {
+		return err
+	}
+
+	historicalPrivKey, err := sr.partnerHistoricalPubKey.GobEncode()
+	if err != nil {
+		return err
+	}
+
+	ipd := sentRequestDisk{
+		PartnerHistoricalPubKey: historicalPrivKey,
+		MyPrivKey:               privKey,
+		MyPubKey:                pubKey,
+		Fingerprint:             sr.fingerprint[:],
+	}
+
+	data, err := json.Marshal(&ipd)
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentSentRequestVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	}
+
+	return sr.kv.Set(versioned.MakePartnerPrefix(sr.partner),
+		currentSentRequestVersion, &obj)
+}
+
+func (sr *SentRequest) delete() error {
+	return sr.kv.Delete(versioned.MakePartnerPrefix(sr.partner),
+		currentSentRequestVersion)
+}
+
+func (sr *SentRequest) GetPartner() *id.ID {
+	return sr.partner
+}
+
+func (sr *SentRequest) GetPartnerHistoricalPubKey() *cyclic.Int {
+	return sr.partnerHistoricalPubKey
+}
+
+func (sr *SentRequest) GetMyPrivKey() *cyclic.Int {
+	return sr.myPrivKey
+}
+
+func (sr *SentRequest) GetMyPubKey() *cyclic.Int {
+	return sr.myPubKey
+}
+
+func (sr *SentRequest) GetFingerprint() format.Fingerprint {
+	return sr.fingerprint
+}
diff --git a/storage/auth/store.go b/storage/auth/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d8f074436461f7c7a3e198bbbbc772d54804388
--- /dev/null
+++ b/storage/auth/store.go
@@ -0,0 +1,410 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+const NoRequest = "Request Not Found"
+
+const storePrefix = "requestMap"
+const requestMapKey = "map"
+
+const requestMapVersion = 0
+
+type Store struct {
+	kv           *versioned.KV
+	grp          *cyclic.Group
+	requests     map[id.ID]*request
+	fingerprints map[format.Fingerprint]fingerprint
+	mux          sync.RWMutex
+}
+
+// NewStore creates a new store. All passed in private keys are added as
+// fingerprints so they can be used to trigger requests.
+func NewStore(kv *versioned.KV, grp *cyclic.Group, privKeys []*cyclic.Int) (*Store, error) {
+	kv = kv.Prefix(storePrefix)
+	s := &Store{
+		kv:           kv,
+		grp:          grp,
+		requests:     make(map[id.ID]*request),
+		fingerprints: make(map[format.Fingerprint]fingerprint),
+	}
+
+	for _, key := range privKeys {
+		pubkey := grp.ExpG(key, grp.NewInt(1))
+		fp := auth.MakeRequestFingerprint(pubkey)
+		s.fingerprints[fp] = fingerprint{
+			Type:    General,
+			PrivKey: key,
+			Request: nil,
+		}
+	}
+
+	return s, s.save()
+}
+
+// LoadStore loads an extant new store. All passed in private keys are added as
+// fingerprints so they can be used to trigger requests.
+func LoadStore(kv *versioned.KV, grp *cyclic.Group, privKeys []*cyclic.Int) (*Store, error) {
+	kv = kv.Prefix(storePrefix)
+	sentObj, err := kv.Get(requestMapKey, requestMapVersion)
+	if err != nil {
+		return nil, errors.WithMessagef(err, "Failed to load requestMap")
+	}
+
+	s := &Store{
+		kv:           kv,
+		grp:          grp,
+		requests:     make(map[id.ID]*request),
+		fingerprints: make(map[format.Fingerprint]fingerprint),
+	}
+
+	for _, key := range privKeys {
+		pubkey := grp.ExpG(key, grp.NewInt(1))
+		fp := auth.MakeRequestFingerprint(pubkey)
+		s.fingerprints[fp] = fingerprint{
+			Type:    General,
+			PrivKey: key,
+			Request: nil,
+		}
+	}
+
+	var requestList []requestDisk
+	if err := json.Unmarshal(sentObj.Data, &requestList); err != nil {
+		return nil, errors.WithMessagef(err, "Failed to "+
+			"unmarshal SentRequestMap")
+	}
+
+	for _, rDisk := range requestList {
+		r := &request{
+			rt: RequestType(rDisk.T),
+		}
+
+		var rid *id.ID
+
+		partner, err := id.Unmarshal(rDisk.ID)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to load stored id: %+v", err)
+		}
+
+		switch r.rt {
+		case Sent:
+			sr, err := loadSentRequest(kv, partner, grp)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to load stored sentRequest: %+v", err)
+			}
+			r.sent = sr
+			s.fingerprints[sr.fingerprint] = fingerprint{
+				Type:    Specific,
+				PrivKey: nil,
+				Request: r,
+			}
+
+			rid = sr.partner
+			r.sent = sr
+
+		case Receive:
+			c, err := utility.LoadContact(kv, partner)
+			if err != nil {
+				jww.FATAL.Panicf("Failed to load stored contact for: %+v", err)
+			}
+
+			rid = c.ID
+			r.receive = &c
+
+		default:
+			jww.FATAL.Panicf("Unknown request type: %d", r.rt)
+		}
+
+		//store in the request map
+		s.requests[*rid] = r
+	}
+
+	return s, nil
+}
+
+func (s *Store) save() error {
+	requestIDList := make([]requestDisk, len(s.requests))
+
+	index := 0
+	for pid, r := range s.requests {
+		rDisk := requestDisk{
+			T:  uint(r.rt),
+			ID: pid.Marshal(),
+		}
+		requestIDList[index] = rDisk
+		index++
+	}
+
+	data, err := json.Marshal(&requestIDList)
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   requestMapVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	}
+
+	return s.kv.Set(requestMapKey, requestMapVersion, &obj)
+}
+
+func (s *Store) AddSent(partner *id.ID, partnerHistoricalPubKey, myPrivKey,
+	myPubKey *cyclic.Int, fp format.Fingerprint) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if _, ok := s.requests[*partner]; ok {
+		return errors.Errorf("Cannot make new sentRequest for partner "+
+			"%s, one already exists", partner)
+	}
+
+	sr := &SentRequest{
+		kv:                      s.kv,
+		partner:                 partner,
+		partnerHistoricalPubKey: partnerHistoricalPubKey,
+		myPrivKey:               myPrivKey,
+		myPubKey:                myPubKey,
+		fingerprint:             fp,
+	}
+
+	if err := sr.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save Sent Request for partner %s", partner)
+	}
+
+	r := &request{
+		rt:      Sent,
+		sent:    sr,
+		receive: nil,
+		mux:     sync.Mutex{},
+	}
+
+	s.requests[*partner] = r
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save Sent Request Map after adding "+
+			"partern %s", partner)
+	}
+
+	jww.INFO.Printf("AddSent PUBKEY FINGERPRINT: %v", sr.fingerprint)
+	jww.INFO.Printf("AddSent PUBKEY: %v", sr.myPubKey.Bytes())
+
+	s.fingerprints[sr.fingerprint] = fingerprint{
+		Type:    Specific,
+		PrivKey: nil,
+		Request: r,
+	}
+
+	return nil
+}
+
+func (s *Store) AddReceived(c contact.Contact) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if _, ok := s.requests[*c.ID]; ok {
+		return errors.Errorf("Cannot add contact for partner "+
+			"%s, one already exists", c.ID)
+	}
+
+	if err := utility.StoreContact(s.kv, c); err != nil {
+		jww.FATAL.Panicf("Failed to save contact for partner %s", c.ID.String())
+	}
+
+	r := &request{
+		rt:      Receive,
+		sent:    nil,
+		receive: &c,
+		mux:     sync.Mutex{},
+	}
+
+	s.requests[*c.ID] = r
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save Sent Request Map after adding "+
+			"partner %s", c.ID)
+	}
+
+	return nil
+}
+
+// GetFingerprint can return either a private key or a sentRequest if the
+// fingerprint is found. If it returns a sentRequest, then it takes the lock to
+// ensure there is only one operator at a time. The user of the API must release
+// the lock by calling store.delete() or store.Failed() with the partner ID.
+func (s *Store) GetFingerprint(fp format.Fingerprint) (FingerprintType,
+	*SentRequest, *cyclic.Int, error) {
+
+	s.mux.RLock()
+	r, ok := s.fingerprints[fp]
+	s.mux.RUnlock()
+	if !ok {
+		return 0, nil, nil, errors.Errorf("Fingerprint cannot be found: %v", fp)
+	}
+
+	switch r.Type {
+	// If it is general, then just return the private key
+	case General:
+		return General, nil, r.PrivKey, nil
+
+	// If it is specific, then take the request lock and return it
+	case Specific:
+		r.Request.mux.Lock()
+		// Check that the request still exists; it could have been deleted
+		// while the lock was taken
+		s.mux.RLock()
+		_, ok := s.requests[*r.Request.sent.partner]
+		s.mux.RUnlock()
+		if !ok {
+			return 0, nil, nil, errors.Errorf("request associated with "+
+				"fingerprint cannot be found: %s", fp)
+		}
+		// Return the request
+		return Specific, r.Request.sent, nil, nil
+
+	default:
+		jww.WARN.Printf("Auth request message ignored due to unknown "+
+			"fingerprint type %d on lookup; should be impossible", r.Type)
+		return 0, nil, nil, errors.New("Unknown fingerprint type")
+	}
+}
+
+// GetReceivedRequest returns the contact representing the receive request, if
+// it exists. If it returns, then it takes the lock to ensure that there is only
+// one operator at a time. The user of the API must release the lock by calling
+// store.delete() or store.Failed() with the partner ID.
+func (s *Store) GetReceivedRequest(partner *id.ID) (contact.Contact, error) {
+	s.mux.RLock()
+	r, ok := s.requests[*partner]
+	s.mux.RUnlock()
+
+	if !ok {
+		return contact.Contact{}, errors.Errorf("Received request not "+
+			"found: %s", partner)
+	}
+
+	// Take the lock to ensure there is only one operator at a time
+	r.mux.Lock()
+
+	// Check that the request still exists; it could have been deleted while the
+	// lock was taken
+	s.mux.RLock()
+	_, ok = s.requests[*partner]
+	s.mux.RUnlock()
+
+	if !ok {
+		r.mux.Unlock()
+		return contact.Contact{}, errors.Errorf("Received request not "+
+			"found: %s", partner)
+	}
+
+	return *r.receive, nil
+}
+
+// GetReceivedRequestData returns the contact representing the receive request
+// if it exists. It does not take the lock. It is only meant to return the
+// contact to an external API user.
+func (s *Store) GetReceivedRequestData(partner *id.ID) (contact.Contact, error) {
+	s.mux.RLock()
+	r, ok := s.requests[*partner]
+	s.mux.RUnlock()
+
+	if !ok || r.receive == nil {
+		return contact.Contact{}, errors.Errorf("Received request not "+
+			"found: %s", partner)
+	}
+
+	return *r.receive, nil
+}
+
+// GetRequest returns request with its type and data. The lock is not taken.
+func (s *Store) GetRequest(partner *id.ID) (RequestType, *SentRequest, contact.Contact, error) {
+	s.mux.RLock()
+	r, ok := s.requests[*partner]
+	s.mux.RUnlock()
+
+	if !ok {
+		return 0, nil, contact.Contact{}, errors.New(NoRequest)
+	}
+
+	switch r.rt {
+	case Sent:
+		return Sent, r.sent, contact.Contact{}, nil
+	case Receive:
+		return Receive, nil, *r.receive, nil
+	default:
+		return 0, nil, contact.Contact{},
+			errors.Errorf("invalid Type: %d", r.rt)
+	}
+}
+
+// Fail is one of two calls after using a request. This one is to be used when
+// the use is unsuccessful. It will allow any thread waiting on access to
+// continue using the structure.
+// It does not return an error because an error is not handleable.
+func (s *Store) Fail(partner *id.ID) {
+	s.mux.RLock()
+	r, ok := s.requests[*partner]
+	s.mux.RUnlock()
+
+	if !ok {
+		jww.ERROR.Panicf("Request cannot be failed, not found: %s", partner)
+		return
+	}
+
+	r.mux.Unlock()
+}
+
+// delete is one of two calls after using a request. This one is to be used when
+// the use is unsuccessful. It deletes all references to the request associated
+// with the passed partner, if it exists. It will allow any thread waiting on
+// access to continue. They should fail due to the deletion of the structure.
+func (s *Store) Delete(partner *id.ID) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	r, ok := s.requests[*partner]
+
+	if !ok {
+		return errors.Errorf("Request not found: %s", partner)
+	}
+
+	switch r.rt {
+	case Sent:
+		delete(s.fingerprints, r.sent.fingerprint)
+		if err := r.sent.delete(); err != nil {
+			jww.FATAL.Panicf("Failed to delete sent request: %+v", err)
+		}
+
+	case Receive:
+		if err := utility.DeleteContact(s.kv, r.receive.ID); err != nil {
+			jww.FATAL.Panicf("Failed to delete recieved request "+
+				"contact: %+v", err)
+		}
+	}
+
+	delete(s.requests, *partner)
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to store updated request map after "+
+			"deletion: %+v", err)
+	}
+
+	r.mux.Unlock()
+	return nil
+}
diff --git a/storage/auth/store_test.go b/storage/auth/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..591cc31b483e05fcee8bb8084416d3dc71b61c0d
--- /dev/null
+++ b/storage/auth/store_test.go
@@ -0,0 +1,629 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package auth
+
+import (
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"sync"
+	"testing"
+)
+
+// Happy path.
+func TestNewStore(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	privKeys := make([]*cyclic.Int, 10)
+	pubKeys := make([]*cyclic.Int, 10)
+	for i := range privKeys {
+		privKeys[i] = grp.NewInt(rand.Int63n(172))
+		pubKeys[i] = grp.ExpG(privKeys[i], grp.NewInt(1))
+	}
+
+	store, err := NewStore(kv, grp, privKeys)
+	if err != nil {
+		t.Errorf("NewStore() returned an error: %+v", err)
+	}
+
+	for i, key := range privKeys {
+		rq, ok := store.fingerprints[auth.MakeRequestFingerprint(pubKeys[i])]
+		if !ok {
+			t.Errorf("Key not found in map (%d): %s", i, pubKeys[i].Text(16))
+		} else if rq.PrivKey.Cmp(key) != 0 {
+			t.Errorf("Key found in map (%d) does not match private: "+
+				"%s vs %s", i, key.Text(10), rq.PrivKey.Text(10))
+		}
+	}
+}
+
+// Happy path.
+func TestLoadStore(t *testing.T) {
+	s, kv, privKeys := makeTestStore(t)
+
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+
+	sr := &SentRequest{
+		kv:                      s.kv,
+		partner:                 id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		partnerHistoricalPubKey: s.grp.NewInt(5),
+		myPrivKey:               s.grp.NewInt(6),
+		myPubKey:                s.grp.NewInt(7),
+		fingerprint:             format.Fingerprint{42},
+	}
+
+	if err := s.AddSent(sr.partner, sr.partnerHistoricalPubKey, sr.myPrivKey,
+		sr.myPubKey, sr.fingerprint); err != nil {
+		t.Fatalf("AddSent() produced an error: %+v", err)
+	}
+
+	store, err := LoadStore(kv, s.grp, privKeys)
+	if err != nil {
+		t.Errorf("LoadStore() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(s, store) {
+		t.Errorf("LoadStore() returned incorrect Store."+
+			"\n\texpected: %+v\n\treceived: %+v", s, store)
+	}
+}
+
+// Happy path: tests that the correct SentRequest is added to the map.
+func TestStore_AddSent(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	partner := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	sr := &SentRequest{
+		kv:                      s.kv,
+		partner:                 partner,
+		partnerHistoricalPubKey: s.grp.NewInt(5),
+		myPrivKey:               s.grp.NewInt(6),
+		myPubKey:                s.grp.NewInt(7),
+		fingerprint:             format.Fingerprint{42},
+	}
+	expectedFP := fingerprint{
+		Type:    Specific,
+		PrivKey: nil,
+		Request: &request{Sent, sr, nil, sync.Mutex{}},
+	}
+
+	err := s.AddSent(partner, sr.partnerHistoricalPubKey, sr.myPrivKey,
+		sr.myPubKey, sr.fingerprint)
+	if err != nil {
+		t.Errorf("AddSent() produced an error: %+v", err)
+	}
+
+	if s.requests[*partner] == nil {
+		t.Errorf("AddSent() failed to add request to map for partner ID %s.",
+			partner)
+	} else if !reflect.DeepEqual(sr, s.requests[*partner].sent) {
+		t.Errorf("AddSent() failed store the correct SentRequest."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			sr, s.requests[*partner].sent)
+	}
+
+	if _, exists := s.fingerprints[sr.fingerprint]; !exists {
+		t.Errorf("AddSent() failed to add fingerprint to map for fingerprint %s.",
+			sr.fingerprint)
+	} else if !reflect.DeepEqual(expectedFP, s.fingerprints[sr.fingerprint]) {
+		t.Errorf("AddSent() failed store the correct fingerprint."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expectedFP, s.fingerprints[sr.fingerprint])
+	}
+}
+
+// Error path: request with request already exists in map.
+func TestStore_AddSent_PartnerAlreadyExistsError(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	partner := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+
+	err := s.AddSent(partner, s.grp.NewInt(5), s.grp.NewInt(6), s.grp.NewInt(7), format.Fingerprint{42})
+	if err != nil {
+		t.Errorf("AddSent() produced an error: %+v", err)
+	}
+
+	err = s.AddSent(partner, s.grp.NewInt(5), s.grp.NewInt(6), s.grp.NewInt(7), format.Fingerprint{42})
+	if err == nil {
+		t.Errorf("AddSent() did not produce the expected error for a request " +
+			"that already exists.")
+	}
+}
+
+// Happy path.
+func TestStore_AddReceived(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+
+	err := s.AddReceived(c)
+	if err != nil {
+		t.Errorf("AddReceived() returned an error: %+v", err)
+	}
+
+	if s.requests[*c.ID] == nil {
+		t.Errorf("AddReceived() failed to add request to map for partner ID %s.",
+			c.ID)
+	} else if !reflect.DeepEqual(c, *s.requests[*c.ID].receive) {
+		t.Errorf("AddReceived() failed store the correct Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", c, *s.requests[*c.ID].receive)
+	}
+}
+
+// Error path: request with request already exists in map.
+func TestStore_AddReceived_PartnerAlreadyExistsError(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+
+	err := s.AddReceived(c)
+	if err != nil {
+		t.Errorf("AddReceived() returned an error: %+v", err)
+	}
+
+	err = s.AddReceived(c)
+	if err == nil {
+		t.Errorf("AddReceived() did not produce the expected error for a " +
+			"request that already exists.")
+	}
+}
+
+// Happy path: fingerprints type is General.
+func TestStore_GetFingerprint_GeneralFingerprintType(t *testing.T) {
+	s, _, privKeys := makeTestStore(t)
+
+	pubkey := s.grp.ExpG(privKeys[0], s.grp.NewInt(1))
+	fp := auth.MakeRequestFingerprint(pubkey)
+	fpType, request, key, err := s.GetFingerprint(fp)
+	if err != nil {
+		t.Errorf("GetFingerprint() returned an error: %+v", err)
+	}
+	if fpType != General {
+		t.Errorf("GetFingerprint() returned incorrect FingerprintType."+
+			"\n\texpected: %d\n\treceived: %d", General, fpType)
+	}
+	if request != nil {
+		t.Errorf("GetFingerprint() returned incorrect request."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+
+	if key.Cmp(privKeys[0]) == -2 {
+		t.Errorf("GetFingerprint() returned incorrect key."+
+			"\n\texpected: %s\n\treceived: %s", privKeys[0].Text(10), key.Text(10))
+	}
+}
+
+// Happy path: fingerprints type is Specific.
+func TestStore_GetFingerprint_SpecificFingerprintType(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	sr := &SentRequest{
+		kv:                      s.kv,
+		partner:                 id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		partnerHistoricalPubKey: s.grp.NewInt(1),
+		myPrivKey:               s.grp.NewInt(2),
+		myPubKey:                s.grp.NewInt(3),
+		fingerprint:             format.Fingerprint{5},
+	}
+	if err := s.AddSent(sr.partner, sr.partnerHistoricalPubKey, sr.myPrivKey,
+		sr.myPubKey, sr.fingerprint); err != nil {
+		t.Fatalf("AddSent() returned an error: %+v", err)
+	}
+
+	fpType, request, key, err := s.GetFingerprint(sr.fingerprint)
+	if err != nil {
+		t.Errorf("GetFingerprint() returned an error: %+v", err)
+	}
+	if fpType != Specific {
+		t.Errorf("GetFingerprint() returned incorrect FingerprintType."+
+			"\n\texpected: %d\n\treceived: %d", Specific, fpType)
+	}
+	if request == nil {
+		t.Errorf("GetFingerprint() returned incorrect request."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+	if key != nil {
+		t.Errorf("GetFingerprint() returned incorrect key."+
+			"\n\texpected: %v\n\treceived: %s", nil, key.Text(10))
+	}
+}
+
+// Error path: fingerprint does not exist.
+func TestStore_GetFingerprint_FingerprintError(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	fpType, request, key, err := s.GetFingerprint(format.Fingerprint{32})
+	if err == nil {
+		t.Error("GetFingerprint() did not return an error when the " +
+			"fingerprint should not be found.")
+	}
+	if fpType != 0 {
+		t.Errorf("GetFingerprint() returned incorrect FingerprintType."+
+			"\n\texpected: %d\n\treceived: %d", 0, fpType)
+	}
+	if request != nil {
+		t.Errorf("GetFingerprint() returned incorrect request."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+	if key != nil {
+		t.Errorf("GetFingerprint() returned incorrect key."+
+			"\n\texpected: %v\n\treceived: %v", nil, key)
+	}
+}
+
+// Error path: fingerprint has an invalid type.
+func TestStore_GetFingerprint_InvalidFingerprintType(t *testing.T) {
+	s, _, privKeys := makeTestStore(t)
+
+	fp := auth.MakeRequestFingerprint(privKeys[0])
+	s.fingerprints[fp] = fingerprint{
+		Type:    0,
+		PrivKey: s.fingerprints[fp].PrivKey,
+		Request: s.fingerprints[fp].Request,
+	}
+	fpType, request, key, err := s.GetFingerprint(fp)
+	if err == nil {
+		t.Error("GetFingerprint() did not return an error when the " +
+			"FingerprintType is invalid.")
+	}
+	if fpType != 0 {
+		t.Errorf("GetFingerprint() returned incorrect FingerprintType."+
+			"\n\texpected: %d\n\treceived: %d", 0, fpType)
+	}
+	if request != nil {
+		t.Errorf("GetFingerprint() returned incorrect request."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+	if key != nil {
+		t.Errorf("GetFingerprint() returned incorrect key."+
+			"\n\texpected: %v\n\treceived: %v", nil, key)
+	}
+}
+
+// Happy path.
+func TestStore_GetReceivedRequest(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+
+	testC, err := s.GetReceivedRequest(c.ID)
+	if err != nil {
+		t.Errorf("GetReceivedRequest() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(c, testC) {
+		t.Errorf("GetReceivedRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", c, testC)
+	}
+
+	// Check if the request's mutex is locked
+	if reflect.ValueOf(&s.requests[*c.ID].mux).Elem().FieldByName("state").Int() != 1 {
+		t.Errorf("GetReceivedRequest() did not lock mutex.")
+	}
+}
+
+// Error path: request is deleted between first and second check.
+func TestStore_GetReceivedRequest_RequestDeleted(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+
+	r := s.requests[*c.ID]
+	r.mux.Lock()
+
+	go func() {
+		delete(s.requests, *c.ID)
+		r.mux.Unlock()
+	}()
+
+	testC, err := s.GetReceivedRequest(c.ID)
+	if err == nil {
+		t.Errorf("GetReceivedRequest() did not return an error when the " +
+			"request should not exist.")
+	}
+
+	if !reflect.DeepEqual(contact.Contact{}, testC) {
+		t.Errorf("GetReceivedRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", contact.Contact{}, testC)
+	}
+
+	// Check if the request's mutex is locked
+	if reflect.ValueOf(&r.mux).Elem().FieldByName("state").Int() != 0 {
+		t.Errorf("GetReceivedRequest() did not unlock mutex.")
+	}
+}
+
+// Error path: request does not exist.
+func TestStore_GetReceivedRequest_RequestNotInMap(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	testC, err := s.GetReceivedRequest(id.NewIdFromUInt(rand.Uint64(), id.User, t))
+	if err == nil {
+		t.Errorf("GetReceivedRequest() did not return an error when the " +
+			"request should not exist.")
+	}
+
+	if !reflect.DeepEqual(contact.Contact{}, testC) {
+		t.Errorf("GetReceivedRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", contact.Contact{}, testC)
+	}
+}
+
+// Happy path.
+func TestStore_GetReceivedRequestData(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+
+	testC, err := s.GetReceivedRequestData(c.ID)
+	if err != nil {
+		t.Errorf("GetReceivedRequestData() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(c, testC) {
+		t.Errorf("GetReceivedRequestData() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", c, testC)
+	}
+}
+
+// Error path: request does not exist.
+func TestStore_GetReceivedRequestData_RequestNotInMap(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	testC, err := s.GetReceivedRequestData(id.NewIdFromUInt(rand.Uint64(), id.User, t))
+	if err == nil {
+		t.Errorf("GetReceivedRequestData() did not return an error when the " +
+			"request should not exist.")
+	}
+
+	if !reflect.DeepEqual(contact.Contact{}, testC) {
+		t.Errorf("GetReceivedRequestData() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", contact.Contact{}, testC)
+	}
+}
+
+// Happy path: request is of type Receive.
+func TestStore_GetRequest_ReceiveRequest(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+
+	rType, request, con, err := s.GetRequest(c.ID)
+	if err != nil {
+		t.Errorf("GetRequest() returned an error: %+v", err)
+	}
+	if rType != Receive {
+		t.Errorf("GetRequest() returned incorrect RequestType."+
+			"\n\texpected: %d\n\treceived: %d", Receive, rType)
+	}
+	if request != nil {
+		t.Errorf("GetRequest() returned incorrect SentRequest."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+	if !reflect.DeepEqual(c, con) {
+		t.Errorf("GetRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", c, con)
+	}
+}
+
+// Happy path: request is of type Sent.
+func TestStore_GetRequest_SentRequest(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	sr := &SentRequest{
+		kv:                      s.kv,
+		partner:                 id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		partnerHistoricalPubKey: s.grp.NewInt(1),
+		myPrivKey:               s.grp.NewInt(2),
+		myPubKey:                s.grp.NewInt(3),
+		fingerprint:             format.Fingerprint{5},
+	}
+	if err := s.AddSent(sr.partner, sr.partnerHistoricalPubKey, sr.myPrivKey,
+		sr.myPubKey, sr.fingerprint); err != nil {
+		t.Fatalf("AddSent() returned an error: %+v", err)
+	}
+
+	rType, request, con, err := s.GetRequest(sr.partner)
+	if err != nil {
+		t.Errorf("GetRequest() returned an error: %+v", err)
+	}
+	if rType != Sent {
+		t.Errorf("GetRequest() returned incorrect RequestType."+
+			"\n\texpected: %d\n\treceived: %d", Sent, rType)
+	}
+	if !reflect.DeepEqual(sr, request) {
+		t.Errorf("GetRequest() returned incorrect SentRequest."+
+			"\n\texpected: %+v\n\treceived: %+v", sr, request)
+	}
+	if !reflect.DeepEqual(contact.Contact{}, con) {
+		t.Errorf("GetRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", contact.Contact{}, con)
+	}
+}
+
+// Error path: request type is invalid.
+func TestStore_GetRequest_InvalidType(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	s.requests[*uid] = &request{rt: 42}
+
+	rType, request, con, err := s.GetRequest(uid)
+	if err == nil {
+		t.Errorf("GetRequest() did not return an error when the request " +
+			"type should be invalid.")
+	}
+	if rType != 0 {
+		t.Errorf("GetRequest() returned incorrect RequestType."+
+			"\n\texpected: %d\n\treceived: %d", 0, rType)
+	}
+	if request != nil {
+		t.Errorf("GetRequest() returned incorrect SentRequest."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+	if !reflect.DeepEqual(contact.Contact{}, con) {
+		t.Errorf("GetRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", contact.Contact{}, con)
+	}
+}
+
+// Error path: request does not exist in map.
+func TestStore_GetRequest_RequestNotInMap(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+
+	rType, request, con, err := s.GetRequest(uid)
+	if err == nil {
+		t.Errorf("GetRequest() did not return an error when the request " +
+			"was not in the map.")
+	}
+	if rType != 0 {
+		t.Errorf("GetRequest() returned incorrect RequestType."+
+			"\n\texpected: %d\n\treceived: %d", 0, rType)
+	}
+	if request != nil {
+		t.Errorf("GetRequest() returned incorrect SentRequest."+
+			"\n\texpected: %+v\n\treceived: %+v", nil, request)
+	}
+	if !reflect.DeepEqual(contact.Contact{}, con) {
+		t.Errorf("GetRequest() returned incorrect Contact."+
+			"\n\texpected: %+v\n\treceived: %+v", contact.Contact{}, con)
+	}
+}
+
+// Happy path.
+func TestStore_Fail(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+	if _, err := s.GetReceivedRequest(c.ID); err != nil {
+		t.Fatalf("GetReceivedRequest() returned an error: %+v", err)
+	}
+
+	defer func() {
+		if r := recover(); r != nil {
+			t.Errorf("The code did not panic")
+		}
+	}()
+
+	s.Fail(c.ID)
+
+	// Check if the request's mutex is locked
+	if reflect.ValueOf(&s.requests[*c.ID].mux).Elem().FieldByName("state").Int() != 0 {
+		t.Errorf("Fail() did not unlock mutex.")
+	}
+}
+
+// Error path: request does not exist.
+func TestStore_Fail_RequestNotInMap(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Fail() did not panic when the request is not in map.")
+		}
+	}()
+
+	s.Fail(id.NewIdFromUInt(rand.Uint64(), id.User, t))
+}
+
+// Happy path: receive request.
+func TestStore_Delete_ReceiveRequest(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	c := contact.Contact{ID: id.NewIdFromUInt(rand.Uint64(), id.User, t)}
+	if err := s.AddReceived(c); err != nil {
+		t.Fatalf("AddReceived() returned an error: %+v", err)
+	}
+	if _, err := s.GetReceivedRequest(c.ID); err != nil {
+		t.Fatalf("GetReceivedRequest() returned an error: %+v", err)
+	}
+
+	err := s.Delete(c.ID)
+	if err != nil {
+		t.Errorf("delete() returned an error: %+v", err)
+	}
+
+	if s.requests[*c.ID] != nil {
+		t.Errorf("delete() failed to delete request for user %s.", c.ID)
+	}
+}
+
+// Happy path: sent request.
+func TestStore_Delete_SentRequest(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+	sr := &SentRequest{
+		kv:                      s.kv,
+		partner:                 id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		partnerHistoricalPubKey: s.grp.NewInt(1),
+		myPrivKey:               s.grp.NewInt(2),
+		myPubKey:                s.grp.NewInt(3),
+		fingerprint:             format.Fingerprint{5},
+	}
+	if err := s.AddSent(sr.partner, sr.partnerHistoricalPubKey, sr.myPrivKey,
+		sr.myPubKey, sr.fingerprint); err != nil {
+		t.Fatalf("AddSent() returned an error: %+v", err)
+	}
+	if _, _, _, err := s.GetFingerprint(sr.fingerprint); err != nil {
+		t.Fatalf("GetFingerprint() returned an error: %+v", err)
+	}
+
+	err := s.Delete(sr.partner)
+	if err != nil {
+		t.Errorf("delete() returned an error: %+v", err)
+	}
+
+	if s.requests[*sr.partner] != nil {
+		t.Errorf("delete() failed to delete request for user %s.", sr.partner)
+	}
+
+	if _, exists := s.fingerprints[sr.fingerprint]; exists {
+		t.Errorf("delete() failed to delete fingerprint for fp %v.", sr.fingerprint)
+	}
+}
+
+// Error path: request does not exist.
+func TestStore_Delete_RequestNotInMap(t *testing.T) {
+	s, _, _ := makeTestStore(t)
+
+	err := s.Delete(id.NewIdFromUInt(rand.Uint64(), id.User, t))
+	if err == nil {
+		t.Errorf("delete() did not return an error when the request was not " +
+			"in the map.")
+	}
+}
+
+func makeTestStore(t *testing.T) (*Store, *versioned.KV, []*cyclic.Int) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(0))
+	privKeys := make([]*cyclic.Int, 10)
+	for i := range privKeys {
+		privKeys[i] = grp.NewInt(rand.Int63n(170) + 1)
+	}
+
+	store, err := NewStore(kv, grp, privKeys)
+	if err != nil {
+		t.Fatalf("Failed to create new Store: %+v", err)
+	}
+
+	return store, kv, privKeys
+}
diff --git a/storage/clientVersion/store.go b/storage/clientVersion/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f1eca48aca688113ba8c240ad8e747029947a90
--- /dev/null
+++ b/storage/clientVersion/store.go
@@ -0,0 +1,117 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package clientVersion
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/primitives/version"
+	"sync"
+	"time"
+)
+
+const (
+	prefix       = "clientVersionStore"
+	storeKey     = "clientVersion"
+	storeVersion = 0
+)
+
+// Store stores the version of the client's storage.
+type Store struct {
+	version version.Version
+	kv      *versioned.KV
+	sync.RWMutex
+}
+
+// NewStore returns a new clientVersion store.
+func NewStore(newVersion version.Version, kv *versioned.KV) (*Store, error) {
+	s := &Store{
+		version: newVersion,
+		kv:      kv.Prefix(prefix),
+	}
+
+	return s, s.save()
+}
+
+// LoadStore loads the clientVersion storage object.
+func LoadStore(kv *versioned.KV) (*Store, error) {
+	s := &Store{
+		kv: kv.Prefix(prefix),
+	}
+
+	obj, err := s.kv.Get(storeKey, storeVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	s.version, err = version.ParseVersion(string(obj.Data))
+	if err != nil {
+		return nil, errors.Errorf("failed to parse client version: %+v", err)
+	}
+
+	return s, nil
+}
+
+// Get returns the stored version.
+func (s *Store) Get() version.Version {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.version
+}
+
+// CheckUpdateRequired determines if the storage needs to be upgraded to the new
+// client version. It returns true if an update is required (new > stored) and
+// false otherwise. The old stored version is returned to be used to determine
+// how to upgrade storage. If the new version is older than the stored version,
+// an error is returned.
+func (s *Store) CheckUpdateRequired(newVersion version.Version) (bool, version.Version, error) {
+	s.Lock()
+	defer s.Unlock()
+
+	oldVersion := s.version
+	diff := version.Cmp(oldVersion, newVersion)
+
+	switch {
+	case diff < 0:
+		return true, oldVersion, s.update(newVersion)
+	case diff > 0:
+		return false, oldVersion, errors.Errorf("new version (%s) is older "+
+			"than stored version (%s).", &newVersion, &oldVersion)
+	default:
+		return false, oldVersion, nil
+	}
+}
+
+// update replaces the current version with the new version if it is newer. Note
+// that this function does not take a lock.
+func (s *Store) update(newVersion version.Version) error {
+	jww.DEBUG.Printf("Updating stored client version from %s to %s.",
+		&s.version, &newVersion)
+
+	// Update version
+	s.version = newVersion
+
+	// Save new version to storage
+	return s.save()
+}
+
+// save stores the clientVersion store. Note that this function does not take
+// a lock.
+func (s *Store) save() error {
+	timeNow := time.Now()
+
+	obj := versioned.Object{
+		Version:   storeVersion,
+		Timestamp: timeNow,
+		Data:      []byte(s.version.String()),
+	}
+
+	return s.kv.Set(storeKey, storeVersion, &obj)
+}
diff --git a/storage/clientVersion/store_test.go b/storage/clientVersion/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..22da1257c04f0b7780d63b46c0cf28a86bd6143d
--- /dev/null
+++ b/storage/clientVersion/store_test.go
@@ -0,0 +1,237 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package clientVersion
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/version"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestNewStore(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	expected := &Store{
+		version: version.New(42, 43, "44"),
+		kv:      kv.Prefix(prefix),
+	}
+
+	test, err := NewStore(expected.version, kv)
+	if err != nil {
+		t.Errorf("NewStore() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expected, test) {
+		t.Errorf("NewStore() failed to return the expected Store."+
+			"\nexpected: %+v\nreceived: %+v", expected, test)
+	}
+}
+
+// Happy path.
+func TestLoadStore(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	ver := version.New(1, 2, "3A")
+
+	expected := &Store{
+		version: ver,
+		kv:      kv.Prefix(prefix),
+	}
+	err := expected.save()
+	if err != nil {
+		t.Fatalf("Failed to save Store: %+v", err)
+	}
+
+	test, err := LoadStore(kv)
+	if err != nil {
+		t.Errorf("LoadStore() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expected, test) {
+		t.Errorf("LoadStore() failed to return the expected Store."+
+			"\nexpected: %+v\nreceived: %+v", expected, test)
+	}
+}
+
+// Error path: an error is returned when the loaded Store has an invalid version
+// that fails to be parsed.
+func TestLoadStore_ParseVersionError(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	obj := versioned.Object{
+		Version:   storeVersion,
+		Timestamp: time.Now(),
+		Data:      []byte("invalid version"),
+	}
+
+	err := kv.Prefix(prefix).Set(storeKey, storeVersion, &obj)
+	if err != nil {
+		t.Fatalf("Failed to save Store: %+v", err)
+	}
+
+	_, err = LoadStore(kv)
+	if err == nil || !strings.Contains(err.Error(), "failed to parse client version") {
+		t.Errorf("LoadStore() did not return an error when the client version "+
+			"is invalid: %+v", err)
+	}
+}
+
+// Happy path.
+func TestStore_Get(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	expected := version.New(1, 2, "3A")
+
+	s := &Store{
+		version: expected,
+		kv:      kv.Prefix(prefix),
+	}
+
+	test := s.Get()
+	if !reflect.DeepEqual(expected, test) {
+		t.Errorf("Get() failed to return the expected version."+
+			"\nexpected: %s\nreceived: %s", &expected, &test)
+	}
+}
+
+// Happy path.
+func TestStore_CheckUpdateRequired(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	storedVersion := version.New(1, 2, "3")
+	newVersion := version.New(2, 3, "4")
+	s, err := NewStore(storedVersion, kv)
+	if err != nil {
+		t.Fatalf("Failed to generate a new Store: %+v", err)
+	}
+
+	updateRequired, oldVersion, err := s.CheckUpdateRequired(newVersion)
+	if err != nil {
+		t.Errorf("CheckUpdateRequired() returned an error: %+v", err)
+	}
+
+	if !updateRequired {
+		t.Errorf("CheckUpdateRequired() did not indicate that an update is "+
+			"required when the new Version (%s) is newer than the stored"+
+			"version (%s)", &newVersion, &storedVersion)
+	}
+
+	if !version.Equal(storedVersion, oldVersion) {
+		t.Errorf("CheckUpdateRequired() did return the expected old Version."+
+			"\nexpected: %s\nreceived: %s", &storedVersion, &oldVersion)
+	}
+}
+
+// Happy path: the new version is equal to the stored version.
+func TestStore_CheckUpdateRequired_EqualVersions(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	storedVersion := version.New(2, 3, "3")
+	newVersion := version.New(2, 3, "4")
+	s, err := NewStore(storedVersion, kv)
+	if err != nil {
+		t.Fatalf("Failed to generate a new Store: %+v", err)
+	}
+
+	updateRequired, oldVersion, err := s.CheckUpdateRequired(newVersion)
+	if err != nil {
+		t.Errorf("CheckUpdateRequired() returned an error: %+v", err)
+	}
+
+	if updateRequired {
+		t.Errorf("CheckUpdateRequired() did not indicate that an update is required "+
+			"when the new Version (%s) is equal to the stored version (%s)",
+			&newVersion, &storedVersion)
+	}
+
+	if !version.Equal(storedVersion, oldVersion) {
+		t.Errorf("CheckUpdateRequired() did return the expected old Version."+
+			"\nexpected: %s\nreceived: %s", &storedVersion, &oldVersion)
+	}
+}
+
+// Error path: new version is older than stored version.
+func TestStore_CheckUpdateRequired_NewVersionTooOldError(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	storedVersion := version.New(2, 3, "4")
+	newVersion := version.New(1, 2, "3")
+	s, err := NewStore(storedVersion, kv)
+	if err != nil {
+		t.Fatalf("Failed to generate a new Store: %+v", err)
+	}
+
+	updateRequired, oldVersion, err := s.CheckUpdateRequired(newVersion)
+	if err == nil || !strings.Contains(err.Error(), "older than stored version") {
+		t.Errorf("CheckUpdateRequired() did not return an error when the new version "+
+			"is older than the stored version: %+v", err)
+	}
+
+	if updateRequired {
+		t.Errorf("CheckUpdateRequired() indicated that an update is required when the "+
+			"new Version (%s) is older than the stored version (%s)",
+			&newVersion, &storedVersion)
+	}
+
+	if !version.Equal(storedVersion, oldVersion) {
+		t.Errorf("CheckUpdateRequired() did return the expected old Version."+
+			"\nexpected: %s\nreceived: %s", &storedVersion, &oldVersion)
+	}
+}
+
+// Happy path.
+func TestStore_update(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	ver1 := version.New(1, 2, "3A")
+	ver2 := version.New(1, 5, "patch5")
+
+	s := &Store{
+		version: ver1,
+		kv:      kv.Prefix(prefix),
+	}
+
+	err := s.update(ver2)
+	if err != nil {
+		t.Errorf("Update() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(ver2, s.version) {
+		t.Errorf("Update() did not set the correct version."+
+			"\nexpected: %s\nreceived: %s", &ver2, &s.version)
+	}
+}
+
+// Happy path.
+func TestStore_save(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	ver := version.New(1, 2, "3A")
+
+	s := &Store{
+		version: ver,
+		kv:      kv.Prefix(prefix),
+	}
+
+	err := s.save()
+	if err != nil {
+		t.Errorf("save() returned an error: %+v", err)
+	}
+
+	obj, err := s.kv.Get(storeKey, storeVersion)
+	if err != nil {
+		t.Errorf("Failed to load clientVersion store: %+v", err)
+	}
+
+	if ver.String() != string(obj.Data) {
+		t.Errorf("Failed to get correct data from stored object."+
+			"\nexpected: %s\nreceived: %s", ver.String(), obj.Data)
+	}
+
+	if storeVersion != obj.Version {
+		t.Errorf("Failed to get correct version from stored object."+
+			"\nexpected: %d\nreceived: %d", storeVersion, obj.Version)
+	}
+
+}
diff --git a/storage/cmix/key.go b/storage/cmix/key.go
new file mode 100644
index 0000000000000000000000000000000000000000..3a7f6400e238f84a481a08449aaa05c02374a48a
--- /dev/null
+++ b/storage/cmix/key.go
@@ -0,0 +1,112 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package cmix
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const currentKeyVersion = 0
+
+type key struct {
+	kv *versioned.KV
+	k  *cyclic.Int
+
+	storeKey string
+}
+
+func newKey(kv *versioned.KV, k *cyclic.Int, id *id.ID) *key {
+	nk := &key{
+		kv:       kv,
+		k:        k,
+		storeKey: keyKey(id),
+	}
+
+	if err := nk.save(); err != nil {
+		jww.FATAL.Panicf("Failed to make nodeKey for %s: %s", id, err)
+	}
+
+	return nk
+}
+
+// returns the cyclic key
+func (k *key) Get() *cyclic.Int {
+	return k.k
+}
+
+// loads the key for the given node id from the versioned keystore
+func loadKey(kv *versioned.KV, id *id.ID) (*key, error) {
+	k := &key{}
+
+	key := keyKey(id)
+
+	obj, err := kv.Get(key, currentKeyVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	err = k.unmarshal(obj.Data)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return k, nil
+}
+
+// saves the key as the key for the given node ID in the passed keystore
+func (k *key) save() error {
+	now := time.Now()
+
+	data, err := k.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentKeyVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return k.kv.Set(k.storeKey, currentKeyVersion, &obj)
+}
+
+// deletes the key from the versioned keystore
+func (k *key) delete(kv *versioned.KV, id *id.ID) {
+	key := keyKey(id)
+	if err := kv.Delete(key, currentKeyVersion); err != nil {
+		jww.FATAL.Panicf("Failed to delete key %s: %s", k, err)
+	}
+}
+
+// makes a binary representation of the given key in the keystore
+func (k *key) marshal() ([]byte, error) {
+	return k.k.GobEncode()
+}
+
+// resets the data of the key from the binary representation of the key passed in
+func (k *key) unmarshal(b []byte) error {
+	k.k = &cyclic.Int{}
+	return k.k.GobDecode(b)
+}
+
+// Adheres to the stringer interface
+func (k *key) String() string {
+	return k.storeKey
+
+}
+
+// generates the key used in the keystore for the given key
+func keyKey(id *id.ID) string {
+	return "nodeKey:" + id.String()
+}
diff --git a/storage/cmix/key_test.go b/storage/cmix/key_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..27e53eaa0ccb3971085ea4f11d42b56849eed47f
--- /dev/null
+++ b/storage/cmix/key_test.go
@@ -0,0 +1,8 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package cmix
diff --git a/storage/cmix/roundKeys.go b/storage/cmix/roundKeys.go
new file mode 100644
index 0000000000000000000000000000000000000000..10d1e6cc9070c26fae9c67bb1ddc90cd79265517
--- /dev/null
+++ b/storage/cmix/roundKeys.go
@@ -0,0 +1,100 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package cmix
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cmix"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"golang.org/x/crypto/blake2b"
+)
+
+type RoundKeys struct {
+	keys []*key
+	g    *cyclic.Group
+}
+
+// Encrypts the given message for CMIX
+// Panics if the passed message is not sized correctly for the group
+func (rk *RoundKeys) Encrypt(msg format.Message,
+	salt []byte, roundID id.Round) (format.Message, [][]byte) {
+
+	if msg.GetPrimeByteLen() != rk.g.GetP().ByteLen() {
+		jww.FATAL.Panicf("Cannot encrypt message whose size does not " +
+			"align with the size of the prime")
+	}
+
+	keys := make([]*cyclic.Int, len(rk.keys))
+
+	for i, k := range rk.keys {
+		jww.TRACE.Printf("CMIXKEY: num: %d, key: %s", i, k.Get().Text(16))
+		keys[i] = k.Get()
+	}
+
+	ecrMsg := ClientEncrypt(rk.g, msg, salt, roundID, keys)
+
+	h, err := hash.NewCMixHash()
+	if err != nil {
+		jww.FATAL.Panicf("Cound not get hash for KMAC generation: %+v", h)
+	}
+
+	KMAC := cmix.GenerateKMACs(salt, keys, roundID, h)
+
+	return ecrMsg, KMAC
+}
+
+func (rk *RoundKeys) MakeClientGatewayKey(salt, digest []byte) []byte {
+	clientGatewayKey := cmix.GenerateClientGatewayKey(rk.keys[0].k)
+	h, _ := hash.NewCMixHash()
+	h.Write(clientGatewayKey)
+	h.Write(salt)
+	hashed := h.Sum(nil)
+	h.Reset()
+	h.Write(hashed)
+	h.Write(digest)
+	return h.Sum(nil)
+}
+
+func ClientEncrypt(grp *cyclic.Group, msg format.Message,
+	salt []byte, roundID id.Round, baseKeys []*cyclic.Int) format.Message {
+
+	// Get the salt for associated data
+	hash, err := blake2b.New256(nil)
+	if err != nil {
+		panic("E2E Client Encrypt could not get blake2b Hash")
+	}
+	hash.Reset()
+	hash.Write(salt)
+	salt2 := hash.Sum(nil)
+
+	// Get encryption keys
+	keyEcrA := cmix.ClientKeyGen(grp, salt, roundID, baseKeys)
+	keyEcrB := cmix.ClientKeyGen(grp, salt2, roundID, baseKeys)
+
+	// Get message payloads as cyclic integers
+	payloadA := grp.NewIntFromBytes(msg.GetPayloadA())
+	payloadB := grp.NewIntFromBytes(msg.GetPayloadB())
+
+	// Encrypt payload A with the key
+	EcrPayloadA := grp.Mul(keyEcrA, payloadA, grp.NewInt(1))
+	EcrPayloadB := grp.Mul(keyEcrB, payloadB, grp.NewInt(1))
+
+	primeLen := grp.GetP().ByteLen()
+
+	// Create the encrypted message
+	encryptedMsg := format.NewMessage(primeLen)
+
+	encryptedMsg.SetPayloadA(EcrPayloadA.LeftpadBytes(uint64(primeLen)))
+	encryptedMsg.SetPayloadB(EcrPayloadB.LeftpadBytes(uint64(primeLen)))
+
+	return encryptedMsg
+
+}
diff --git a/storage/cmix/roundKeys_test.go b/storage/cmix/roundKeys_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..de839aa2a382d7713fd72ddb8873aa05eae1a583
--- /dev/null
+++ b/storage/cmix/roundKeys_test.go
@@ -0,0 +1,130 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package cmix
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// tests that the encrypted paylaods and kmacs generated are consistent
+func TestRoundKeys_Encrypt_Consistency(t *testing.T) {
+	const numKeys = 5
+
+	expectedPayload := []byte{107, 20, 177, 34, 255, 243, 201, 126, 124, 105, 4,
+		62, 204, 52, 56, 2, 60, 196, 105, 167, 80, 78, 189, 83, 248, 113, 207,
+		34, 255, 55, 37, 48, 75, 130, 200, 218, 88, 16, 29, 171, 26, 26, 77, 59,
+		244, 111, 117, 236, 102, 86, 32, 31, 223, 26, 151, 112, 191, 183, 152,
+		18, 104, 58, 49, 42, 77, 233, 163, 193, 36, 7, 44, 173, 99, 65, 24, 127,
+		197, 96, 51, 69, 8, 154, 35, 119, 147, 80, 113, 55, 173, 129, 151, 195,
+		56, 11, 92, 2, 181, 135, 1, 114, 12, 197, 55, 252, 123, 89, 92, 185, 87,
+		215, 193, 203, 199, 224, 58, 173, 193, 159, 166, 22, 60, 138, 97, 15,
+		173, 213, 45, 236, 7, 66, 39, 168, 21, 26, 210, 66, 176, 135, 131, 113,
+		157, 53, 120, 128, 187, 167, 127, 170, 248, 215, 158, 18, 61, 158, 137,
+		62, 120, 254, 114, 93, 78, 11, 13, 104, 94, 232, 98, 108, 238, 42, 181,
+		221, 128, 124, 188, 119, 13, 101, 7, 61, 85, 19, 20, 140, 32, 101, 39,
+		151, 93, 134, 78, 155, 100, 110, 192, 76, 62, 249, 91, 105, 225, 180,
+		95, 197, 101, 80, 8, 93, 139, 78, 109, 197, 255, 218, 6, 167, 49, 61,
+		184, 178, 174, 155, 147, 238, 228, 169, 27, 175, 119, 76, 217, 240, 1,
+		134, 114, 3, 179, 223, 152, 68, 152, 221, 44, 128, 55, 165, 206, 116,
+		88, 188, 72, 41, 41, 9, 67, 188, 182, 118, 213, 25, 237, 146, 170, 80,
+		42, 101, 230, 87, 244, 170, 176, 110, 94, 43, 110, 200, 54, 126, 206,
+		252, 182, 21, 207, 142, 170, 150, 34, 155, 99, 110, 131, 120, 137, 255,
+		200, 132, 249, 213, 180, 121, 235, 126, 30, 149, 18, 8, 159, 153, 73,
+		71, 104, 246, 231, 168, 201, 108, 42, 10, 110, 35, 183, 160, 15, 11,
+		171, 117, 0, 87, 251, 218, 121, 155, 237, 58, 24, 139, 217, 62, 238,
+		255, 116, 172, 135, 221, 207, 163, 214, 62, 1, 144, 245, 233, 147, 188,
+		67, 97, 161, 79, 109, 129, 114, 21, 183, 66, 54, 242, 120, 91, 158, 35,
+		110, 167, 44, 54, 87, 208, 145, 212, 59, 160, 115, 214, 146, 201, 199,
+		104, 86, 140, 131, 189, 146, 47, 165, 197, 90, 100, 105, 16, 223, 96,
+		86, 132, 221, 190, 175, 241, 121, 157, 19, 190, 243, 191, 116, 92, 31,
+		209, 147, 7, 233, 188, 114, 88, 225, 180, 52, 139, 70, 88, 193, 111,
+		49, 209, 4, 19, 135, 206, 56, 164, 230, 222, 219, 153, 94, 163, 168,
+		181, 185, 206, 124, 13, 179, 32, 93, 85, 6, 179, 57, 197, 89, 254,
+		180, 133, 147, 174, 182, 38, 8, 127, 20, 133, 100, 20, 228, 62, 252,
+		175, 50, 239, 179, 108, 59, 222, 29, 113, 140, 2, 104, 167, 175, 193,
+		208, 149, 24, 135, 165, 106, 249, 164, 122, 139, 169, 193, 39, 209, 132,
+		238, 23, 153, 115, 200, 104, 31}
+
+
+		expectedKmacs := [][]byte{{110, 235, 79, 128, 16, 94, 181, 95, 101,
+				152, 187, 204, 87, 236, 211, 102, 88, 130, 191, 103, 23, 229,
+				48, 142, 155, 167, 200, 108, 66, 172, 178, 209},
+			{48, 74, 148, 205, 235, 46, 172, 128, 28, 42, 116, 27, 64, 83, 122,
+				5, 51, 162, 200, 198, 234, 92, 77, 131, 136, 108, 57, 97, 193,
+				208, 148, 217},
+			{202, 163, 19, 179, 175, 100, 71, 176, 241, 80, 85, 174, 120, 45,
+				152, 117, 82, 193, 203, 188, 158, 60, 111, 217, 64, 47, 219,
+				169, 100, 177, 42, 159},
+			{66, 121, 20, 21, 206, 142, 3, 75, 229, 94, 197, 4, 117, 223, 245,
+				117, 14, 17, 158, 138, 176, 106, 93, 55, 247, 155, 250, 232,
+				41, 169, 197, 150},
+			{65, 74, 222, 172, 217, 13, 56, 208, 111, 98, 199, 205, 74, 141, 30,
+				109, 51, 20, 186, 9, 234, 197, 6, 200, 139, 86, 139, 130, 8, 15,
+				32, 209}}
+
+
+	cmixGrp := cyclic.NewGroup(
+		large.NewIntFromString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+
+			"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+
+			"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+
+			"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+
+			"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"+
+			"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"+
+			"83655D23DCA3AD961C62F356208552BB9ED529077096966D"+
+			"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"+
+			"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"+
+			"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"+
+			"15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16),
+		large.NewIntFromString("2", 16))
+
+	prng := rand.New(rand.NewSource(42))
+
+	keys := make([]*key, numKeys)
+
+	for i := 0; i < numKeys; i++ {
+		keyBytes, _ := csprng.GenerateInGroup(cmixGrp.GetPBytes(), cmixGrp.GetP().ByteLen(), prng)
+		keys[i] = &key{
+			k: cmixGrp.NewIntFromBytes(keyBytes),
+		}
+	}
+
+	salt := make([]byte, 32)
+	prng.Read(salt)
+
+	msg := format.NewMessage(cmixGrp.GetP().ByteLen())
+	contents := make([]byte, msg.ContentsSize())
+	prng.Read(contents)
+	msg.SetContents(contents)
+
+	rk := RoundKeys{
+		keys: keys,
+		g:    cmixGrp,
+	}
+
+	rid := id.Round(42)
+
+	encMsg, kmacs := rk.Encrypt(msg, salt, rid)
+
+	if !bytes.Equal(encMsg.Marshal(), expectedPayload) {
+		t.Errorf("Encrypted messages do not match\n "+
+			"expected: %v\n received: %v", expectedPayload, encMsg.Marshal())
+	}
+
+	if !reflect.DeepEqual(kmacs, expectedKmacs) {
+		t.Errorf("kmacs do not match\n "+
+			"expected: %v\n received: %v", expectedKmacs, kmacs)
+	}
+}
diff --git a/storage/cmix/store.go b/storage/cmix/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..ad0a10cd0995abca38207dd1ea77ff5d0dc905ae
--- /dev/null
+++ b/storage/cmix/store.go
@@ -0,0 +1,260 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package cmix
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+const prefix = "cmix"
+const currentStoreVersion = 0
+const (
+	storeKey   = "KeyStore"
+	pubKeyKey  = "DhPubKey"
+	privKeyKey = "DhPrivKey"
+	grpKey     = "GroupKey"
+)
+
+type Store struct {
+	nodes        map[id.ID]*key
+	dhPrivateKey *cyclic.Int
+	dhPublicKey  *cyclic.Int
+
+	grp *cyclic.Group
+	kv  *versioned.KV
+	mux sync.RWMutex
+}
+
+// NewStore returns a new cMix storage object.
+func NewStore(grp *cyclic.Group, kv *versioned.KV, priv *cyclic.Int) (*Store, error) {
+	// Generate public key
+	pub := diffieHellman.GeneratePublicKey(priv, grp)
+	kv = kv.Prefix(prefix)
+
+	s := &Store{
+		nodes:        make(map[id.ID]*key),
+		dhPrivateKey: priv,
+		dhPublicKey:  pub,
+		grp:          grp,
+		kv:           kv,
+	}
+
+	err := utility.StoreCyclicKey(kv, pub, pubKeyKey)
+	if err != nil {
+		return nil,
+			errors.WithMessage(err, "Failed to store cMix DH public key")
+	}
+
+	err = utility.StoreCyclicKey(kv, priv, privKeyKey)
+	if err != nil {
+		return nil,
+			errors.WithMessage(err, "Failed to store cMix DH private key")
+	}
+
+	err = utility.StoreGroup(kv, grp, grpKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to store cMix group")
+	}
+
+	return s, s.save()
+}
+
+// LoadStore loads the cMix storage object.
+func LoadStore(kv *versioned.KV) (*Store, error) {
+	kv = kv.Prefix(prefix)
+	s := &Store{
+		nodes: make(map[id.ID]*key),
+		kv:    kv,
+	}
+
+	obj, err := kv.Get(storeKey, currentKeyVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	err = s.unmarshal(obj.Data)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return s, nil
+}
+
+// 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) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	nodeKey := newKey(s.kv, k, nid)
+
+	s.nodes[*nid] = nodeKey
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save nodeKey list for %s: %+v", nid, err)
+	}
+}
+
+// Remove removes a node key from the nodes map and saves.
+func (s *Store) Remove(nid *id.ID) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	nodeKey, ok := s.nodes[*nid]
+	if !ok {
+		return
+	}
+
+	nodeKey.delete(s.kv, nid)
+
+	delete(s.nodes, *nid)
+
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to make nodeKey for %s: %+v", nid, err)
+	}
+}
+
+// GetRoundKeys returns a RoundKeys for the topology and a list of nodes it did
+// not have a key for. If there are missing keys, then returns nil RoundKeys.
+func (s *Store) GetRoundKeys(topology *connect.Circuit) (*RoundKeys, []*id.ID) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	var missingNodes []*id.ID
+
+	keys := make([]*key, topology.Len())
+
+	for i := 0; i < topology.Len(); i++ {
+		nid := topology.GetNodeAtIndex(i)
+		k, ok := s.nodes[*nid]
+		if !ok {
+			missingNodes = append(missingNodes, nid)
+		} else {
+			keys[i] = k
+		}
+	}
+
+	// Handle missing keys case
+	if len(missingNodes) > 0 {
+		return nil, missingNodes
+	}
+
+	rk := &RoundKeys{
+		keys: keys,
+		g:    s.grp,
+	}
+
+	return rk, missingNodes
+}
+
+// GetDHPrivateKey returns the diffie hellman private key
+func (s *Store) GetDHPrivateKey() *cyclic.Int {
+	return s.dhPrivateKey
+}
+
+// GetDHPublicKey returns the diffie hellman public key.
+func (s *Store) GetDHPublicKey() *cyclic.Int {
+	return s.dhPublicKey
+}
+
+// GetGroup returns the cyclic group used for cMix.
+func (s *Store) GetGroup() *cyclic.Group {
+	return s.grp
+}
+
+func (s *Store) IsRegistered(nid *id.ID) bool {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	_, ok := s.nodes[*nid]
+	return ok
+}
+
+// Count returns the number of registered nodes.
+func (s *Store) Count() int {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return len(s.nodes)
+}
+
+// save stores the cMix store.
+func (s *Store) save() error {
+	now := time.Now()
+
+	data, err := s.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentStoreVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return s.kv.Set(storeKey, currentKeyVersion, &obj)
+}
+
+// marshal builds a byte representation of the Store.
+func (s *Store) marshal() ([]byte, error) {
+	nodes := make([]id.ID, len(s.nodes))
+
+	index := 0
+	for nid := range s.nodes {
+		nodes[index] = nid
+		index++
+	}
+
+	return json.Marshal(&nodes)
+}
+
+// unmarshal restores the data for a Store from the byte representation of the
+// Store
+func (s *Store) unmarshal(b []byte) error {
+	var nodes []id.ID
+
+	err := json.Unmarshal(b, &nodes)
+	if err != nil {
+		return err
+	}
+
+	for _, nid := range nodes {
+		k, err := loadKey(s.kv, &nid)
+		if err != nil {
+			return errors.WithMessagef(err, "could not load node key for %s", &nid)
+		}
+		s.nodes[nid] = k
+	}
+
+	s.dhPrivateKey, err = utility.LoadCyclicKey(s.kv, privKeyKey)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to load cMix DH private key")
+	}
+
+	s.dhPublicKey, err = utility.LoadCyclicKey(s.kv, pubKeyKey)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to load cMix DH public key")
+	}
+
+	s.grp, err = utility.LoadGroup(s.kv, grpKey)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to load cMix group")
+	}
+
+	return nil
+}
diff --git a/storage/cmix/store_test.go b/storage/cmix/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8e05e0c39438f1b22b63ea6ff1d3f3b3648b2b73
--- /dev/null
+++ b/storage/cmix/store_test.go
@@ -0,0 +1,201 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package cmix
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+// Happy path
+func TestNewStore(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := versioned.NewKV(kv)
+
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	priv := grp.NewInt(2)
+	pub := diffieHellman.GeneratePublicKey(priv, grp)
+
+	store, err := NewStore(grp, vkv, priv)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+
+	if store.nodes == nil {
+		t.Errorf("Failed to initialize nodes")
+	}
+	if store.GetDHPrivateKey() == nil || store.GetDHPrivateKey().Cmp(priv) != 0 {
+		t.Errorf("Failed to set store.dhPrivateKey correctly")
+	}
+	if store.GetDHPublicKey() == nil || store.GetDHPublicKey().Cmp(pub) != 0 {
+		t.Errorf("Failed to set store.dhPublicKey correctly")
+	}
+	if store.grp == nil {
+		t.Errorf("Failed to set store.grp")
+	}
+	if store.kv == nil {
+		t.Errorf("Failed to set store.kv")
+	}
+}
+
+// Happy path Add/Done test
+func TestStore_AddRemove(t *testing.T) {
+	// Uncomment to print keys that Set and Get are called on
+	// jww.SetStdoutThreshold(jww.LevelTrace)
+
+	testStore, _ := makeTestStore()
+
+	nodeId := id.NewIdFromString("test", id.Node, t)
+	key := testStore.grp.NewInt(5)
+
+	testStore.Add(nodeId, key)
+	if _, exists := testStore.nodes[*nodeId]; !exists {
+		t.Fatal("Failed to add node key")
+	}
+
+	testStore.Remove(nodeId)
+	if _, exists := testStore.nodes[*nodeId]; exists {
+		t.Fatal("Failed to remove node key")
+	}
+}
+
+// Happy path
+func TestLoadStore(t *testing.T) {
+	// Uncomment to print keys that Set and Get are called on
+	// jww.SetStdoutThreshold(jww.LevelTrace)
+
+	testStore, kv := makeTestStore()
+
+	// Add a test node key
+	nodeId := id.NewIdFromString("test", id.Node, t)
+	key := testStore.grp.NewInt(5)
+
+	testStore.Add(nodeId, key)
+
+	// Load the store and check its attributes
+	store, err := LoadStore(kv)
+	if err != nil {
+		t.Fatalf("Unable to load store: %+v", err)
+	}
+	if store.GetDHPublicKey().Cmp(testStore.GetDHPublicKey()) != 0 {
+		t.Errorf("LoadStore failed to load public key")
+	}
+	if store.GetDHPrivateKey().Cmp(testStore.GetDHPrivateKey()) != 0 {
+		t.Errorf("LoadStore failed to load public key")
+	}
+	if len(store.nodes) != len(testStore.nodes) {
+		t.Errorf("LoadStore failed to load node keys")
+	}
+}
+
+// Happy path
+func TestStore_GetRoundKeys(t *testing.T) {
+	// Uncomment to print keys that Set and Get are called on
+	// jww.SetStdoutThreshold(jww.LevelTrace)
+
+	testStore, _ := makeTestStore()
+	// Set up the circuit
+	numIds := 10
+	nodeIds := make([]*id.ID, numIds)
+	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)
+
+		// This is wack but it cleans up after the test
+		defer testStore.Remove(nodeIds[i])
+	}
+
+	circuit := connect.NewCircuit(nodeIds)
+	result, missing := testStore.GetRoundKeys(circuit)
+	if len(missing) != 0 {
+		t.Errorf("Expected to have no missing keys, got %d", len(missing))
+	}
+	if result == nil || len(result.keys) != numIds {
+		t.Errorf("Expected to have %d node keys", numIds)
+	}
+}
+
+// Missing keys path
+func TestStore_GetRoundKeys_Missing(t *testing.T) {
+	// Uncomment to print keys that Set and Get are called on
+	// jww.SetStdoutThreshold(jww.LevelTrace)
+
+	testStore, _ := makeTestStore()
+	// Set up the circuit
+	numIds := 10
+	nodeIds := make([]*id.ID, numIds)
+	for i := 0; i < numIds; i++ {
+		nodeIds[i] = id.NewIdFromUInt(uint64(i)+1, id.Node, t)
+		key := testStore.grp.NewInt(int64(i) + 1)
+
+		// Only add every other node so there are missing nodes
+		if i%2 == 0 {
+			testStore.Add(nodeIds[i], key)
+			testStore.Add(nodeIds[i], key)
+
+			// This is wack but it cleans up after the test
+			defer testStore.Remove(nodeIds[i])
+		}
+	}
+
+	circuit := connect.NewCircuit(nodeIds)
+	result, missing := testStore.GetRoundKeys(circuit)
+	if len(missing) != numIds/2 {
+		t.Errorf("Expected to have %d missing keys, got %d", numIds/2, len(missing))
+	}
+	if result != nil {
+		t.Errorf("Expected nil value for result due to missing keys!")
+	}
+}
+
+// Happy path.
+func TestStore_Count(t *testing.T) {
+	vkv := versioned.NewKV(make(ekv.Memstore))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+
+	store, err := NewStore(grp, vkv, grp.NewInt(2))
+	if err != nil {
+		t.Fatalf("Failed to generate new Store: %+v", err)
+	}
+
+	if store.Count() != 0 {
+		t.Errorf("Count() did not return the expected value for a new Store."+
+			"\nexpected: %d\nreceived: %d", 0, store.Count())
+	}
+
+	count := 50
+	for i := 0; i < count; i++ {
+		store.Add(id.NewIdFromUInt(uint64(i), id.Node, t), grp.NewInt(int64(42+i)))
+	}
+
+	if store.Count() != count {
+		t.Errorf("Count() did not return the expected value."+
+			"\nexpected: %d\nreceived: %d", count, store.Count())
+	}
+}
+
+// Main testing function.
+func makeTestStore() (*Store, *versioned.KV) {
+
+	kv := make(ekv.Memstore)
+	vkv := versioned.NewKV(kv)
+
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	priv := grp.NewInt(2)
+
+	testStore, _ := NewStore(grp, vkv, priv)
+
+	return testStore, vkv
+}
diff --git a/storage/conversation/partner.go b/storage/conversation/partner.go
new file mode 100644
index 0000000000000000000000000000000000000000..0fef58be68864181ccd18a7282f6be627b26c29f
--- /dev/null
+++ b/storage/conversation/partner.go
@@ -0,0 +1,205 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package conversation
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"math"
+	"strings"
+	"sync"
+	"time"
+)
+
+const (
+	currentConversationVersion = 0
+	maxTruncatedID             = math.MaxUint32
+	bottomRegion               = maxTruncatedID / 4
+	topRegion                  = bottomRegion * 3
+)
+
+type Conversation struct {
+	// Public and stored data
+	lastReceivedID         uint32
+	numReceivedRevolutions uint32
+	nextSentID             uint64
+
+	// Private and non-stored data
+	partner *id.ID
+	kv      *versioned.KV
+	mux     sync.Mutex
+}
+
+// conversationDisk stores the public data of Conversation for saving to disk.
+type conversationDisk struct {
+	// Public and stored data
+	LastReceivedID         uint32
+	NumReceivedRevolutions uint32
+	NextSendID             uint64
+}
+
+// LoadOrMakeConversation returns the Conversation with the given ID, if it can
+// be found in KV. Otherwise, a new conversation with the given ID is generated,
+// saved to KV, and returned.
+func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation {
+	c, err := loadConversation(kv, partner)
+	if err != nil && !strings.Contains(err.Error(), "Failed to Load conversation") {
+		jww.FATAL.Panicf("Failed to loadOrMakeConversation: %s", err)
+	}
+
+	// Create new conversation and save to KV if one does not exist
+	if c == nil {
+		c = &Conversation{
+			lastReceivedID:         0,
+			numReceivedRevolutions: 0,
+			nextSentID:             0,
+			partner:                partner,
+			kv:                     kv,
+		}
+
+		if err = c.save(); err != nil {
+			jww.FATAL.Panicf("Failed to save new conversation: %s", err)
+		}
+	}
+
+	return c
+}
+
+// ProcessReceivedMessageID finds the full 64-bit message ID and updates the
+// internal last message ID if the new ID is newer.
+func (c *Conversation) ProcessReceivedMessageID(mid uint32) uint64 {
+	c.mux.Lock()
+	defer c.mux.Unlock()
+
+	var high uint32
+	switch cmp(c.lastReceivedID, mid) {
+	case 1:
+		c.numReceivedRevolutions++
+		c.lastReceivedID = mid
+		if err := c.save(); err != nil {
+			jww.FATAL.Panicf("Failed to save after updating Last "+
+				"Received ID in a conversation: %s", err)
+		}
+		high = c.numReceivedRevolutions
+
+	case 0:
+		if mid > c.lastReceivedID {
+			c.lastReceivedID = mid
+			if err := c.save(); err != nil {
+				jww.FATAL.Panicf("Failed to save after updating Last "+
+					"Received ID in a conversation: %s", err)
+			}
+		}
+		high = c.numReceivedRevolutions
+
+	case -1:
+		high = c.numReceivedRevolutions - 1
+	}
+
+	return (uint64(high) << 32) | uint64(mid)
+}
+
+func cmp(a, b uint32) int {
+	if a > topRegion && b < bottomRegion {
+		return 1
+	} else if a < bottomRegion && b > topRegion {
+		return -1
+	}
+	return 0
+}
+
+// GetNextSendID returns the next sendID in both full and truncated formats.
+func (c *Conversation) GetNextSendID() (uint64, uint32) {
+	c.mux.Lock()
+	old := c.nextSentID
+	c.nextSentID++
+	if err := c.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save after incrementing the sendID: %s",
+			err)
+	}
+	c.mux.Unlock()
+	return old, uint32(old & 0x00000000FFFFFFFF)
+}
+
+// loadConversation returns the Conversation with the given ID from KV storage.
+func loadConversation(kv *versioned.KV, partner *id.ID) (*Conversation, error) {
+	key := makeConversationKey(partner)
+
+	obj, err := kv.Get(key, currentConversationVersion)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to Load conversation")
+	}
+
+	c := &Conversation{
+		partner: partner,
+		kv:      kv,
+	}
+
+	if err = c.unmarshal(obj.Data); err != nil {
+		return nil, errors.WithMessage(err, "Failed to Load conversation")
+	}
+
+	return c, nil
+}
+
+// save saves the Conversation to KV storage.
+func (c *Conversation) save() error {
+	data, err := c.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentConversationVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	}
+
+	key := makeConversationKey(c.partner)
+	return c.kv.Set(key, currentConversationVersion, &obj)
+}
+
+// delete removes the Conversation from KV storage.
+func (c *Conversation) delete() error {
+	key := makeConversationKey(c.partner)
+	return c.kv.Delete(key, currentConversationVersion)
+}
+
+func (c *Conversation) unmarshal(b []byte) error {
+	cd := conversationDisk{}
+
+	if err := json.Unmarshal(b, &cd); err != nil {
+		return errors.Wrap(err, "Failed to Unmarshal Conversation")
+	}
+
+	c.lastReceivedID = cd.LastReceivedID
+	c.numReceivedRevolutions = cd.NumReceivedRevolutions
+	c.nextSentID = cd.NextSendID
+
+	return nil
+}
+
+func (c *Conversation) marshal() ([]byte, error) {
+	cd := conversationDisk{}
+	cd.LastReceivedID = c.lastReceivedID
+	cd.NumReceivedRevolutions = c.numReceivedRevolutions
+	cd.NextSendID = c.nextSentID
+
+	b, err := json.Marshal(&cd)
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to unmarshal conversation")
+	}
+	return b, nil
+}
+
+func makeConversationKey(partner *id.ID) string {
+	return versioned.MakePartnerPrefix(partner)
+}
diff --git a/storage/conversation/partner_test.go b/storage/conversation/partner_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2381e77bf6ced1f871c949d79bc59fb943e2680
--- /dev/null
+++ b/storage/conversation/partner_test.go
@@ -0,0 +1,240 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package conversation
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// Tests happy path of LoadOrMakeConversation() when making a new Conversation.
+func TestLoadOrMakeConversation_Make(t *testing.T) {
+	// Uncomment to print keys that Set and Get are called on
+	jww.SetStdoutThreshold(jww.LevelTrace)
+	// Set up test values
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := &Conversation{
+		lastReceivedID:         0,
+		numReceivedRevolutions: 0,
+		nextSentID:             0,
+		partner:                partner,
+		kv:                     kv,
+	}
+
+	// Create new Conversation
+	conv := LoadOrMakeConversation(kv, partner)
+
+	// Check that the result matches the expected Conversation
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("LoadOrMakeConversation() made unexpected Conversation."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, conv)
+	}
+}
+
+// Tests happy path of LoadOrMakeConversation() when loading a Conversation.
+func TestLoadOrMakeConversation_Load(t *testing.T) {
+	// Set up test values
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+
+	// Load Conversation
+	conv := LoadOrMakeConversation(kv, partner)
+
+	// Check that the result matches the expected Conversation
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("LoadOrMakeConversation() made unexpected Conversation."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, conv)
+	}
+}
+
+// Tests case 1 of Conversation.ProcessReceivedMessageID().
+func TestConversation_ProcessReceivedMessageID_Case_1(t *testing.T) {
+	// Set up test values
+	mid := uint32(5)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+	expectedConv.lastReceivedID = mid
+	expectedConv.numReceivedRevolutions = 1
+	conv := LoadOrMakeConversation(kv, partner)
+	conv.lastReceivedID = topRegion + 5
+	expectedResult := uint64(expectedConv.numReceivedRevolutions)<<32 | uint64(mid)
+
+	result := conv.ProcessReceivedMessageID(mid)
+	if result != expectedResult {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"result.\n\texpected: %+v\n\trecieved: %+v",
+			expectedResult, result)
+	}
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"Conversation.\n\texpected: %+v\n\trecieved: %+v",
+			expectedConv, conv)
+	}
+}
+
+// Tests case 0 of Conversation.ProcessReceivedMessageID().
+func TestConversation_ProcessReceivedMessageID_Case_0(t *testing.T) {
+	// Set up test values
+	mid := uint32(5)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+	expectedConv.lastReceivedID = mid
+	conv := LoadOrMakeConversation(kv, partner)
+	expectedResult := uint64(expectedConv.numReceivedRevolutions)<<32 | uint64(mid)
+
+	result := conv.ProcessReceivedMessageID(mid)
+	if result != expectedResult {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"result.\n\texpected: %+v\n\trecieved: %+v",
+			expectedResult, result)
+	}
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"Conversation.\n\texpected: %+v\n\trecieved: %+v",
+			expectedConv, conv)
+	}
+}
+
+// Tests case -1 of Conversation.ProcessReceivedMessageID().
+func TestConversation_ProcessReceivedMessageID_Case_Neg1(t *testing.T) {
+	// Set up test values
+	mid := uint32(topRegion + 5)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := LoadOrMakeConversation(kv, partner)
+	expectedConv.lastReceivedID = bottomRegion - 5
+	conv := LoadOrMakeConversation(kv, partner)
+	conv.lastReceivedID = bottomRegion - 5
+	expectedResult := uint64(expectedConv.numReceivedRevolutions-1)<<32 | uint64(mid)
+
+	result := conv.ProcessReceivedMessageID(mid)
+	if result != expectedResult {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"result.\n\texpected: %+v\n\trecieved: %+v",
+			expectedResult, result)
+	}
+	if !reflect.DeepEqual(expectedConv, conv) {
+		t.Errorf("ProcessReceivedMessageID() did not product the expected "+
+			"Conversation.\n\texpected: %+v\n\trecieved: %+v",
+			expectedConv, conv)
+	}
+}
+
+// Tests happy path of Conversation.GetNextSendID().
+func TestConversation_GetNextSendID(t *testing.T) {
+	// Set up test values
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	conv := LoadOrMakeConversation(kv, partner)
+	conv.nextSentID = maxTruncatedID - 100
+
+	for i := uint64(maxTruncatedID - 100); i < maxTruncatedID+100; i++ {
+		fullID, truncID := conv.GetNextSendID()
+		if fullID != i {
+			t.Errorf("Returned incorrect full sendID."+
+				"\n\texpected: %d\n\treceived: %d", i, fullID)
+		}
+		if truncID != uint32(i) {
+			t.Errorf("Returned incorrect truncated sendID."+
+				"\n\texpected: %d\n\treceived: %d", uint32(i), truncID)
+		}
+	}
+}
+
+// Tests the happy path of save() and loadConversation().
+func TestConversation_save_load(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	expectedConv := makeRandomConv(kv, partner)
+	expectedErr := "loadConversation() produced an error: Failed to Load " +
+		"conversation: object not found"
+
+	err := expectedConv.save()
+	if err != nil {
+		t.Errorf("save() produced an error: %v", err)
+	}
+
+	testConv, err := loadConversation(kv, partner)
+	if err != nil {
+		t.Errorf("loadConversation() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedConv, testConv) {
+		t.Errorf("saving and loading Conversation failed."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, testConv)
+	}
+
+	_, err = loadConversation(versioned.NewKV(make(ekv.Memstore)), partner)
+	if err == nil {
+		t.Errorf("loadConversation() failed to produce an error."+
+			"\n\texpected: %s\n\treceived: %v", expectedErr, nil)
+	}
+}
+
+// Happy path.
+func TestConversation_Delete(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partner := id.NewIdFromString("partner ID", id.User, t)
+	conv := makeRandomConv(kv, partner)
+
+	if err := conv.save(); err != nil {
+		t.Fatalf("Failed to save conversation to storage: %+v", err)
+	}
+
+	if _, err := loadConversation(kv, partner); err != nil {
+		t.Fatalf("Failed to load conversation from storage: %v", err)
+	}
+
+	if err := conv.delete(); err != nil {
+		t.Errorf("delete() produced an error: %+v", err)
+	}
+
+	if _, err := loadConversation(kv, partner); err == nil {
+		t.Error("Object found in storage when it should be deleted.")
+	}
+}
+
+// Tests the happy path of marshal() and unmarshal().
+func TestConversation_marshal_unmarshal(t *testing.T) {
+	expectedConv := makeRandomConv(versioned.NewKV(make(ekv.Memstore)),
+		id.NewIdFromString("partner ID", id.User, t))
+	testConv := LoadOrMakeConversation(expectedConv.kv, expectedConv.partner)
+
+	data, err := expectedConv.marshal()
+	if err != nil {
+		t.Errorf("marshal() returned an error: %v", err)
+	}
+
+	err = testConv.unmarshal(data)
+	if err != nil {
+		t.Errorf("unmarshal() returned an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedConv, testConv) {
+		t.Errorf("marshaling and unmarshaling Conversation failed."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedConv, testConv)
+	}
+}
+
+func makeRandomConv(kv *versioned.KV, partner *id.ID) *Conversation {
+	c := LoadOrMakeConversation(kv, partner)
+	c.lastReceivedID = rand.Uint32()
+	c.numReceivedRevolutions = rand.Uint32()
+	c.nextSentID = rand.Uint64()
+
+	return c
+}
diff --git a/storage/conversation/store.go b/storage/conversation/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..662e6c1f2599816258e9e75f294ae7f27dc425cb
--- /dev/null
+++ b/storage/conversation/store.go
@@ -0,0 +1,74 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package conversation
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+)
+
+const conversationKeyPrefix = "conversation"
+
+type Store struct {
+	loadedConversations map[id.ID]*Conversation
+	kv                  *versioned.KV
+	mux                 sync.RWMutex
+}
+
+// NewStore returns a new conversation store made off of the KV.
+func NewStore(kv *versioned.KV) *Store {
+	kv = kv.Prefix(conversationKeyPrefix)
+	return &Store{
+		loadedConversations: make(map[id.ID]*Conversation),
+		kv:                  kv,
+	}
+}
+
+// Get gets the conversation with the given partner ID from RAM, if it is there.
+// Otherwise, it loads it from disk.
+func (s *Store) Get(partner *id.ID) *Conversation {
+	s.mux.RLock()
+	c, ok := s.loadedConversations[*partner]
+	s.mux.RUnlock()
+
+	if !ok {
+		s.mux.Lock()
+		c, ok = s.loadedConversations[*partner]
+		if !ok {
+			c = LoadOrMakeConversation(s.kv, partner)
+			s.loadedConversations[*partner] = c
+		}
+		s.mux.Unlock()
+	}
+	return c
+}
+
+// delete deletes the conversation with the given partner ID from memory and
+// storage. Panics if the object cannot be deleted from storage.
+func (s *Store) Delete(partner *id.ID) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	// Get contact from memory
+	c, exists := s.loadedConversations[*partner]
+	if !exists {
+		return
+	}
+
+	// delete contact from storage
+	err := c.delete()
+	if err != nil {
+		jww.FATAL.Panicf("Failed to remover conversation with ID %s from "+
+			"storage: %+v", partner, err)
+	}
+
+	// delete contact from memory
+	delete(s.loadedConversations, *partner)
+}
diff --git a/storage/conversation/store_test.go b/storage/conversation/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7505280e1c534486b60b47b76aac4858fdc1bc4a
--- /dev/null
+++ b/storage/conversation/store_test.go
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package conversation
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"testing"
+)
+
+// Read jww trace output to determine if key names are ok
+func TestStore_Get_Prefix(t *testing.T) {
+	// Uncomment to print keys that Set and Get are called on
+	jww.SetStdoutThreshold(jww.LevelTrace)
+
+	// It's a conversation with a partner, so does there need to be an additional layer of hierarchy here later?
+	rootKv := versioned.NewKV(make(ekv.Memstore))
+	store := NewStore(rootKv)
+	conv := store.Get(id.NewIdFromUInt(8, id.User, t))
+	t.Log(conv)
+}
+
+// Happy path.
+func TestStore_Delete(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	store := NewStore(kv)
+	pids := make([]*id.ID, 10)
+
+	// Generate list of IDs
+	for i := range pids {
+		pids[i] = id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	}
+
+	// Add IDs to storage and memory
+	for _, pid := range pids {
+		store.Get(pid)
+	}
+
+	// delete conversations with IDs with even numbered indexes
+	for i := 0; i < len(pids); i += 2 {
+		store.Delete(pids[i])
+	}
+
+	// Ensure even numbered conversation were deleted and all others still exist
+	for i, pid := range pids {
+		_, exists := store.loadedConversations[*pid]
+		if i%2 == 0 {
+			if exists {
+				t.Errorf("%d. delete() failed to delete the conversation "+
+					"(ID %s) from memory.", i, pid)
+			}
+		} else if !exists {
+			t.Errorf("%d. delete() unexpetedly deleted the conversation "+
+				"(ID %s) from memory.", i, pid)
+		}
+	}
+}
diff --git a/storage/e2e/context.go b/storage/e2e/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..beb7211ed6f8b1caca614a46bb76a03c76651c84
--- /dev/null
+++ b/storage/e2e/context.go
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type context struct {
+	fa fingerprintAccess
+
+	grp *cyclic.Group
+
+	rng *fastRNG.StreamGenerator
+
+	myID *id.ID
+}
diff --git a/storage/e2e/fingerprintAccess.go b/storage/e2e/fingerprintAccess.go
new file mode 100644
index 0000000000000000000000000000000000000000..82e130ed4da604ffe310f1f81362d08ab1c01130
--- /dev/null
+++ b/storage/e2e/fingerprintAccess.go
@@ -0,0 +1,15 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+type fingerprintAccess interface {
+	// Receives a list of fingerprints to add. Overrides on collision.
+	add([]*Key)
+	// Receives a list of fingerprints to delete. Ignores any not available Keys
+	remove([]*Key)
+}
diff --git a/storage/e2e/key.go b/storage/e2e/key.go
new file mode 100644
index 0000000000000000000000000000000000000000..49900a09acd032bdbae55c1e90bbc7a69a357c4f
--- /dev/null
+++ b/storage/e2e/key.go
@@ -0,0 +1,103 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"github.com/pkg/errors"
+	e2eCrypto "gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/elixxir/primitives/format"
+)
+
+type Key struct {
+	// Links
+	session *Session
+
+	fp *format.Fingerprint
+
+	// keyNum is the index of the key by order of creation
+	// it is used to identify the key in the key.Session
+	keyNum uint32
+}
+
+func newKey(session *Session, keynum uint32) *Key {
+	return &Key{
+		session: session,
+		keyNum:  keynum,
+	}
+}
+
+// return pointers to higher level management structures
+func (k *Key) GetSession() *Session { return k.session }
+
+// returns the key fingerprint if it has it, otherwise generates it
+// this function does not memoize the fingerprint if it doesnt have it because
+// in most cases it will not be used for a long time and as a result should not
+// be stored in ram.
+func (k *Key) Fingerprint() format.Fingerprint {
+	if k.fp != nil {
+		return *k.fp
+	}
+	return e2eCrypto.DeriveKeyFingerprint(k.session.baseKey, k.keyNum,
+		k.session.relationshipFingerprint)
+}
+
+// the E2E key to encrypt msg to its intended recipient
+// It also properly populates the associated data, including the MAC, fingerprint,
+// and encrypted timestamp
+func (k *Key) Encrypt(msg format.Message) format.Message {
+	fp := k.Fingerprint()
+	key := k.generateKey()
+
+	// set the fingerprint
+	msg.SetKeyFP(fp)
+
+	// encrypt the payload
+	encPayload := e2eCrypto.Crypt(key, fp, msg.GetContents())
+	msg.SetContents(encPayload)
+
+	// create the MAC
+	// MAC is HMAC(key, ciphertext)
+	// Currently, the MAC doesn't include any of the associated data
+	MAC := hash.CreateHMAC(encPayload, key[:])
+	msg.SetMac(MAC)
+
+	return msg
+}
+
+// Decrypt uses the E2E key to decrypt the message
+// It returns an error in case of HMAC verification failure
+// or in case of a decryption error (related to padding)
+func (k *Key) Decrypt(msg format.Message) (format.Message, error) {
+	fp := k.Fingerprint()
+	key := k.generateKey()
+
+	// Verify the MAC is correct
+	if !hash.VerifyHMAC(msg.GetContents(), msg.GetMac(), key[:]) {
+		return format.Message{}, errors.New("HMAC verification failed for E2E message")
+	}
+
+	// Decrypt the payload
+	decryptedPayload := e2eCrypto.Crypt(key, fp, msg.GetContents())
+
+	//put the decrypted payload back in the message
+	msg.SetContents(decryptedPayload)
+
+	return msg, nil
+}
+
+// Sets the key as used
+func (k *Key) denoteUse() {
+	k.session.useKey(k.keyNum)
+}
+
+// Generates the key and returns it
+func (k *Key) generateKey() e2eCrypto.Key {
+	return e2eCrypto.DeriveKey(k.session.baseKey, k.keyNum,
+		k.session.relationshipFingerprint)
+}
diff --git a/storage/e2e/key_test.go b/storage/e2e/key_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d74f53e174685483666c37150be86aef2d3a69a9
--- /dev/null
+++ b/storage/e2e/key_test.go
@@ -0,0 +1,220 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Happy path of newKey().
+func Test_newKey(t *testing.T) {
+	expectedKey := &Key{
+		session: getSession(t),
+		keyNum:  rand.Uint32(),
+	}
+
+	testKey := newKey(expectedKey.session, expectedKey.keyNum)
+
+	if !reflect.DeepEqual(expectedKey, testKey) {
+		t.Errorf("newKey() did not produce the expected Key."+
+			"\n\texpected: %v\n\treceived: %v",
+			expectedKey, testKey)
+	}
+}
+
+// Happy path of Key.GetSession().
+func TestKey_GetSession(t *testing.T) {
+	k := newKey(getSession(t), rand.Uint32())
+
+	testSession := k.GetSession()
+
+	if !reflect.DeepEqual(k.session, testSession) {
+
+		if !reflect.DeepEqual(k.session, testSession) {
+			t.Errorf("GetSession() did not produce the expected Session."+
+				"\n\texpected: %v\n\treceived: %v",
+				k.session, testSession)
+		}
+	}
+}
+
+// Happy path of Key.Fingerprint().
+func TestKey_Fingerprint(t *testing.T) {
+	k := newKey(getSession(t), rand.Uint32())
+
+	// Generate test and expected fingerprints
+	testFingerprint := getFingerprint()
+	testData := []struct {
+		testFP     *format.Fingerprint
+		expectedFP format.Fingerprint
+	}{
+		{testFingerprint, *testFingerprint},
+		{nil, e2e.DeriveKeyFingerprint(k.session.baseKey, k.keyNum)},
+	}
+
+	// Test cases
+	for _, data := range testData {
+		k.fp = data.testFP
+		testFP := k.Fingerprint()
+
+		if !reflect.DeepEqual(data.expectedFP, testFP) {
+			t.Errorf("Fingerprint() did not produce the expected Fingerprint."+
+				"\n\texpected: %v\n\treceived: %v",
+				data.expectedFP, testFP)
+		}
+	}
+}
+
+func TestKey_EncryptDecrypt(t *testing.T) {
+
+	const numTests = 100
+
+	grp := getGroup()
+	rng := csprng.NewSystemRNG()
+	prng := rand.New(rand.NewSource(42))
+
+	for i := 0; i < numTests; i++ {
+		// generate the baseKey and session
+		privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+		publicKey := dh.GeneratePublicKey(privateKey, grp)
+		baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp)
+
+		s := &Session{
+			baseKey: baseKey,
+		}
+
+		//create the keys
+		k := newKey(s, prng.Uint32())
+
+		//make the message to be encrypted
+		msg := format.NewMessage(grp.GetP().ByteLen())
+
+		//set the contents
+		contents := make([]byte, msg.ContentsSize())
+		prng.Read(contents)
+		msg.SetContents(contents)
+
+		// Encrypt
+		ecrMsg := k.Encrypt(msg)
+
+		if !reflect.DeepEqual(k.Fingerprint(), ecrMsg.GetKeyFP()) {
+			t.Errorf("Fingerprint in the ecrypted payload is wrong: "+
+				"Expected: %+v, Recieved: %+v", k.Fingerprint(), ecrMsg.GetKeyFP())
+		}
+
+		// Decrypt
+		resultMsg, _ := k.Decrypt(ecrMsg)
+
+		if !bytes.Equal(resultMsg.GetContents(), msg.GetContents()) {
+			t.Errorf("contents in the decrypted payload does not match: "+
+				"Expected: %v, Recieved: %v", msg.GetContents(), resultMsg.GetContents())
+		}
+	}
+}
+
+// Happy path of Key.denoteUse()
+func TestKey_denoteUse(t *testing.T) {
+	keyNum := uint32(rand.Int31n(31))
+
+	k := newKey(getSession(t), keyNum)
+
+	k.denoteUse()
+
+	if !k.session.keyState.Used(keyNum) {
+		t.Errorf("denoteUse() did not use the key")
+	}
+}
+
+// Happy path of generateKey().
+func TestKey_generateKey(t *testing.T) {
+	k := newKey(getSession(t), rand.Uint32())
+
+	// Generate test CryptoType values and expected keys
+	expectedKey := e2e.DeriveKey(k.session.baseKey, k.keyNum)
+	testKey := k.generateKey()
+
+	if !reflect.DeepEqual(expectedKey, testKey) {
+		t.Errorf("generateKey() did not produce the expected e2e key."+
+			"\n\texpected: %v\n\treceived: %v",
+			expectedKey, testKey)
+	}
+
+}
+
+func getGroup() *cyclic.Group {
+	e2eGrp := cyclic.NewGroup(
+		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B"+
+			"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE"+
+			"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F"+
+			"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041"+
+			"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45"+
+			"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209"+
+			"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29"+
+			"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E"+
+			"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2"+
+			"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696"+
+			"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E"+
+			"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873"+
+			"847AEF49F66E43873", 16),
+		large.NewIntFromString("2", 16))
+
+	return e2eGrp
+
+}
+
+func getSession(t *testing.T) *Session {
+	if t == nil {
+		panic("getSession is a testing function and should be called from a test")
+	}
+	grp := getGroup()
+	rng := csprng.NewSystemRNG()
+
+	// generate the baseKey and session
+	privateKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+	publicKey := dh.GeneratePublicKey(privateKey, grp)
+	baseKey := dh.GenerateSessionKey(privateKey, publicKey, grp)
+
+	fps := newFingerprints()
+	ctx := &context{
+		fa:  &fps,
+		grp: grp,
+	}
+
+	keyState, err := newStateVector(versioned.NewKV(make(ekv.Memstore)), "keyState", rand.Uint32())
+	if err != nil {
+		panic(err)
+	}
+
+	return &Session{
+		relationship: &relationship{
+			manager: &Manager{ctx: ctx},
+		},
+		baseKey:  baseKey,
+		keyState: keyState,
+	}
+}
+
+func getFingerprint() *format.Fingerprint {
+	rand.Seed(time.Now().UnixNano())
+	fp := format.Fingerprint{}
+	rand.Read(fp[:])
+
+	return &fp
+}
diff --git a/storage/e2e/manager.go b/storage/e2e/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..b1feb294761730105b5fa70fa9f27b669c79f78a
--- /dev/null
+++ b/storage/e2e/manager.go
@@ -0,0 +1,201 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+const managerPrefix = "Manager{partner:%s}"
+const originMyPrivKeyKey = "originMyPrivKey"
+const originPartnerPubKey = "originPartnerPubKey"
+
+type Manager struct {
+	ctx *context
+	kv  *versioned.KV
+
+	partner *id.ID
+
+	originMyPrivKey     *cyclic.Int
+	originPartnerPubKey *cyclic.Int
+
+	receive *relationship
+	send    *relationship
+}
+
+// newManager creates the relationship and its first Send and Receive sessions.
+func newManager(ctx *context, kv *versioned.KV, partnerID *id.ID, myPrivKey,
+	partnerPubKey *cyclic.Int,
+	sendParams, receiveParams params.E2ESessionParams) *Manager {
+
+	kv = kv.Prefix(fmt.Sprintf(managerPrefix, partnerID))
+
+	m := &Manager{
+		ctx:                 ctx,
+		kv:                  kv,
+		originMyPrivKey:     myPrivKey,
+		originPartnerPubKey: partnerPubKey,
+		partner:             partnerID,
+	}
+
+	if err := utility.StoreCyclicKey(kv, myPrivKey, originMyPrivKeyKey); err != nil {
+		jww.FATAL.Panicf("Failed to store %s: %+v", originMyPrivKeyKey,
+			err)
+	}
+
+	if err := utility.StoreCyclicKey(kv, partnerPubKey, originPartnerPubKey); err != nil {
+		jww.FATAL.Panicf("Failed to store %s: %+v", originPartnerPubKey,
+			err)
+	}
+
+	m.send = NewRelationship(m, Send, sendParams)
+	m.receive = NewRelationship(m, Receive, receiveParams)
+
+	return m
+}
+
+//loads a relationship and all buffers and sessions from disk
+func loadManager(ctx *context, kv *versioned.KV, partnerID *id.ID) (*Manager, error) {
+
+	kv = kv.Prefix(fmt.Sprintf(managerPrefix, partnerID))
+
+	m := &Manager{
+		ctx:     ctx,
+		partner: partnerID,
+		kv:      kv,
+	}
+
+	var err error
+	m.originMyPrivKey, err = utility.LoadCyclicKey(kv, originMyPrivKeyKey)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to load %s: %+v", originMyPrivKeyKey,
+			err)
+	}
+
+	m.originPartnerPubKey, err = utility.LoadCyclicKey(kv, originPartnerPubKey)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to load %s: %+v", originPartnerPubKey,
+			err)
+	}
+
+	m.send, err = LoadRelationship(m, Send)
+	if err != nil {
+		return nil, errors.WithMessage(err,
+			"Failed to load partner key relationship due to failure to "+
+				"load the Send session buffer")
+	}
+
+	m.receive, err = LoadRelationship(m, Receive)
+	if err != nil {
+		return nil, errors.WithMessage(err,
+			"Failed to load partner key relationship due to failure to "+
+				"load the Receive session buffer")
+	}
+
+	return m, nil
+}
+
+// NewReceiveSession creates a new Receive session using the latest private key
+// this user has sent and the new public key received from the partner. If the
+// session already exists, then it will not be overwritten and the extant
+// session will be returned with the bool set to true denoting a duplicate. This
+// allows for support of duplicate key exchange triggering.
+func (m *Manager) NewReceiveSession(partnerPubKey *cyclic.Int, e2eParams params.E2ESessionParams,
+	source *Session) (*Session, bool) {
+
+	// Check if the session already exists
+	baseKey := dh.GenerateSessionKey(source.myPrivKey, partnerPubKey, m.ctx.grp)
+	sessionID := getSessionIDFromBaseKey(baseKey)
+
+	if s := m.receive.GetByID(sessionID); s != nil {
+		return s, true
+	}
+
+	// Add the session to the buffer
+	session := m.receive.AddSession(source.myPrivKey, partnerPubKey, baseKey,
+		source.GetID(), Confirmed, e2eParams)
+
+	return session, false
+}
+
+// NewSendSession creates a new Receive session using the latest public key
+// received from the partner and a new private key for the user. Passing in a
+// private key is optional. A private key will be generated if none is passed.
+func (m *Manager) NewSendSession(myPrivKey *cyclic.Int, e2eParams params.E2ESessionParams) *Session {
+	// Find the latest public key from the other party
+	sourceSession := m.receive.getNewestRekeyableSession()
+
+	// Add the session to the Send session buffer and return
+	return m.send.AddSession(myPrivKey, sourceSession.partnerPubKey, nil,
+		sourceSession.GetID(), Sending, e2eParams)
+}
+
+// GetKeyForSending gets the correct session to Send with depending on the type
+// of Send.
+func (m *Manager) GetKeyForSending(st params.SendType) (*Key, error) {
+	switch st {
+	case params.Standard:
+		return m.send.getKeyForSending()
+	case params.KeyExchange:
+		return m.send.getKeyForRekey()
+	default:
+	}
+
+	return nil, errors.Errorf("Cannot get session for invalid Send Type: %s", st)
+}
+
+// GetPartnerID returns a copy of the ID of the partner.
+func (m *Manager) GetPartnerID() *id.ID {
+	p := m.partner
+	return p
+}
+
+// GetSendSession gets the Send session of the passed ID. Returns nil if no
+// session is found.
+func (m *Manager) GetSendSession(sid SessionID) *Session {
+	return m.send.GetByID(sid)
+}
+
+// GetSendSession gets the Send session of the passed ID. Returns nil if no
+// session is found.
+func (m *Manager) GetSendRelationshipFingerprint() []byte {
+	return m.send.fingerprint
+}
+
+// GetReceiveSession gets the Receive session of the passed ID. Returns nil if
+// no session is found.
+func (m *Manager) GetReceiveSession(sid SessionID) *Session {
+	return m.receive.GetByID(sid)
+}
+
+// Confirm confirms a Send session is known about by the partner.
+func (m *Manager) Confirm(sid SessionID) error {
+	return m.send.Confirm(sid)
+}
+
+// TriggerNegotiations returns a list of key exchange operations if any are
+// necessary.
+func (m *Manager) TriggerNegotiations() []*Session {
+	return m.send.TriggerNegotiation()
+}
+
+func (m *Manager) GetMyOriginPrivateKey() *cyclic.Int {
+	return m.originMyPrivKey.DeepCopy()
+}
+
+func (m *Manager) GetPartnerOriginPublicKey() *cyclic.Int {
+	return m.originPartnerPubKey.DeepCopy()
+}
diff --git a/storage/e2e/manager_test.go b/storage/e2e/manager_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..58b0ca6bb6c5b846abc994b8c5f43e90819be664
--- /dev/null
+++ b/storage/e2e/manager_test.go
@@ -0,0 +1,285 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"bytes"
+	"fmt"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests happy path of newManager.
+func Test_newManager(t *testing.T) {
+	// Set up expected and test values
+	s, ctx := makeTestSession()
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partnerID := id.NewIdFromUInt(100, id.User, t)
+	expectedM := &Manager{
+		ctx:                 ctx,
+		kv:                  kv.Prefix(fmt.Sprintf(managerPrefix, partnerID)),
+		partner:             partnerID,
+		originPartnerPubKey: s.partnerPubKey,
+		originMyPrivKey:     s.myPrivKey,
+	}
+	expectedM.send = NewRelationship(expectedM, Send,
+		params.GetDefaultE2ESessionParams())
+	expectedM.receive = NewRelationship(expectedM, Receive,
+		params.GetDefaultE2ESessionParams())
+
+	// Create new relationship
+	m := newManager(ctx, kv, partnerID, s.myPrivKey, s.partnerPubKey,
+		s.e2eParams,
+		s.e2eParams)
+
+	// Check if the new relationship matches the expected
+	if !managersEqual(expectedM, m, t) {
+		t.Errorf("newManager() did not produce the expected Manager."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedM, m)
+	}
+}
+
+// Tests happy path of loadManager.
+func TestLoadManager(t *testing.T) {
+	// Set up expected and test values
+	expectedM, kv := newTestManager(t)
+
+	// Attempt to load relationship
+	m, err := loadManager(expectedM.ctx, kv, expectedM.partner)
+	if err != nil {
+		t.Errorf("loadManager() returned an error: %v", err)
+	}
+
+	// Check if the loaded relationship matches the expected
+	if !managersEqual(expectedM, m, t) {
+		t.Errorf("loadManager() did not produce the expected Manager."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedM, m)
+	}
+}
+
+// Tests happy path of Manager.NewReceiveSession.
+func TestManager_NewReceiveSession(t *testing.T) {
+	// Set up test values
+	m, _ := newTestManager(t)
+	s, _ := makeTestSession()
+
+	se, exists := m.NewReceiveSession(s.partnerPubKey, s.e2eParams, s)
+	if exists {
+		t.Errorf("NewReceiveSession() did not return the correct value."+
+			"\n\texpected: %v\n\treceived: %v", false, exists)
+	}
+	if !m.partner.Cmp(se.GetPartner()) || !bytes.Equal(s.GetID().Marshal(), se.GetID().Marshal()) {
+		t.Errorf("NewReceiveSession() did not return the correct session."+
+			"\n\texpected partner: %v\n\treceived partner: %v"+
+			"\n\texpected ID: %v\n\treceived ID: %v",
+			m.partner, se.GetPartner(), s.GetID(), se.GetID())
+	}
+
+	se, exists = m.NewReceiveSession(s.partnerPubKey, s.e2eParams, s)
+	if !exists {
+		t.Errorf("NewReceiveSession() did not return the correct value."+
+			"\n\texpected: %v\n\treceived: %v", true, exists)
+	}
+	if !m.partner.Cmp(se.GetPartner()) || !bytes.Equal(s.GetID().Marshal(), se.GetID().Marshal()) {
+		t.Errorf("NewReceiveSession() did not return the correct session."+
+			"\n\texpected partner: %v\n\treceived partner: %v"+
+			"\n\texpected ID: %v\n\treceived ID: %v",
+			m.partner, se.GetPartner(), s.GetID(), se.GetID())
+	}
+}
+
+// Tests happy path of Manager.NewSendSession.
+func TestManager_NewSendSession(t *testing.T) {
+	// Set up test values
+	m, _ := newTestManager(t)
+	s, _ := makeTestSession()
+
+	se := m.NewSendSession(s.myPrivKey, s.e2eParams)
+	if !m.partner.Cmp(se.GetPartner()) {
+		t.Errorf("NewSendSession() did not return the correct session."+
+			"\n\texpected partner: %v\n\treceived partner: %v",
+			m.partner, se.GetPartner())
+	}
+
+	se = m.NewSendSession(s.partnerPubKey, s.e2eParams)
+	if !m.partner.Cmp(se.GetPartner()) {
+		t.Errorf("NewSendSession() did not return the correct session."+
+			"\n\texpected partner: %v\n\treceived partner: %v",
+			m.partner, se.GetPartner())
+	}
+}
+
+// Tests happy path of Manager.GetKeyForSending.
+func TestManager_GetKeyForSending(t *testing.T) {
+	// Set up test values
+	m, _ := newTestManager(t)
+	p := params.GetDefaultE2E()
+	expectedKey := &Key{
+		session: m.send.sessions[0],
+	}
+
+	key, err := m.GetKeyForSending(p.Type)
+	if err != nil {
+		t.Errorf("GetKeyForSending() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedKey, key) {
+		t.Errorf("GetKeyForSending() did not return the correct key."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expectedKey, key)
+	}
+
+	p.Type = params.KeyExchange
+	m.send.sessions[0].negotiationStatus = NewSessionTriggered
+	expectedKey.keyNum++
+
+	key, err = m.GetKeyForSending(p.Type)
+	if err != nil {
+		t.Errorf("GetKeyForSending() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedKey, key) {
+		t.Errorf("GetKeyForSending() did not return the correct key."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expectedKey, key)
+	}
+}
+
+// Tests that Manager.GetKeyForSending returns an error for invalid SendType.
+func TestManager_GetKeyForSending_Error(t *testing.T) {
+	// Set up test values
+	m, _ := newTestManager(t)
+	p := params.GetDefaultE2E()
+	p.Type = 2
+
+	key, err := m.GetKeyForSending(p.Type)
+	if err == nil {
+		t.Errorf("GetKeyForSending() did not produce an error for invalid SendType.")
+	}
+
+	if key != nil {
+		t.Errorf("GetKeyForSending() did not return the correct key."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			nil, key)
+	}
+}
+
+// Tests happy path of Manager.GetPartnerID.
+func TestManager_GetPartnerID(t *testing.T) {
+	m, _ := newTestManager(t)
+
+	pid := m.GetPartnerID()
+
+	if !m.partner.Cmp(pid) {
+		t.Errorf("GetPartnerID() returned incorrect partner ID."+
+			"\n\texpected: %s\n\treceived: %s", m.partner, pid)
+	}
+}
+
+// Tests happy path of Manager.GetSendSession.
+func TestManager_GetSendSession(t *testing.T) {
+	m, _ := newTestManager(t)
+
+	s := m.GetSendSession(m.send.sessions[0].GetID())
+
+	if !reflect.DeepEqual(m.send.sessions[0], s) {
+		t.Errorf("GetSendSession() returned incorrect session."+
+			"\n\texpected: %s\n\treceived: %s", m.send.sessions[0], s)
+	}
+}
+
+// Tests happy path of Manager.GetReceiveSession.
+func TestManager_GetReceiveSession(t *testing.T) {
+	m, _ := newTestManager(t)
+
+	s := m.GetReceiveSession(m.receive.sessions[0].GetID())
+
+	if !reflect.DeepEqual(m.receive.sessions[0], s) {
+		t.Errorf("GetReceiveSession() returned incorrect session."+
+			"\n\texpected: %s\n\treceived: %s", m.receive.sessions[0], s)
+	}
+}
+
+// Tests happy path of Manager.Confirm.
+func TestManager_Confirm(t *testing.T) {
+	m, _ := newTestManager(t)
+	m.send.sessions[0].negotiationStatus = Sent
+	err := m.Confirm(m.send.sessions[0].GetID())
+	if err != nil {
+		t.Errorf("Confirm produced an error: %v", err)
+	}
+}
+
+// Tests happy path of Manager.TriggerNegotiations.
+func TestManager_TriggerNegotiations(t *testing.T) {
+	m, _ := newTestManager(t)
+	m.send.sessions[0].negotiationStatus = Unconfirmed
+	sessions := m.TriggerNegotiations()
+	if !reflect.DeepEqual(m.send.sessions, sessions) {
+		t.Errorf("TriggerNegotiations() returned incorrect sessions."+
+			"\n\texpected: %s\n\treceived: %s", m.send.sessions, sessions)
+	}
+}
+
+// newTestManager returns a new relationship for testing.
+func newTestManager(t *testing.T) (*Manager, *versioned.KV) {
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	s, ctx := makeTestSession()
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partnerID := id.NewIdFromUInts([4]uint64{prng.Uint64(), prng.Uint64(),
+		prng.Uint64(), prng.Uint64()}, id.User, t)
+
+	// Create new relationship
+	m := newManager(ctx, kv, partnerID, s.myPrivKey, s.partnerPubKey,
+		s.e2eParams,
+		s.e2eParams)
+
+	return m, kv
+}
+
+func managersEqual(expected, received *Manager, t *testing.T) bool {
+	equal := true
+	if !reflect.DeepEqual(expected.ctx, received.ctx) {
+		t.Errorf("Did not Receive expected Manager.ctx."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expected.ctx, received.ctx)
+		equal = false
+	}
+	if !reflect.DeepEqual(expected.kv, received.kv) {
+		t.Errorf("Did not Receive expected Manager.kv."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expected.kv, received.kv)
+		equal = false
+	}
+	if !expected.partner.Cmp(received.partner) {
+		t.Errorf("Did not Receive expected Manager.partner."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expected.partner, received.partner)
+		equal = false
+	}
+	if !relationshipsEqual(expected.receive, received.receive) {
+		t.Errorf("Did not Receive expected Manager.Receive."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expected.receive, received.receive)
+		equal = false
+	}
+	if !relationshipsEqual(expected.send, received.send) {
+		t.Errorf("Did not Receive expected Manager.Send."+
+			"\n\texpected: %+v\n\treceived: %+v",
+			expected.send, received.send)
+		equal = false
+	}
+
+	return equal
+}
diff --git a/storage/e2e/negotiation.go b/storage/e2e/negotiation.go
new file mode 100644
index 0000000000000000000000000000000000000000..783f6a04a16d537ca230a8a01287d7198185fe19
--- /dev/null
+++ b/storage/e2e/negotiation.go
@@ -0,0 +1,46 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import "fmt"
+
+// Fix-me: this solution is incompatible with offline sending, when that is
+// added, a session which has not been confirmed will never partnerSource the
+// creation of new session, the Unconfirmed->Confirmed and
+// Confirmed->NewSessionCreated most likely need to be two separate enums
+// tracked separately
+type Negotiation uint8
+
+const (
+	Unconfirmed         Negotiation = 0
+	Sending                         = 1
+	Sent                            = 2
+	Confirmed                       = 3
+	NewSessionTriggered             = 4
+	NewSessionCreated               = 5
+)
+
+//Adherence to stringer interface
+func (c Negotiation) String() string {
+	switch c {
+	case Unconfirmed:
+		return "Unconfirmed"
+	case Sending:
+		return "Sending"
+	case Sent:
+		return "Sent"
+	case Confirmed:
+		return "Confirmed"
+	case NewSessionTriggered:
+		return "NewSessionTriggered"
+	case NewSessionCreated:
+		return "NewSessionCreated"
+	default:
+		return fmt.Sprintf("Unknown Negotiation %v", uint8(c))
+	}
+}
diff --git a/storage/e2e/relationship.go b/storage/e2e/relationship.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ff938ab6431084f70f26ba2689ed8f928f46660
--- /dev/null
+++ b/storage/e2e/relationship.go
@@ -0,0 +1,369 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"sync"
+	"time"
+)
+
+const maxUnconfirmed uint = 3
+const currentRelationshipVersion = 0
+const currentRelationshipFingerprintVersion = 0
+const relationshipKey = "relationship"
+const relationshipFingerprintKey = "relationshipFingerprint"
+
+type relationship struct {
+	manager *Manager
+	t       RelationshipType
+
+	kv *versioned.KV
+
+	sessions    []*Session
+	sessionByID map[SessionID]*Session
+
+	fingerprint []byte
+
+	mux     sync.RWMutex
+	sendMux sync.Mutex
+}
+
+func NewRelationship(manager *Manager, t RelationshipType,
+	initialParams params.E2ESessionParams) *relationship {
+
+	kv := manager.kv.Prefix(t.prefix())
+
+	//build the fingerprint
+	fingerprint := makeRelationshipFingerprint(t, manager.ctx.grp,
+		manager.originMyPrivKey, manager.originPartnerPubKey, manager.ctx.myID,
+		manager.partner)
+
+	if err := storeRelationshipFingerprint(fingerprint, kv); err != nil {
+		jww.FATAL.Panicf("Failed to store relationship fingerpint "+
+			"for new relationship: %+v", err)
+	}
+
+	r := &relationship{
+		manager:     manager,
+		t:           t,
+		sessions:    make([]*Session, 0),
+		sessionByID: make(map[SessionID]*Session),
+		fingerprint: fingerprint,
+		kv:          kv,
+	}
+
+	// set to confirmed because the first session is always confirmed as a
+	// result of the negotiation before creation
+	s := newSession(r, r.t, manager.originMyPrivKey,
+		manager.originPartnerPubKey, nil, SessionID{},
+		r.fingerprint, Confirmed, initialParams)
+
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to Send session after setting to "+
+			"confimred: %+v", err)
+	}
+
+	r.addSession(s)
+
+	if err := r.save(); err != nil {
+		jww.FATAL.Printf("Failed to save Relationship %s after "+
+			"adding session %s: %s", relationshipKey, s, err)
+	}
+
+	return r
+}
+
+func LoadRelationship(manager *Manager, t RelationshipType) (*relationship, error) {
+
+	kv := manager.kv.Prefix(t.prefix())
+
+	r := &relationship{
+		t:           t,
+		manager:     manager,
+		sessionByID: make(map[SessionID]*Session),
+		kv:          kv,
+	}
+
+	obj, err := kv.Get(relationshipKey, currentRelationshipVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	err = r.unmarshal(obj.Data)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return r, nil
+}
+
+func (r *relationship) save() error {
+
+	now := time.Now()
+
+	data, err := r.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentRelationshipVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return r.kv.Set(relationshipKey, currentRelationshipVersion, &obj)
+}
+
+//ekv functions
+func (r *relationship) marshal() ([]byte, error) {
+	sessions := make([]SessionID, len(r.sessions))
+
+	index := 0
+	for sid := range r.sessionByID {
+		sessions[index] = sid
+		index++
+	}
+
+	return json.Marshal(&sessions)
+}
+
+func (r *relationship) unmarshal(b []byte) error {
+	var sessions []SessionID
+
+	err := json.Unmarshal(b, &sessions)
+
+	if err != nil {
+		return err
+	}
+
+	//load the fingerprint
+	r.fingerprint = loadRelationshipFingerprint(r.kv)
+
+	//load all the sessions
+	for _, sid := range sessions {
+		sessionKV := r.kv.Prefix(makeSessionPrefix(sid))
+		session, err := loadSession(r, sessionKV, r.fingerprint)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to load session %s for %s: %+v",
+				makeSessionPrefix(sid), r.manager.partner, err)
+		}
+		r.addSession(session)
+	}
+
+	return nil
+}
+
+func (r *relationship) AddSession(myPrivKey, partnerPubKey, baseKey *cyclic.Int,
+	trigger SessionID, negotiationStatus Negotiation,
+	e2eParams params.E2ESessionParams) *Session {
+	r.mux.Lock()
+	defer r.mux.Unlock()
+
+	s := newSession(r, r.t, myPrivKey, partnerPubKey, baseKey, trigger,
+		r.fingerprint, negotiationStatus, e2eParams)
+
+	r.addSession(s)
+	if err := r.save(); err != nil {
+		jww.FATAL.Printf("Failed to save Relationship %s after "+
+			"adding session %s: %s", relationshipKey, s, err)
+	}
+
+	return s
+}
+
+func (r *relationship) addSession(s *Session) {
+	r.sessions = append([]*Session{s}, r.sessions...)
+	r.sessionByID[s.GetID()] = s
+	return
+}
+
+func (r *relationship) GetNewest() *Session {
+	r.mux.RLock()
+	defer r.mux.RUnlock()
+	if len(r.sessions) == 0 {
+		return nil
+	}
+	return r.sessions[0]
+}
+
+// returns the key  which is most likely to be successful for sending
+func (r *relationship) getKeyForSending() (*Key, error) {
+	r.sendMux.Lock()
+	defer r.sendMux.Unlock()
+	s := r.getSessionForSending()
+	if s == nil {
+		return nil, errors.New("Failed to find a session for sending")
+	}
+
+	return s.PopKey()
+}
+
+// returns the session which is most likely to be successful for sending
+func (r *relationship) getSessionForSending() *Session {
+	sessions := r.sessions
+
+	var confirmedRekey []*Session
+	var unconfirmedActive []*Session
+	var unconfirmedRekey []*Session
+
+	for _, s := range sessions {
+		status := s.Status()
+		confirmed := s.IsConfirmed()
+		if status == Active && confirmed {
+			//always return the first confirmed active, happy path
+			return s
+		} else if status == RekeyNeeded && confirmed {
+			confirmedRekey = append(confirmedRekey, s)
+		} else if status == Active && !confirmed {
+			unconfirmedActive = append(unconfirmedActive, s)
+		} else if status == RekeyNeeded && !confirmed {
+			unconfirmedRekey = append(unconfirmedRekey, s)
+		}
+	}
+
+	//return the newest based upon priority
+	if len(confirmedRekey) > 0 {
+		return confirmedRekey[0]
+	} else if len(unconfirmedActive) > 0 {
+		return unconfirmedActive[0]
+	} else if len(unconfirmedRekey) > 0 {
+		return unconfirmedRekey[0]
+	}
+
+	jww.INFO.Printf("Details about %v sessions which are invalid:", len(sessions))
+	for i, s := range sessions {
+		if s == nil {
+			jww.INFO.Printf("\tSession %v is nil", i)
+		} else {
+			jww.INFO.Printf("\tSession %v: status: %v, confimred: %v", i, s.Status(), s.IsConfirmed())
+		}
+	}
+
+	return nil
+}
+
+// returns a list of session that need rekeys. Nil instances mean a new rekey
+// from scratch
+func (r *relationship) TriggerNegotiation() []*Session {
+	//dont need to take the lock due to the use of a copy of the buffer
+	sessions := r.getInternalBufferShallowCopy()
+	var instructions []*Session
+	for _, ses := range sessions {
+		if ses.triggerNegotiation() {
+			instructions = append(instructions, ses)
+		}
+	}
+	return instructions
+}
+
+// returns a key which should be used for rekeying
+func (r *relationship) getKeyForRekey() (*Key, error) {
+	r.sendMux.Lock()
+	defer r.sendMux.Unlock()
+	s := r.getNewestRekeyableSession()
+	if s == nil {
+		return nil, errors.New("Failed to find a session for rekeying")
+	}
+
+	return s.PopReKey()
+}
+
+// returns the newest session which can be used to start a key negotiation
+func (r *relationship) getNewestRekeyableSession() *Session {
+	//dont need to take the lock due to the use of a copy of the buffer
+	sessions := r.getInternalBufferShallowCopy()
+	if len(sessions) == 0 {
+		return nil
+	}
+	for _, s := range r.sessions {
+		//fmt.Println(i)
+		// This looks like it might not be thread safe, I think it is because
+		// the failure mode is it skips to a lower key to rekey with, which is
+		// always valid. It isn't clear it can fail though because we are
+		// accessing the data in the same order it would be written (i think)
+		if s.Status() != RekeyEmpty && s.IsConfirmed() {
+			return s
+		}
+	}
+	return nil
+}
+
+func (r *relationship) GetByID(id SessionID) *Session {
+	r.mux.RLock()
+	defer r.mux.RUnlock()
+	return r.sessionByID[id]
+}
+
+// sets the passed session ID as confirmed. Call "GetSessionRotation" after
+// to get any sessions that are to be deleted and then "DeleteSession" to
+// remove them
+func (r *relationship) Confirm(id SessionID) error {
+	r.mux.Lock()
+	defer r.mux.Unlock()
+
+	s, ok := r.sessionByID[id]
+	if !ok {
+		return errors.Errorf("Could not confirm session %s, does not exist", id)
+	}
+
+	s.SetNegotiationStatus(Confirmed)
+
+	r.clean()
+
+	return nil
+}
+
+// adding or removing a session is always done via replacing the entire
+// slice, this allow us to copy the slice under the read lock and do the
+// rest of the work while not taking the lock
+func (r *relationship) getInternalBufferShallowCopy() []*Session {
+	r.mux.RLock()
+	defer r.mux.RUnlock()
+	return r.sessions
+}
+
+func (r *relationship) clean() {
+
+	numConfirmed := uint(0)
+
+	var newSessions []*Session
+	editsMade := false
+
+	for _, s := range r.sessions {
+		if s.IsConfirmed() {
+			numConfirmed++
+			//if the number of newer confirmed is sufficient, delete the confirmed
+			if numConfirmed > maxUnconfirmed {
+				delete(r.sessionByID, s.GetID())
+				s.Delete()
+				editsMade = true
+				continue
+			}
+		}
+		newSessions = append(newSessions, s)
+	}
+
+	//only do the update and save if changes occured
+	if editsMade {
+		r.sessions = newSessions
+
+		if err := r.save(); err != nil {
+			jww.FATAL.Printf("Failed to save Session Buffer %s after "+
+				"clean: %s", r.kv.GetFullKey(relationshipKey,
+				currentRelationshipVersion), err)
+		}
+	}
+}
diff --git a/storage/e2e/relationshipFingerprint.go b/storage/e2e/relationshipFingerprint.go
new file mode 100644
index 0000000000000000000000000000000000000000..f071e5ffb0dcf4e90e40e461338eedbf9c78fc51
--- /dev/null
+++ b/storage/e2e/relationshipFingerprint.go
@@ -0,0 +1,59 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+func makeRelationshipFingerprint(t RelationshipType, grp *cyclic.Group,
+	myPrivKey, partnerPubKey *cyclic.Int, me, partner *id.ID) []byte {
+
+	myPubKey := grp.ExpG(myPrivKey, grp.NewIntFromUInt(1))
+
+	switch t {
+	case Send:
+		return e2e.MakeRelationshipFingerprint(myPubKey, partnerPubKey,
+			me, partner)
+	case Receive:
+		return e2e.MakeRelationshipFingerprint(myPubKey, partnerPubKey,
+			partner, me)
+	default:
+		jww.FATAL.Panicf("Cannot built relationship fingerprint for "+
+			"'%s'", t)
+	}
+	return nil
+}
+
+func storeRelationshipFingerprint(fp []byte, kv *versioned.KV) error {
+	now := time.Now()
+	obj := versioned.Object{
+		Version:   currentRelationshipFingerprintVersion,
+		Timestamp: now,
+		Data:      fp,
+	}
+
+	return kv.Set(relationshipFingerprintKey, currentRelationshipVersion,
+		&obj)
+}
+
+func loadRelationshipFingerprint(kv *versioned.KV) []byte {
+	obj, err := kv.Get(relationshipFingerprintKey,
+		currentRelationshipVersion)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to load relationshipFingerprint at %s: "+
+			"%s", kv.GetFullKey(relationshipFingerprintKey,
+			currentRelationshipFingerprintVersion), err)
+	}
+	return obj.Data
+}
diff --git a/storage/e2e/relationshipType.go b/storage/e2e/relationshipType.go
new file mode 100644
index 0000000000000000000000000000000000000000..5fb5980f1f1affde2165328aa719fb16ff40b1d4
--- /dev/null
+++ b/storage/e2e/relationshipType.go
@@ -0,0 +1,43 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"fmt"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+type RelationshipType uint
+
+const (
+	Send RelationshipType = iota
+	Receive
+)
+
+func (rt RelationshipType) String() string {
+	switch rt {
+	case Send:
+		return "Send"
+	case Receive:
+		return "Receive"
+	default:
+		return fmt.Sprintf("Unknown relationship type: %d", rt)
+	}
+}
+
+func (rt RelationshipType) prefix() string {
+	switch rt {
+	case Send:
+		return "Send"
+	case Receive:
+		return "Receive"
+	default:
+		jww.FATAL.Panicf("No prefix for relationship type: %s", rt)
+	}
+	return ""
+}
diff --git a/storage/e2e/relationship_test.go b/storage/e2e/relationship_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..df9e7be0ecc120ab445ab055808e05dcc4cbc884
--- /dev/null
+++ b/storage/e2e/relationship_test.go
@@ -0,0 +1,536 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"reflect"
+	"testing"
+)
+
+// Subtest: unmarshal/marshal with one session in the buff
+func TestRelationship_MarshalUnmarshal(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+
+	// Serialization should include session slice only
+	serialized, err := sb.marshal()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	sb2 := &relationship{
+		manager:     mgr,
+		t:           0,
+		kv:          sb.kv,
+		sessions:    make([]*Session, 0),
+		sessionByID: make(map[SessionID]*Session),
+	}
+
+	err = sb2.unmarshal(serialized)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// compare sb2 sesh list and map
+	if !relationshipsEqual(sb, sb2) {
+		t.Error("session buffs not equal")
+	}
+}
+
+// Shows that Relationship returns an equivalent session buff to the one that was saved
+func TestLoadRelationship(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+
+	err := sb.save()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	sb2, err := LoadRelationship(mgr, Send)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !relationshipsEqual(sb, sb2) {
+		t.Error("session buffers not equal")
+	}
+}
+
+// Shows that Relationship returns a valid session buff
+func TestNewRelationshipBuff(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	if mgr != sb.manager {
+		t.Error("managers should be identical")
+	}
+	if sb.sessionByID == nil || len(sb.sessionByID) != 1 {
+		t.Error("session map should not be nil, and should have one " +
+			"element")
+	}
+	if sb.sessions == nil || len(sb.sessions) != 1 {
+		t.Error("session list should not be nil, and should have one " +
+			"element")
+	}
+}
+
+// Shows that AddSession adds one session to the relationship
+func TestRelationship_AddSession(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	if len(sb.sessions) != 1 {
+		t.Error("starting session slice length should be 1")
+	}
+	if len(sb.sessionByID) != 1 {
+		t.Error("starting session map length should be 1")
+	}
+	session, _ := makeTestSession()
+	// Note: AddSession doesn't change the session relationship or set anything else up
+	// to match the session to the session buffer. To work properly, the session
+	// should have been created using the same relationship (which is not the case in
+	// this test.)
+	sb.AddSession(session.myPrivKey, session.partnerPubKey, nil,
+		session.partnerSource, Sending, session.e2eParams)
+	if len(sb.sessions) != 2 {
+		t.Error("ending session slice length should be 2")
+	}
+	if len(sb.sessionByID) != 2 {
+		t.Error("ending session map length should be 2")
+	}
+	if session.GetID() != sb.sessions[0].GetID() {
+		t.Error("session added should have same ID")
+	}
+}
+
+// GetNewest should get the session that was most recently added to the buff
+func TestRelationship_GetNewest(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	// The newest session should be nil upon session buffer creation
+	nilSession := sb.GetNewest()
+	if nilSession == nil {
+		t.Error("should not have gotten a nil session from a buffer " +
+			"with one session")
+	}
+
+	session, _ := makeTestSession()
+	sb.AddSession(session.myPrivKey, session.partnerPubKey, nil,
+		session.partnerSource, Sending, session.e2eParams)
+	if session.GetID() != sb.GetNewest().GetID() {
+		t.Error("session added should have same ID")
+	}
+
+	session2, _ := makeTestSession()
+	sb.AddSession(session2.myPrivKey, session2.partnerPubKey, nil,
+		session2.partnerSource, Sending, session.e2eParams)
+	if session2.GetID() != sb.GetNewest().GetID() {
+		t.Error("session added should have same ID")
+	}
+
+}
+
+// Shows that Confirm confirms the specified session in the buff
+func TestRelationship_Confirm(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	session, _ := makeTestSession()
+
+	sb.AddSession(session.myPrivKey, session.partnerPubKey, nil,
+		session.partnerSource, Sending, session.e2eParams)
+	sb.sessions[1].negotiationStatus = Sent
+
+	if sb.sessions[1].IsConfirmed() {
+		t.Error("session should not be confirmed before confirmation")
+	}
+
+	err := sb.Confirm(sb.sessions[1].GetID())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !sb.sessions[1].IsConfirmed() {
+		t.Error("session should be confirmed after confirmation")
+	}
+}
+
+// Shows that the session buff returns an error when the session doesn't exist
+func TestRelationship_Confirm_Err(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	session, _ := makeTestSession()
+
+	err := sb.Confirm(session.GetID())
+	if err == nil {
+		t.Error("Confirming a session not in the buff should result in an error")
+	}
+}
+
+// Shows that a session can get got by ID from the buff
+func TestRelationship_GetByID(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	session, _ := makeTestSession()
+	session = sb.AddSession(session.myPrivKey, session.partnerPubKey, nil,
+		session.partnerSource, Sending, session.e2eParams)
+	session2 := sb.GetByID(session.GetID())
+	if !reflect.DeepEqual(session, session2) {
+		t.Error("gotten session should be the same")
+	}
+}
+
+// Shows that GetNewestRekeyableSession acts as expected:
+// returning sessions that are confirmed and past rekeyThreshold
+func TestRelationship_GetNewestRekeyableSession(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	sb.sessions[0].negotiationStatus = Unconfirmed
+	// no available rekeyable sessions: nil
+	session2 := sb.getNewestRekeyableSession()
+	if session2 != nil {
+		t.Error("newest rekeyable session should be nil")
+	}
+
+	// add a rekeyable session: that session
+	session, _ := makeTestSession()
+	sb.AddSession(session.myPrivKey, session.partnerPubKey, session.baseKey,
+		session.partnerSource, Sending, session.e2eParams)
+	sb.sessions[0].negotiationStatus = Confirmed
+	session3 := sb.getNewestRekeyableSession()
+
+	if session3 == nil {
+		t.Error("no session returned")
+	} else if session3.GetID() != sb.sessions[0].GetID() {
+		t.Error("didn't get the expected session")
+	}
+
+	// add another rekeyable session: that session
+	// show the newest session is selected
+	additionalSession, _ := makeTestSession()
+	sb.AddSession(additionalSession.myPrivKey, additionalSession.partnerPubKey,
+		additionalSession.partnerPubKey, additionalSession.partnerSource,
+		Sending, additionalSession.e2eParams)
+
+	sb.sessions[0].negotiationStatus = Confirmed
+
+	session4 := sb.getNewestRekeyableSession()
+	if session4 == nil {
+		t.Error("no session returned")
+	} else if session4.GetID() != sb.sessions[0].GetID() {
+		t.Error("didn't get the expected session")
+	}
+
+	// make the very newest session unrekeyable: the previous session
+	//sb.sessions[1].negotiationStatus = Confirmed
+	sb.sessions[0].negotiationStatus = Unconfirmed
+
+	session5 := sb.getNewestRekeyableSession()
+	if session5 == nil {
+		t.Error("no session returned")
+	} else if session5.GetID() != sb.sessions[1].GetID() {
+		t.Error("didn't get the expected session")
+	}
+}
+
+// Shows that GetSessionForSending follows the hierarchy of sessions correctly
+func TestRelationship_GetSessionForSending(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+
+	sb.sessions = make([]*Session, 0)
+	sb.sessionByID = make(map[SessionID]*Session)
+
+	none := sb.getSessionForSending()
+	if none != nil {
+		t.Error("getSessionForSending should return nil if there aren't any sendable sessions")
+	}
+
+	// First case: unconfirmed rekey
+	unconfirmedRekey, _ := makeTestSession()
+
+	sb.AddSession(unconfirmedRekey.myPrivKey, unconfirmedRekey.partnerPubKey,
+		unconfirmedRekey.partnerPubKey, unconfirmedRekey.partnerSource,
+		Sending, unconfirmedRekey.e2eParams)
+	sb.sessions[0].negotiationStatus = Unconfirmed
+	sb.sessions[0].keyState.numkeys = 2000
+	sb.sessions[0].rekeyThreshold = 1000
+	sb.sessions[0].keyState.numAvailable = 600
+	sending := sb.getSessionForSending()
+	if sending.GetID() != sb.sessions[0].GetID() {
+		t.Error("got an unexpected session")
+	}
+	if sending.Status() != RekeyNeeded || sending.IsConfirmed() {
+		t.Errorf("returned session is expected to be 'RekeyNedded' "+
+			"'Unconfirmed', it is: %s, confirmed: %v", sending.Status(),
+			sending.IsConfirmed())
+	}
+
+	// Second case: unconfirmed active
+	unconfirmedActive, _ := makeTestSession()
+
+	sb.AddSession(unconfirmedActive.myPrivKey, unconfirmedActive.partnerPubKey,
+		unconfirmedActive.partnerPubKey, unconfirmedActive.partnerSource,
+		Sending, unconfirmedActive.e2eParams)
+	sb.sessions[0].negotiationStatus = Unconfirmed
+	sb.sessions[0].keyState.numkeys = 2000
+	sb.sessions[0].rekeyThreshold = 1000
+	sb.sessions[0].keyState.numAvailable = 2000
+	sending = sb.getSessionForSending()
+	if sending.GetID() != sb.sessions[0].GetID() {
+		t.Error("got an unexpected session")
+	}
+
+	if sending.Status() != Active || sending.IsConfirmed() {
+		t.Errorf("returned session is expected to be 'Active' "+
+			"'Unconfirmed', it is: %s, confirmed: %v", sending.Status(),
+			sending.IsConfirmed())
+	}
+
+	// Third case: confirmed rekey
+	confirmedRekey, _ := makeTestSession()
+
+	sb.AddSession(confirmedRekey.myPrivKey, confirmedRekey.partnerPubKey,
+		confirmedRekey.partnerPubKey, confirmedRekey.partnerSource,
+		Sending, confirmedRekey.e2eParams)
+	sb.sessions[0].negotiationStatus = Confirmed
+	sb.sessions[0].keyState.numkeys = 2000
+	sb.sessions[0].rekeyThreshold = 1000
+	sb.sessions[0].keyState.numAvailable = 600
+	sending = sb.getSessionForSending()
+	if sending.GetID() != sb.sessions[0].GetID() {
+		t.Error("got an unexpected session")
+	}
+	if sending.Status() != RekeyNeeded || !sending.IsConfirmed() {
+		t.Errorf("returned session is expected to be 'RekeyNeeded' "+
+			"'Confirmed', it is: %s, confirmed: %v", sending.Status(),
+			sending.IsConfirmed())
+	}
+
+	// Fourth case: confirmed active
+	confirmedActive, _ := makeTestSession()
+	sb.AddSession(confirmedActive.myPrivKey, confirmedActive.partnerPubKey,
+		confirmedActive.partnerPubKey, confirmedActive.partnerSource,
+		Sending, confirmedActive.e2eParams)
+
+	sb.sessions[0].negotiationStatus = Confirmed
+	sb.sessions[0].keyState.numkeys = 2000
+	sb.sessions[0].keyState.numAvailable = 2000
+	sb.sessions[0].rekeyThreshold = 1000
+	sending = sb.getSessionForSending()
+	if sending.GetID() != sb.sessions[0].GetID() {
+		t.Errorf("got an unexpected session of state: %s", sending.Status())
+	}
+	if sending.Status() != Active || !sending.IsConfirmed() {
+		t.Errorf("returned session is expected to be 'Active' "+
+			"'Confirmed', it is: %s, confirmed: %v", sending.Status(),
+			sending.IsConfirmed())
+	}
+}
+
+// Shows that GetKeyForRekey returns a key if there's an appropriate session for rekeying
+func TestSessionBuff_GetKeyForRekey(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+
+	sb.sessions = make([]*Session, 0)
+	sb.sessionByID = make(map[SessionID]*Session)
+
+	// no available rekeyable sessions: error
+	key, err := sb.getKeyForRekey()
+	if err == nil {
+		t.Error("should have returned an error with no sessions available")
+	}
+	if key != nil {
+		t.Error("shouldn't have returned a key with no sessions available")
+	}
+
+	session, _ := makeTestSession()
+	sb.AddSession(session.myPrivKey, session.partnerPubKey,
+		session.partnerPubKey, session.partnerSource,
+		Sending, session.e2eParams)
+	sb.sessions[0].negotiationStatus = Confirmed
+	key, err = sb.getKeyForRekey()
+	if err != nil {
+		t.Error(err)
+	}
+	if key == nil {
+		t.Error("should have returned a valid key with a rekeyable session available")
+	}
+}
+
+// Shows that GetKeyForSending returns a key if there's an appropriate session for sending
+func TestSessionBuff_GetKeyForSending(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+
+	sb.sessions = make([]*Session, 0)
+	sb.sessionByID = make(map[SessionID]*Session)
+
+	// no available rekeyable sessions: error
+	key, err := sb.getKeyForSending()
+	if err == nil {
+		t.Error("should have returned an error with no sessions available")
+	}
+	if key != nil {
+		t.Error("shouldn't have returned a key with no sessions available")
+	}
+
+	session, _ := makeTestSession()
+	sb.AddSession(session.myPrivKey, session.partnerPubKey,
+		session.partnerPubKey, session.partnerSource,
+		Sending, session.e2eParams)
+	key, err = sb.getKeyForSending()
+	if err != nil {
+		t.Error(err)
+	}
+	if key == nil {
+		t.Error("should have returned a valid key with a sendable session available")
+	}
+}
+
+// Shows that TriggerNegotiation sets up for negotiation correctly
+func TestSessionBuff_TriggerNegotiation(t *testing.T) {
+	mgr := makeTestRelationshipManager(t)
+	sb := NewRelationship(mgr, Send, params.GetDefaultE2ESessionParams())
+	sb.sessions = make([]*Session, 0)
+	sb.sessionByID = make(map[SessionID]*Session)
+
+	session, _ := makeTestSession()
+	session = sb.AddSession(session.myPrivKey, session.partnerPubKey,
+		session.partnerPubKey, session.partnerSource,
+		Sending, session.e2eParams)
+	session.negotiationStatus = Confirmed
+	// The added session isn't ready for rekey so it's not returned here
+	negotiations := sb.TriggerNegotiation()
+	if len(negotiations) != 0 {
+		t.Errorf("should have had zero negotiations: %+v", negotiations)
+	}
+	session2, _ := makeTestSession()
+	// Make only a few keys available to trigger the rekeyThreshold
+	session2 = sb.AddSession(session2.myPrivKey, session2.partnerPubKey,
+		session2.partnerPubKey, session2.partnerSource,
+		Sending, session2.e2eParams)
+	session2.keyState.numAvailable = 4
+	session2.negotiationStatus = Confirmed
+	negotiations = sb.TriggerNegotiation()
+	if len(negotiations) != 1 {
+		t.Fatal("should have had one negotiation")
+	}
+	if negotiations[0].GetID() != session2.GetID() {
+		t.Error("negotiated sessions should include the rekeyable " +
+			"session")
+	}
+	if session2.negotiationStatus != NewSessionTriggered {
+		t.Errorf("Trigger negotiations should have set status to "+
+			"triggered: %s", session2.negotiationStatus)
+	}
+
+	// Unconfirmed sessions should also be included in the list
+	// as the client should attempt to confirm them
+	session3, _ := makeTestSession()
+
+	session3 = sb.AddSession(session3.myPrivKey, session3.partnerPubKey,
+		session3.partnerPubKey, session3.partnerSource,
+		Sending, session3.e2eParams)
+	session3.negotiationStatus = Unconfirmed
+
+	// Set session 2 status back to Confirmed to show that more than one session can be returned
+	session2.negotiationStatus = Confirmed
+	// Trigger negotiations
+	negotiations = sb.TriggerNegotiation()
+
+	if len(negotiations) != 2 {
+		t.Fatal("num of negotiated sessions here should be 2")
+	}
+	found := false
+	for i := range negotiations {
+		if negotiations[i].GetID() == session3.GetID() {
+			found = true
+			if negotiations[i].negotiationStatus != Sending {
+				t.Error("triggering negotiation should change session3 to sending")
+			}
+		}
+	}
+	if !found {
+		t.Error("session3 not found")
+	}
+
+	found = false
+	for i := range negotiations {
+		if negotiations[i].GetID() == session2.GetID() {
+			found = true
+		}
+	}
+	if !found {
+		t.Error("session2 not found")
+	}
+}
+
+func makeTestRelationshipManager(t *testing.T) *Manager {
+	fps := newFingerprints()
+	g := getGroup()
+	return &Manager{
+		ctx: &context{
+			fa:   &fps,
+			grp:  g,
+			myID: &id.ID{},
+		},
+		kv:                  versioned.NewKV(make(ekv.Memstore)),
+		partner:             id.NewIdFromUInt(8, id.User, t),
+		originMyPrivKey:     g.NewInt(2),
+		originPartnerPubKey: g.NewInt(3),
+	}
+}
+
+// Revises a session to fit a sessionbuff and saves it to the sessionbuff's kv store
+func adaptToBuff(session *Session, buff *relationship, t *testing.T) {
+	session.relationship = buff
+	session.keyState.kv = buff.manager.kv
+	err := session.keyState.save()
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = session.save()
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+// Compare certain fields of two session buffs for equality
+func relationshipsEqual(buff *relationship, buff2 *relationship) bool {
+	if len(buff.sessionByID) != len(buff2.sessionByID) {
+		return false
+	}
+	if len(buff.sessions) != len(buff2.sessions) {
+		return false
+	}
+
+	if !bytes.Equal(buff.fingerprint, buff2.fingerprint) {
+		return false
+	}
+	// Make sure all sessions are present
+	for k := range buff.sessionByID {
+		_, ok := buff2.sessionByID[k]
+		if !ok {
+			// key not present in other map
+			return false
+		}
+	}
+	// Comparing base key only for now
+	// This should ensure that the session buffers have the same sessions in the same order
+	for i := range buff.sessions {
+		if buff.sessions[i].baseKey.Cmp(buff2.sessions[i].baseKey) != 0 {
+			return false
+		}
+	}
+	return true
+}
diff --git a/storage/e2e/session.go b/storage/e2e/session.go
new file mode 100644
index 0000000000000000000000000000000000000000..76c614861f663ff834e22cc53e7e54c72f58d3ff
--- /dev/null
+++ b/storage/e2e/session.go
@@ -0,0 +1,599 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"encoding/json"
+	"fmt"
+	"math/big"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/xx_network/crypto/randomness"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+const currentSessionVersion = 0
+const sessionPrefix = "session{ID:%s}"
+const sessionKey = "session"
+
+type Session struct {
+	//pointer to relationship
+	relationship *relationship
+	//prefixed kv
+	kv *versioned.KV
+	//params
+	e2eParams params.E2ESessionParams
+
+	//type
+	t RelationshipType
+
+	// Underlying key
+	baseKey *cyclic.Int
+	// Own Private Key
+	myPrivKey *cyclic.Int
+	// Partner Public Key
+	partnerPubKey *cyclic.Int
+	// ID of the session which teh partner public key comes from for this
+	// sessions creation.  Shares a partner public key if a Send session,
+	// shares a myPrivateKey if a Receive session
+	partnerSource SessionID
+	//fingerprint of relationship
+	relationshipFingerprint []byte
+
+	//denotes if the other party has confirmed this key
+	negotiationStatus Negotiation
+
+	// Number of keys used before the system attempts a rekey
+	rekeyThreshold uint32
+
+	// Received Keys dirty bits
+	// Each bit represents a single Key
+	keyState *stateVector
+
+	//mutex
+	mux sync.RWMutex
+}
+
+// As this is serialized by json, any field that should be serialized
+// must be exported
+// Utility struct to write part of session data to disk
+type SessionDisk struct {
+	E2EParams params.E2ESessionParams
+
+	//session type
+	Type uint8
+
+	// Underlying key
+	BaseKey []byte
+	// Own Private Key
+	MyPrivKey []byte
+	// Partner Public Key
+	PartnerPubKey []byte
+	// ID of the session which triggered this sessions creation.
+	Trigger []byte
+	// relationship fp
+	RelationshipFingerprint []byte
+
+	//denotes if the other party has confirmed this key
+	Confirmation uint8
+
+	// Number of keys usable before rekey
+	RekeyThreshold uint32
+}
+
+/*CONSTRUCTORS*/
+//Generator which creates all keys and structures
+func newSession(ship *relationship, t RelationshipType, myPrivKey, partnerPubKey,
+	baseKey *cyclic.Int, trigger SessionID, relationshipFingerprint []byte,
+	negotiationStatus Negotiation, e2eParams params.E2ESessionParams) *Session {
+
+	if e2eParams.MinKeys < 10 {
+		jww.FATAL.Panicf("Cannot create a session with a minnimum number " +
+			"of keys less than 10")
+	}
+
+	session := &Session{
+		e2eParams:               e2eParams,
+		relationship:            ship,
+		t:                       t,
+		myPrivKey:               myPrivKey,
+		partnerPubKey:           partnerPubKey,
+		baseKey:                 baseKey,
+		relationshipFingerprint: relationshipFingerprint,
+		negotiationStatus:       negotiationStatus,
+		partnerSource:           trigger,
+	}
+
+	session.kv = session.generate(ship.kv)
+
+	jww.INFO.Printf("New Session with Partner %s:\n\tType: %s"+
+		"\n\tBaseKey: %s\n\tRelationship Fingerprint: %v\n\tNumKeys: %d"+
+		"\n\tMy Private Key: %s\n\tPartner Public Key: %s",
+		ship.manager.partner,
+		t,
+		session.baseKey.TextVerbose(16, 0),
+		session.relationshipFingerprint,
+		session.rekeyThreshold,
+		session.myPrivKey.TextVerbose(16, 0),
+		session.partnerPubKey.TextVerbose(16, 0))
+
+	err := session.save()
+	if err != nil {
+		jww.FATAL.Printf("Failed to make new session for Partner %s: %s",
+			ship.manager.partner, err)
+	}
+
+	return session
+}
+
+// Load session and state vector from kv and populate runtime fields
+func loadSession(ship *relationship, kv *versioned.KV,
+	relationshipFingerprint []byte) (*Session, error) {
+
+	session := Session{
+		relationship: ship,
+		kv:           kv,
+	}
+
+	obj, err := kv.Get(sessionKey, currentSessionVersion)
+	if err != nil {
+		return nil, errors.WithMessagef(err, "Failed to load %s",
+			kv.GetFullKey(sessionKey, currentSessionVersion))
+	}
+
+	// TODO: Not necessary until we have versions on this object...
+	//obj, err := sessionUpgradeTable.Upgrade(obj)
+
+	err = session.unmarshal(obj.Data)
+	if err != nil {
+		return nil, err
+	}
+
+	if session.t == Receive {
+		// register key fingerprints
+		ship.manager.ctx.fa.add(session.getUnusedKeys())
+	}
+	session.relationshipFingerprint = relationshipFingerprint
+
+	return &session, nil
+}
+
+func (s *Session) save() error {
+
+	now := time.Now()
+
+	data, err := s.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentSessionVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return s.kv.Set(sessionKey, currentSessionVersion, &obj)
+}
+
+/*METHODS*/
+// Done all unused key fingerprints
+// delete this session and its key states from the storage
+func (s *Session) Delete() {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	s.relationship.manager.ctx.fa.remove(s.getUnusedKeys())
+
+	stateVectorErr := s.keyState.Delete()
+	sessionErr := s.kv.Delete(sessionKey, currentSessionVersion)
+
+	if stateVectorErr != nil && sessionErr != nil {
+		jww.ERROR.Printf("Error deleting state vector with key %v: %v", stateVectorKey, stateVectorErr.Error())
+		jww.ERROR.Panicf("Error deleting session with key %v: %v", sessionKey, sessionErr)
+	} else if sessionErr != nil {
+		jww.ERROR.Panicf("Error deleting session with key %v: %v", sessionKey, sessionErr)
+	} else if stateVectorErr != nil {
+		jww.ERROR.Panicf("Error deleting state vector with key %v: %v", stateVectorKey, stateVectorErr.Error())
+	}
+}
+
+//Gets the base key.
+func (s *Session) GetBaseKey() *cyclic.Int {
+	// no lock is needed because this cannot be edited
+	return s.baseKey.DeepCopy()
+}
+
+func (s *Session) GetMyPrivKey() *cyclic.Int {
+	// no lock is needed because this cannot be edited
+	return s.myPrivKey.DeepCopy()
+}
+
+func (s *Session) GetPartnerPubKey() *cyclic.Int {
+	// no lock is needed because this cannot be edited
+	return s.partnerPubKey.DeepCopy()
+}
+
+func (s *Session) GetSource() SessionID {
+	// no lock is needed because this cannot be edited
+	return s.partnerSource
+}
+
+//underlying definition of session id
+func getSessionIDFromBaseKey(baseKey *cyclic.Int) SessionID {
+	// no lock is needed because this cannot be edited
+	sid := SessionID{}
+	h, _ := hash.NewCMixHash()
+	h.Write(baseKey.Bytes())
+	copy(sid[:], h.Sum(nil))
+	return sid
+}
+
+//underlying definition of session id
+// FOR TESTING PURPOSES ONLY
+func GetSessionIDFromBaseKeyForTesting(baseKey *cyclic.Int, i interface{}) SessionID {
+	switch i.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("GetSessionIDFromBaseKeyForTesting is restricted to testing only. Got %T", i)
+	}
+	return getSessionIDFromBaseKey(baseKey)
+}
+
+//Blake2B hash of base key used for storage
+func (s *Session) GetID() SessionID {
+	return getSessionIDFromBaseKey(s.baseKey)
+}
+
+// returns the ID of the partner for this session
+func (s *Session) GetPartner() *id.ID {
+	if s.relationship != nil {
+		return s.relationship.manager.partner
+	} else {
+		return nil
+	}
+}
+
+//ekv functions
+func (s *Session) marshal() ([]byte, error) {
+	sd := SessionDisk{}
+
+	sd.E2EParams = s.e2eParams
+	sd.Type = uint8(s.t)
+	sd.BaseKey = s.baseKey.Bytes()
+	sd.MyPrivKey = s.myPrivKey.Bytes()
+	sd.PartnerPubKey = s.partnerPubKey.Bytes()
+	sd.Trigger = s.partnerSource[:]
+	sd.RelationshipFingerprint = s.relationshipFingerprint
+
+	// assume in progress confirmations and session creations have failed on
+	// reset, therefore do not store their pending progress
+	if s.negotiationStatus == Sending {
+		sd.Confirmation = uint8(Unconfirmed)
+	} else if s.negotiationStatus == NewSessionTriggered {
+		sd.Confirmation = uint8(Confirmed)
+	} else {
+		sd.Confirmation = uint8(s.negotiationStatus)
+	}
+
+	sd.RekeyThreshold = s.rekeyThreshold
+
+	return json.Marshal(&sd)
+}
+
+func (s *Session) unmarshal(b []byte) error {
+
+	sd := SessionDisk{}
+
+	err := json.Unmarshal(b, &sd)
+
+	if err != nil {
+		return err
+	}
+
+	grp := s.relationship.manager.ctx.grp
+
+	s.e2eParams = sd.E2EParams
+	s.t = RelationshipType(sd.Type)
+	s.baseKey = grp.NewIntFromBytes(sd.BaseKey)
+	s.myPrivKey = grp.NewIntFromBytes(sd.MyPrivKey)
+	s.partnerPubKey = grp.NewIntFromBytes(sd.PartnerPubKey)
+	s.negotiationStatus = Negotiation(sd.Confirmation)
+	s.rekeyThreshold = sd.RekeyThreshold
+	s.relationshipFingerprint = sd.RelationshipFingerprint
+	copy(s.partnerSource[:], sd.Trigger)
+
+	s.keyState, err = loadStateVector(s.kv, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+//key usage
+// Pops the first unused key, skipping any which are denoted as used.
+// will return if the remaining keys are designated as rekeys
+func (s *Session) PopKey() (*Key, error) {
+	if s.keyState.GetNumAvailable() <= uint32(s.e2eParams.NumRekeys) {
+		return nil, errors.New("no more keys left, remaining reserved " +
+			"for rekey")
+	}
+	keyNum, err := s.keyState.Next()
+	if err != nil {
+		return nil, err
+	}
+
+	return newKey(s, keyNum), nil
+}
+
+func (s *Session) PopReKey() (*Key, error) {
+	keyNum, err := s.keyState.Next()
+	if err != nil {
+		return nil, err
+	}
+
+	return newKey(s, keyNum), nil
+}
+
+func (s *Session) GetRelationshipFingerprint() []byte {
+	return s.relationshipFingerprint
+}
+
+// returns the state of the session, which denotes if the Session is active,
+// functional but in need of a rekey, empty of Send key, or empty of rekeys
+func (s *Session) Status() Status {
+	// copy the num available so it stays consistent as this function does its
+	// checks
+	numAvailable := s.keyState.GetNumAvailable()
+	numUsed := s.keyState.GetNumUsed()
+
+	if numAvailable == 0 {
+		return RekeyEmpty
+	} else if numAvailable <= uint32(s.e2eParams.NumRekeys) {
+		return Empty
+		// do not need to make a copy of getNumKeys becasue it is static and
+		// only used once
+	} else if numUsed >= s.rekeyThreshold {
+		return RekeyNeeded
+	} else {
+		return Active
+	}
+}
+
+// Sets the negotiation status, this tracks the state of the key negotiation,
+// only certain movements are allowed
+//   Unconfirmed <--> Sending --> Sent --> Confirmed <--> NewSessionTriggered --> NewSessionCreated
+//				  -------------->
+//
+// Saves the session unless the status is sending so that on reload the rekey
+// will be redone if it was in the process of sending
+
+// Moving from Unconfirmed to Sending and from Confirmed to NewSessionTriggered
+// is handled by  Session.triggerNegotiation() which is called by the
+// Manager as part of Manager.TriggerNegotiations() and will be rejected
+// from this function
+
+var legalStateChanges = [][]bool{
+	{false, false, false, false, false, false},
+	{true, false, true, true, false, false},
+	{false, false, false, true, false, false},
+	{false, false, false, false, true, false},
+	{false, false, false, true, false, true},
+	{false, false, false, false, false, false},
+}
+
+func (s *Session) SetNegotiationStatus(status Negotiation) {
+	if err := s.TrySetNegotiationStatus(status); err != nil {
+		jww.FATAL.Panicf("Failed to set Negotiation status: %s", err)
+	}
+}
+
+func (s *Session) TrySetNegotiationStatus(status Negotiation) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	//only allow the correct state changes to propagate
+	if !legalStateChanges[s.negotiationStatus][status] {
+		return errors.Errorf("Negotiation status change from %s to %s "+
+			"is not valid", s.negotiationStatus, status)
+	}
+
+	// the states of Sending and NewSessionTriggered are not saved to disk when
+	// moved from Unconfirmed or Confirmed respectively so the actions are
+	// re-triggered if there is a crash and reload. As a result, a save when
+	// reverting states is unnecessary
+	save := !((s.negotiationStatus == Sending && status == Unconfirmed) ||
+		(s.negotiationStatus == NewSessionTriggered && status == Confirmed))
+
+	//change the state
+	oldStatus := s.negotiationStatus
+	s.negotiationStatus = status
+
+	//save the status if appropriate
+	if save {
+		if err := s.save(); err != nil {
+			jww.FATAL.Panicf("Failed to save Session %s when moving from %s to %s", s, oldStatus, status)
+		}
+	}
+
+	return nil
+}
+
+// This function, in a mostly thread safe manner, checks if the session needs a
+// negotiation, returns if it does while updating the session to denote the
+// negotiation was triggered
+// WARNING: This function relies on proper action by the caller for data safety.
+// When triggering the creation of a new session (the first case) it does not
+// store to disk the fact that it has triggered the session. This is because
+// every session should only partnerSource one other session and in the event that
+// session partnerSource does not resolve before a crash, by not storing it the
+// partnerSource will automatically happen again when reloading after the crash.
+// In order to ensure the session creation is not triggered again after the
+// reload, it is the responsibility of the caller to call
+// Session.SetConfirmationStatus(NewSessionCreated) .
+func (s *Session) triggerNegotiation() bool {
+	// Due to the fact that a read lock cannot be transitioned to a
+	// write lock, the state checks need to happen a second time because it
+	// is possible for another thread to take the read lock and update the
+	// state between this thread releasing it and regaining it again. In this
+	// case, such double locking is preferable because the majority of the time,
+	// the checked cases will turn out to be false.
+	s.mux.RLock()
+	// If we've used more keys than the RekeyThreshold, it's time for a rekey
+	if s.keyState.GetNumUsed() >= s.rekeyThreshold && s.negotiationStatus == Confirmed {
+		s.mux.RUnlock()
+		s.mux.Lock()
+		if s.keyState.GetNumUsed() >= s.rekeyThreshold && s.negotiationStatus == Confirmed {
+			//partnerSource a rekey to create a new session
+			s.negotiationStatus = NewSessionTriggered
+			// no save is make after the update because we do not want this state
+			// saved to disk. The caller will shortly execute the operation,
+			// and then move to the next state. If a crash occurs before, by not
+			// storing this state this operation will be repeated after reload
+			// The save function has been modified so if another call causes a
+			// save, "NewSessionTriggered" will be overwritten with "Confirmed"
+			// in the saved data.
+			s.mux.Unlock()
+			return true
+		} else {
+			s.mux.Unlock()
+			return false
+		}
+	} else if s.negotiationStatus == Unconfirmed {
+		// retrigger this sessions negotiation
+		s.mux.RUnlock()
+		s.mux.Lock()
+		if s.negotiationStatus == Unconfirmed {
+			s.negotiationStatus = Sending
+			// no save is made after the update because we do not want this state
+			// saved to disk. The caller will shortly execute the operation,
+			// and then move to the next state. If a crash occurs before, by not
+			// storing this state this operation will be repeated after reload
+			// The save function has been modified so if another call causes a
+			// save, "Sending" will be overwritten with "Unconfirmed"
+			// in the saved data.
+			s.mux.Unlock()
+			return true
+		} else {
+			s.mux.Unlock()
+			return false
+		}
+	}
+	s.mux.RUnlock()
+	return false
+}
+
+// checks if the session has been confirmed
+func (s *Session) NegotiationStatus() Negotiation {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.negotiationStatus
+}
+
+// checks if the session has been confirmed
+func (s *Session) IsConfirmed() bool {
+	c := s.NegotiationStatus()
+	//fmt.Println(c)
+	return c >= Confirmed
+}
+
+func (s *Session) String() string {
+	partner := s.GetPartner()
+	if partner != nil {
+		return fmt.Sprintf("{Partner: %s, ID: %s}",
+			partner, s.GetID())
+	} else {
+		return fmt.Sprintf("{Partner: nil, ID: %s}", s.GetID())
+	}
+}
+
+/*PRIVATE*/
+func (s *Session) useKey(keynum uint32) {
+	s.keyState.Use(keynum)
+}
+
+// generates keys from the base data stored in the session object.
+// myPrivKey will be generated if not present
+func (s *Session) generate(kv *versioned.KV) *versioned.KV {
+	grp := s.relationship.manager.ctx.grp
+
+	//generate private key if it is not present
+	if s.myPrivKey == nil {
+		stream := s.relationship.manager.ctx.rng.GetStream()
+		s.myPrivKey = dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp,
+			stream)
+		stream.Close()
+	}
+
+	// compute the base key if it is not already there
+	if s.baseKey == nil {
+		s.baseKey = dh.GenerateSessionKey(s.myPrivKey, s.partnerPubKey, grp)
+	}
+
+	kv = kv.Prefix(makeSessionPrefix(s.GetID()))
+
+	p := s.e2eParams
+	h, _ := hash.NewCMixHash()
+
+	//generate rekeyThreshold and keying info
+	numKeys := uint32(randomness.RandInInterval(big.NewInt(
+		int64(p.MaxKeys-p.MinKeys)),
+		s.baseKey.Bytes(), h).Int64() + int64(p.MinKeys))
+
+	// start rekeying when 75% of keys have been used
+	s.rekeyThreshold = (numKeys * 3) / 4
+
+	// the total number of keys should be the number of rekeys plus the
+	// number of keys to use
+	numKeys = numKeys + uint32(s.e2eParams.NumRekeys)
+
+	//create the new state vectors. This will cause disk operations storing them
+
+	// To generate the state vector key correctly,
+	// basekey must be computed as the session ID is the hash of basekey
+	var err error
+	s.keyState, err = newStateVector(kv, "", numKeys)
+	if err != nil {
+		jww.FATAL.Printf("Failed key generation: %s", err)
+	}
+
+	//register keys for reception if this is a reception session
+	if s.t == Receive {
+		//register keys
+		s.relationship.manager.ctx.fa.add(s.getUnusedKeys())
+	}
+
+	return kv
+}
+
+//returns key objects for all unused keys
+func (s *Session) getUnusedKeys() []*Key {
+	keyNums := s.keyState.GetUnusedKeyNums()
+
+	keys := make([]*Key, len(keyNums))
+	for i, keyNum := range keyNums {
+		keys[i] = newKey(s, keyNum)
+	}
+
+	return keys
+}
+
+//builds the
+func makeSessionPrefix(sid SessionID) string {
+	return fmt.Sprintf(sessionPrefix, sid)
+}
diff --git a/storage/e2e/sessionID.go b/storage/e2e/sessionID.go
new file mode 100644
index 0000000000000000000000000000000000000000..00b8ebca5c4709dc12d06da89c247eafbb09894d
--- /dev/null
+++ b/storage/e2e/sessionID.go
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"encoding/base64"
+	"github.com/pkg/errors"
+)
+
+const sessionIDLen = 32
+
+type SessionID [sessionIDLen]byte
+
+func (sid SessionID) Marshal() []byte {
+	return sid[:]
+}
+
+func (sid SessionID) String() string {
+	return base64.StdEncoding.EncodeToString(sid[:])
+}
+
+func (sid *SessionID) Unmarshal(b []byte) error {
+	if len(b) != sessionIDLen {
+		return errors.New("SessionID of invalid length received")
+	}
+	copy(sid[:], b)
+	return nil
+}
diff --git a/storage/e2e/session_test.go b/storage/e2e/session_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..495dc39095218506a1bbe427a746b971d0998164
--- /dev/null
+++ b/storage/e2e/session_test.go
@@ -0,0 +1,647 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"errors"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/primitives/id"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestSession_generate_noPrivateKeyReceive(t *testing.T) {
+
+	grp := getGroup()
+	rng := csprng.NewSystemRNG()
+	partnerPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+	partnerPubKey := dh.GeneratePublicKey(partnerPrivKey, grp)
+
+	//create context objects for general use
+	fps := newFingerprints()
+	ctx := &context{
+		fa:  &fps,
+		grp: grp,
+		rng: fastRNG.NewStreamGenerator(1, 0, csprng.NewSystemRNG),
+	}
+
+	//build the session
+	s := &Session{
+		partnerPubKey: partnerPubKey,
+		e2eParams:     params.GetDefaultE2ESessionParams(),
+		relationship: &relationship{
+			manager: &Manager{ctx: ctx},
+		},
+		t: Receive,
+	}
+
+	//run the generate command
+	s.generate(versioned.NewKV(make(ekv.Memstore)))
+
+	//check that it generated a private key
+	if s.myPrivKey == nil {
+		t.Errorf("Private key was not generated when missing")
+	}
+
+	//verify the basekey is correct
+	expectedBaseKey := dh.GenerateSessionKey(s.myPrivKey, s.partnerPubKey, grp)
+
+	if expectedBaseKey.Cmp(s.baseKey) != 0 {
+		t.Errorf("generated base key does not match expected base key")
+	}
+
+	//verify the rekeyThreshold was generated
+	if s.rekeyThreshold == 0 {
+		t.Errorf("rekeyThreshold not generated")
+	}
+
+	//verify keystates where created
+	if s.keyState == nil {
+		t.Errorf("keystates not generated")
+	}
+
+	//verify keys were registered in the fingerprintMap
+	for keyNum := uint32(0); keyNum < s.keyState.numkeys; keyNum++ {
+		key := newKey(s, keyNum)
+		if _, ok := fps.toKey[key.Fingerprint()]; !ok {
+			t.Errorf("key %v not in fingerprint map", keyNum)
+		}
+	}
+}
+
+func TestSession_generate_PrivateKeySend(t *testing.T) {
+
+	grp := getGroup()
+	rng := csprng.NewSystemRNG()
+	partnerPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+	partnerPubKey := dh.GeneratePublicKey(partnerPrivKey, grp)
+
+	myPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+
+	//create context objects for general use
+	fps := newFingerprints()
+	ctx := &context{
+		fa:  &fps,
+		grp: grp,
+	}
+
+	//build the session
+	s := &Session{
+		myPrivKey:     myPrivKey,
+		partnerPubKey: partnerPubKey,
+		e2eParams:     params.GetDefaultE2ESessionParams(),
+		relationship: &relationship{
+			manager: &Manager{ctx: ctx},
+		},
+		t: Send,
+	}
+
+	//run the generate command
+	s.generate(versioned.NewKV(make(ekv.Memstore)))
+
+	//check that it generated a private key
+	if s.myPrivKey.Cmp(myPrivKey) != 0 {
+		t.Errorf("Public key was generated when not missing")
+	}
+
+	//verify the basekey is correct
+	expectedBaseKey := dh.GenerateSessionKey(s.myPrivKey, s.partnerPubKey, grp)
+
+	if expectedBaseKey.Cmp(s.baseKey) != 0 {
+		t.Errorf("generated base key does not match expected base key")
+	}
+
+	//verify the rekeyThreshold was generated
+	if s.rekeyThreshold == 0 {
+		t.Errorf("rekeyThreshold not generated")
+	}
+
+	//verify keystates where created
+	if s.keyState == nil {
+		t.Errorf("keystates not generated")
+	}
+
+	//verify keys were not registered in the fingerprintMap
+	for keyNum := uint32(0); keyNum < s.keyState.numkeys; keyNum++ {
+		key := newKey(s, keyNum)
+		if _, ok := fps.toKey[key.Fingerprint()]; ok {
+			t.Errorf("key %v in fingerprint map", keyNum)
+		}
+	}
+}
+
+// Shows that newSession can result in all the fields being populated
+func TestNewSession(t *testing.T) {
+	// Make a test session to easily populate all the fields
+	sessionA, _ := makeTestSession()
+
+	// Make a new session with the variables we got from makeTestSession
+	sessionB := newSession(sessionA.relationship, sessionA.t, sessionA.myPrivKey,
+		sessionA.partnerPubKey, sessionA.baseKey, sessionA.GetID(), []byte(""),
+		sessionA.negotiationStatus, sessionA.e2eParams)
+
+	err := cmpSerializedFields(sessionA, sessionB)
+	if err != nil {
+		t.Error(err)
+	}
+	// For everything else, just make sure it's populated
+	if sessionB.keyState == nil {
+		t.Error("newSession should populate keyState")
+	}
+	if sessionB.relationship == nil {
+		t.Error("newSession should populate relationship")
+	}
+	if sessionB.rekeyThreshold == 0 {
+		t.Error("newSession should populate rekeyThreshold")
+	}
+}
+
+// Shows that loadSession can result in all the fields being populated
+func TestSession_Load(t *testing.T) {
+	// Make a test session to easily populate all the fields
+	sessionA, _ := makeTestSession()
+	err := sessionA.save()
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Load another, hopefully identical session from the storage
+	sessionB, err := loadSession(sessionA.relationship, sessionA.kv, []byte(""))
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = cmpSerializedFields(sessionA, sessionB)
+	if err != nil {
+		t.Error(err)
+	}
+	// Key state should also be loaded and equivalent to the other session
+	// during loadSession()
+	err = cmpKeyState(sessionA.keyState, sessionB.keyState)
+	if err != nil {
+		t.Error(err)
+	}
+	// For everything else, just make sure it's populated
+	if sessionB.relationship == nil {
+		t.Error("load should populate relationship")
+	}
+	if sessionB.rekeyThreshold == 0 {
+		t.Error("load should populate rekeyThreshold")
+	}
+}
+
+func cmpKeyState(a *stateVector, b *stateVector) error {
+	// ignore ctx, mux
+	if a.key != b.key {
+		return errors.New("keys differed")
+	}
+	if a.numAvailable != b.numAvailable {
+		return errors.New("numAvailable differed")
+	}
+	if a.firstAvailable != b.firstAvailable {
+		return errors.New("firstAvailable differed")
+	}
+	if a.numkeys != b.numkeys {
+		return errors.New("numkeys differed")
+	}
+	if len(a.vect) != len(b.vect) {
+		return errors.New("vect differed")
+	}
+	for i := range a.vect {
+		if a.vect[i] != b.vect[i] {
+			return errors.New("vect differed")
+		}
+	}
+	return nil
+}
+
+// Create a new session. Marshal and unmarshal it
+func TestSession_Serialization(t *testing.T) {
+	s, ctx := makeTestSession()
+	sSerialized, err := s.marshal()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	sDeserialized := &Session{
+		relationship: &relationship{
+			manager: &Manager{ctx: ctx},
+		},
+		kv: s.kv,
+	}
+	err = sDeserialized.unmarshal(sSerialized)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}
+
+// compare fields also represented in SessionDisk
+// fields not represented in SessionDisk shouldn't be expected to be populated by Unmarshal
+func cmpSerializedFields(a *Session, b *Session) error {
+	if a.negotiationStatus != b.negotiationStatus {
+		return errors.New("confirmed differed")
+	}
+	if a.t != b.t {
+		return errors.New("t differed")
+	}
+	if a.e2eParams.MaxKeys != b.e2eParams.MaxKeys {
+		return errors.New("maxKeys differed")
+	}
+	if a.e2eParams.MinKeys != b.e2eParams.MinKeys {
+		return errors.New("minKeys differed")
+	}
+	if a.e2eParams.NumRekeys != b.e2eParams.NumRekeys {
+		return errors.New("numRekeys differed")
+	}
+	if a.e2eParams.MinNumKeys != b.e2eParams.MinNumKeys {
+		return errors.New("minNumKeys differed")
+	}
+	if a.e2eParams.TTLScalar != b.e2eParams.TTLScalar {
+		return errors.New("ttlScalar differed")
+	}
+	if a.baseKey.Cmp(b.baseKey) != 0 {
+		return errors.New("baseKey differed")
+	}
+	if a.myPrivKey.Cmp(b.myPrivKey) != 0 {
+		return errors.New("myPrivKey differed")
+	}
+	if a.partnerPubKey.Cmp(b.partnerPubKey) != 0 {
+		return errors.New("partnerPubKey differed")
+	}
+	return nil
+}
+
+// PopKey should return a new key from this session
+func TestSession_PopKey(t *testing.T) {
+	s, _ := makeTestSession()
+	key, err := s.PopKey()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if key == nil {
+		t.Error("PopKey should have returned non-nil key")
+	}
+	if key.session != s {
+		t.Error("Key should record it belongs to this session")
+	}
+	// PopKey should return the first available key
+	if key.keyNum != 0 {
+		t.Error("First key popped should have keynum 0")
+	}
+}
+
+// delete should remove unused keys from this session
+func TestSession_Delete(t *testing.T) {
+	s, _ := makeTestSession()
+	err := s.save()
+	if err != nil {
+		t.Fatal(err)
+	}
+	s.Delete()
+
+	// Getting the keys that should have been stored should now result in an error
+	_, err = s.kv.Get(stateVectorKey, 0)
+	if err == nil {
+		t.Error("State vector was gettable")
+	}
+	_, err = s.kv.Get(sessionKey, 0)
+	if err == nil {
+		t.Error("Session was gettable")
+	}
+}
+
+// PopKey should return an error if it's time for this session to rekey
+// or if the key state vector is out of keys
+// Unfortunately, the key state vector being out of keys is something
+// that will also get caught by the other error first. So it's only practical
+// to test the one error.
+func TestSession_PopKey_Error(t *testing.T) {
+	s, _ := makeTestSession()
+	// Construct a specific state vector that will quickly run out of keys
+	var err error
+	s.keyState, err = newStateVector(s.kv, "", 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = s.PopKey()
+	if err == nil {
+		t.Fatal("PopKey should have returned an error")
+	}
+	t.Log(err)
+}
+
+// PopRekey should return the next key
+// There's no boundary, except for the number of keynums in the state vector
+func TestSession_PopReKey(t *testing.T) {
+	s, _ := makeTestSession()
+	key, err := s.PopReKey()
+	if err != nil {
+		t.Fatal("PopKey should have returned an error")
+	}
+	if key == nil {
+		t.Error("Key should be non-nil")
+	}
+	if key.session != s {
+		t.Error("Key should record it belongs to this session")
+	}
+	// PopReKey should return the first available key
+	if key.keyNum != 0 {
+		t.Error("First key popped should have keynum 0")
+	}
+}
+
+// PopRekey should not return the next key if there are no more keys available
+// in the state vector
+func TestSession_PopReKey_Err(t *testing.T) {
+	s, _ := makeTestSession()
+	// Construct a specific state vector that will quickly run out of keys
+	var err error
+	s.keyState, err = newStateVector(s.kv, "", 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = s.PopReKey()
+	if err == nil {
+		t.Fatal("PopReKey should have returned an error")
+	}
+}
+
+// Simple test that shows the base key can get got
+func TestSession_GetBaseKey(t *testing.T) {
+	s, _ := makeTestSession()
+	baseKey := s.GetBaseKey()
+	if baseKey.Cmp(s.baseKey) != 0 {
+		t.Errorf("expected %v, got %v", baseKey.Text(16), s.baseKey.Text(16))
+	}
+}
+
+// Smoke test for GetID
+func TestSession_GetID(t *testing.T) {
+	s, _ := makeTestSession()
+	id := s.GetID()
+	if len(id.Marshal()) == 0 {
+		t.Error("Zero length for session ID!")
+	}
+}
+
+// Smoke test for GetPartnerPubKey
+func TestSession_GetPartnerPubKey(t *testing.T) {
+	s, _ := makeTestSession()
+	partnerPubKey := s.GetPartnerPubKey()
+	if partnerPubKey.Cmp(s.partnerPubKey) != 0 {
+		t.Errorf("expected %v, got %v", partnerPubKey.Text(16), s.partnerPubKey.Text(16))
+	}
+}
+
+// Smoke test for GetMyPrivKey
+func TestSession_GetMyPrivKey(t *testing.T) {
+	s, _ := makeTestSession()
+	myPrivKey := s.GetMyPrivKey()
+	if myPrivKey.Cmp(s.myPrivKey) != 0 {
+		t.Errorf("expected %v, got %v", myPrivKey.Text(16), s.myPrivKey.Text(16))
+	}
+}
+
+// Shows that IsConfirmed returns whether the session is confirmed
+func TestSession_IsConfirmed(t *testing.T) {
+	s, _ := makeTestSession()
+	s.negotiationStatus = Unconfirmed
+	if s.IsConfirmed() {
+		t.Error("s was confirmed when it shouldn't have been")
+	}
+	s.negotiationStatus = Confirmed
+	if !s.IsConfirmed() {
+		t.Error("s wasn't confirmed when it should have been")
+	}
+}
+
+// Shows that Status can result in all possible statuses
+func TestSession_Status(t *testing.T) {
+	s, _ := makeTestSession()
+	var err error
+	s.keyState, err = newStateVector(s.kv, "", 500)
+	if err != nil {
+		t.Fatal(err)
+	}
+	s.keyState.numAvailable = 0
+	if s.Status() != RekeyEmpty {
+		t.Error("status should have been rekey empty with no keys left")
+	}
+	s.keyState.numAvailable = 1
+	if s.Status() != Empty {
+		t.Error("Status should have been empty")
+	}
+	// Passing the rekeyThreshold should result in a rekey being needed
+	s.keyState.numAvailable = s.keyState.numkeys - s.rekeyThreshold
+	if s.Status() != RekeyNeeded {
+		t.Error("Just past the rekeyThreshold, rekey should be needed")
+	}
+	s.keyState.numAvailable = s.keyState.numkeys
+	s.rekeyThreshold = 450
+	if s.Status() != Active {
+		t.Errorf("If all keys available, session should be active, recieved: %s", s.Status())
+	}
+}
+
+// Tests that state transitions as documented don't cause panics
+// Tests that the session saves or doesn't save when appropriate
+func TestSession_SetNegotiationStatus(t *testing.T) {
+	s, _ := makeTestSession()
+	//	Normal paths: SetNegotiationStatus should not fail
+	// Use timestamps to determine whether a save has occurred
+	s.negotiationStatus = Sending
+	now := time.Now()
+	time.Sleep(time.Millisecond)
+	s.SetNegotiationStatus(Sent)
+	if s.negotiationStatus != Sent {
+		t.Error("SetNegotiationStatus didn't set the negotiation status")
+	}
+	object, err := s.kv.Get(sessionKey, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !object.Timestamp.After(now) {
+		t.Errorf("save didn't occur after switching Sending to Sent")
+	}
+
+	now = time.Now()
+	time.Sleep(time.Millisecond)
+	s.SetNegotiationStatus(Confirmed)
+	if s.negotiationStatus != Confirmed {
+		t.Error("SetNegotiationStatus didn't set the negotiation status")
+	}
+	object, err = s.kv.Get(sessionKey, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !object.Timestamp.After(now) {
+		t.Errorf("save didn't occur after switching Sent to Confirmed")
+	}
+
+	now = time.Now()
+	time.Sleep(time.Millisecond)
+	s.negotiationStatus = NewSessionTriggered
+	s.SetNegotiationStatus(NewSessionCreated)
+	if s.negotiationStatus != NewSessionCreated {
+		t.Error("SetNegotiationStatus didn't set the negotiation status")
+	}
+	object, err = s.kv.Get(sessionKey, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !object.Timestamp.After(now) {
+		t.Error("save didn't occur after switching Sent to Confirmed")
+	}
+
+	// Reverting paths: SetNegotiationStatus should not fail, and a save should not take place
+	time.Sleep(time.Millisecond)
+	now = time.Now()
+	time.Sleep(time.Millisecond)
+	s.negotiationStatus = Sending
+	s.SetNegotiationStatus(Unconfirmed)
+	if s.negotiationStatus != Unconfirmed {
+		t.Error("SetNegotiationStatus didn't set the negotiation status")
+	}
+	object, err = s.kv.Get(sessionKey, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !object.Timestamp.Before(now) {
+		t.Error("save occurred after switching Sent to Confirmed")
+	}
+
+	s.negotiationStatus = NewSessionTriggered
+	s.SetNegotiationStatus(Confirmed)
+	if s.negotiationStatus != Confirmed {
+		t.Error("SetNegotiationStatus didn't set the negotiation status")
+	}
+	object, err = s.kv.Get(sessionKey, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !object.Timestamp.Before(now) {
+		t.Error("save occurred after switching Sent to Confirmed")
+	}
+}
+
+// Tests that TriggerNegotiation makes only valid state transitions
+func TestSession_TriggerNegotiation(t *testing.T) {
+	s, _ := makeTestSession()
+	// Set up num keys used to be > rekeyThreshold: should partnerSource negotiation
+	s.keyState.numAvailable = 50
+	s.keyState.numkeys = 100
+	s.rekeyThreshold = 49
+	s.negotiationStatus = Confirmed
+
+	if !s.triggerNegotiation() {
+		t.Error("partnerSource negotiation unexpectedly failed")
+	}
+	if s.negotiationStatus != NewSessionTriggered {
+		t.Errorf("negotiationStatus: got %v, expected %v", s.negotiationStatus, NewSessionTriggered)
+	}
+
+	// Set up num keys used to be = rekeyThreshold: should partnerSource negotiation
+	s.rekeyThreshold = 50
+	s.negotiationStatus = Confirmed
+
+	if !s.triggerNegotiation() {
+		t.Error("partnerSource negotiation unexpectedly failed")
+	}
+	if s.negotiationStatus != NewSessionTriggered {
+		t.Errorf("negotiationStatus: got %v, expected %v", s.negotiationStatus, NewSessionTriggered)
+	}
+
+	// Set up num keys used to be < rekeyThreshold: shouldn't partnerSource negotiation
+	s.rekeyThreshold = 51
+	s.negotiationStatus = Confirmed
+
+	if s.triggerNegotiation() {
+		t.Error("trigger negotiation unexpectedly failed")
+	}
+	if s.negotiationStatus != Confirmed {
+		t.Errorf("negotiationStatus: got %v, expected %v", s.negotiationStatus, NewSessionTriggered)
+	}
+
+	// Test other case: partnerSource sending	confirmation message on unconfirmed session
+	s.negotiationStatus = Unconfirmed
+	if !s.triggerNegotiation() {
+		t.Error("partnerSource negotiation unexpectedly failed")
+	}
+	if s.negotiationStatus != Sending {
+		t.Errorf("negotiationStatus: got %v, expected %v", s.negotiationStatus, NewSessionTriggered)
+	}
+}
+
+// Shows that String doesn't cause errors or panics
+// Also can be used to examine or change output of String()
+func TestSession_String(t *testing.T) {
+	s, _ := makeTestSession()
+	t.Log(s.String())
+	s.relationship.manager.partner = id.NewIdFromUInt(80, id.User, t)
+	t.Log(s.String())
+}
+
+// Shows that GetSource gets the partnerSource we set
+func TestSession_GetTrigger(t *testing.T) {
+	s, _ := makeTestSession()
+	thisTrigger := s.GetID()
+	s.partnerSource = thisTrigger
+	if !reflect.DeepEqual(s.GetSource(), thisTrigger) {
+		t.Error("Trigger different from expected")
+	}
+}
+
+// Make a default test session with some things populated
+func makeTestSession() (*Session, *context) {
+	grp := getGroup()
+	rng := csprng.NewSystemRNG()
+	partnerPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+	partnerPubKey := dh.GeneratePublicKey(partnerPrivKey, grp)
+	myPrivKey := dh.GeneratePrivateKey(dh.DefaultPrivateKeyLength, grp, rng)
+	baseKey := dh.GenerateSessionKey(myPrivKey, partnerPubKey, grp)
+
+	//create context objects for general use
+	fps := newFingerprints()
+	ctx := &context{
+		fa:   &fps,
+		grp:  grp,
+		myID: &id.ID{},
+	}
+
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	s := &Session{
+		baseKey:       baseKey,
+		myPrivKey:     myPrivKey,
+		partnerPubKey: partnerPubKey,
+		e2eParams:     params.GetDefaultE2ESessionParams(),
+		relationship: &relationship{
+			manager: &Manager{
+				ctx: ctx,
+				kv:  kv,
+			},
+			kv: kv,
+		},
+		kv:                kv,
+		t:                 Receive,
+		negotiationStatus: Confirmed,
+		rekeyThreshold:    5,
+	}
+	var err error
+	s.keyState, err = newStateVector(s.kv,
+		"", 1024)
+	if err != nil {
+		panic(err)
+	}
+	return s, ctx
+}
diff --git a/storage/e2e/stateVector.go b/storage/e2e/stateVector.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a2e6271c0f42f65060f4416bfd7603f2a3d4d5d
--- /dev/null
+++ b/storage/e2e/stateVector.go
@@ -0,0 +1,256 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"sync"
+	"time"
+)
+
+const currentStateVectorVersion = 0
+const stateVectorKey = "stateVector"
+
+type stateVector struct {
+	kv  *versioned.KV
+	key string
+
+	// Bitfield for key states
+	// If a key is clean, its bit will be 0
+	// Otherwise, it's dirty/used/not available, and its bit will be 1
+	vect []uint64
+
+	firstAvailable uint32
+	numkeys        uint32
+	numAvailable   uint32
+
+	mux sync.RWMutex
+}
+
+// Fields must be exported for json marshal to serialize them
+type stateVectorDisk struct {
+	Vect           []uint64
+	FirstAvailable uint32
+	NumAvailable   uint32
+	Numkeys        uint32
+}
+
+func newStateVector(kv *versioned.KV, key string, numkeys uint32) (*stateVector, error) {
+	numBlocks := (numkeys + 63) / 64
+
+	sv := &stateVector{
+		kv:             kv,
+		vect:           make([]uint64, numBlocks),
+		key:            stateVectorKey + key,
+		firstAvailable: 0,
+		numAvailable:   numkeys,
+		numkeys:        numkeys,
+	}
+
+	return sv, sv.save()
+}
+
+func loadStateVector(kv *versioned.KV, key string) (*stateVector, error) {
+	sv := &stateVector{
+		kv:  kv,
+		key: stateVectorKey + key,
+	}
+
+	obj, err := kv.Get(sv.key, currentStateVectorVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	err = sv.unmarshal(obj.Data)
+	if err != nil {
+		return nil, err
+	}
+
+	return sv, nil
+}
+
+func (sv *stateVector) save() error {
+	now := time.Now()
+
+	data, err := sv.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentStateVectorVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return sv.kv.Set(sv.key, currentStateVectorVersion, &obj)
+}
+
+func (sv *stateVector) Use(keynum uint32) {
+	sv.mux.Lock()
+	defer sv.mux.Unlock()
+
+	block := keynum / 64
+	pos := keynum % 64
+
+	sv.vect[block] |= 1 << pos
+
+	if keynum == sv.firstAvailable {
+		sv.nextAvailable()
+	}
+
+	sv.numAvailable--
+
+	if err := sv.save(); err != nil {
+		jww.FATAL.Printf("Failed to save %s on Use(): %s", sv, err)
+	}
+}
+
+func (sv *stateVector) GetNumAvailable() uint32 {
+	sv.mux.RLock()
+	defer sv.mux.RUnlock()
+	return sv.numAvailable
+}
+
+func (sv *stateVector) GetNumUsed() uint32 {
+	sv.mux.RLock()
+	defer sv.mux.RUnlock()
+	return sv.numkeys - sv.numAvailable
+}
+
+func (sv *stateVector) Used(keynum uint32) bool {
+	sv.mux.RLock()
+	defer sv.mux.RUnlock()
+
+	return sv.used(keynum)
+}
+
+func (sv *stateVector) used(keynum uint32) bool {
+	block := keynum / 64
+	pos := keynum % 64
+
+	return (sv.vect[block]>>pos)&1 == 1
+}
+
+func (sv *stateVector) Next() (uint32, error) {
+	sv.mux.Lock()
+	defer sv.mux.Unlock()
+
+	if sv.firstAvailable >= sv.numkeys {
+		return sv.numkeys, errors.New("No keys remaining")
+	}
+
+	next := sv.firstAvailable
+
+	sv.nextAvailable()
+	sv.numAvailable--
+
+	if err := sv.save(); err != nil {
+		jww.FATAL.Printf("Failed to save %s on Next(): %s", sv, err)
+	}
+
+	return next, nil
+
+}
+
+func (sv *stateVector) GetNumKeys() uint32 {
+	return sv.numkeys
+}
+
+//returns a list of unused keys
+func (sv *stateVector) GetUnusedKeyNums() []uint32 {
+	sv.mux.RLock()
+	defer sv.mux.RUnlock()
+
+	keyNums := make([]uint32, 0, sv.numAvailable)
+
+	for keyNum := sv.firstAvailable; keyNum < sv.numkeys; keyNum++ {
+		if !sv.used(keyNum) {
+			keyNums = append(keyNums, keyNum)
+		}
+	}
+
+	return keyNums
+}
+
+//returns a list of used keys
+func (sv *stateVector) GetUsedKeyNums() []uint32 {
+	sv.mux.RLock()
+	defer sv.mux.RUnlock()
+
+	keyNums := make([]uint32, 0, sv.numkeys-sv.numAvailable)
+
+	for keyNum := sv.firstAvailable; keyNum < sv.numkeys; keyNum++ {
+		if sv.used(keyNum) {
+			keyNums = append(keyNums, keyNum)
+		}
+	}
+
+	return keyNums
+}
+
+//Adheres to the stringer interface
+func (sv *stateVector) String() string {
+	return fmt.Sprintf("stateVector: %s", sv.key)
+}
+
+//Deletes the state vector from storage
+func (sv *stateVector) Delete() error {
+	return sv.kv.Delete(sv.key, currentStateVectorVersion)
+}
+
+// finds the next used state and sets that as firstAvailable. This does not
+// execute a store and a store must be executed after.
+func (sv *stateVector) nextAvailable() {
+
+	block := (sv.firstAvailable + 1) / 64
+	pos := (sv.firstAvailable + 1) % 64
+
+	for ; block < uint32(len(sv.vect)) && (sv.vect[block]>>pos)&1 == 1; pos++ {
+		if pos == 63 {
+			pos = 0
+			block++
+		}
+	}
+
+	sv.firstAvailable = block*64 + pos
+}
+
+//ekv functions
+func (sv *stateVector) marshal() ([]byte, error) {
+	svd := stateVectorDisk{}
+
+	svd.FirstAvailable = sv.firstAvailable
+	svd.Numkeys = sv.numkeys
+	svd.NumAvailable = sv.numAvailable
+	svd.Vect = sv.vect
+
+	return json.Marshal(&svd)
+}
+
+func (sv *stateVector) unmarshal(b []byte) error {
+
+	svd := stateVectorDisk{}
+
+	err := json.Unmarshal(b, &svd)
+
+	if err != nil {
+		return err
+	}
+
+	sv.firstAvailable = svd.FirstAvailable
+	sv.numkeys = svd.Numkeys
+	sv.numAvailable = svd.NumAvailable
+	sv.vect = svd.Vect
+
+	return nil
+}
diff --git a/storage/e2e/stateVector_test.go b/storage/e2e/stateVector_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..99fd5cf606088b3d55ddda3f3e6045a04effe9af
--- /dev/null
+++ b/storage/e2e/stateVector_test.go
@@ -0,0 +1,251 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"fmt"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"math/bits"
+	"reflect"
+	"testing"
+)
+
+// GetNumAvailable gets the number of slots left in the state vector
+func TestStateVector_GetNumAvailable(t *testing.T) {
+	const numAvailable = 23
+	sv := &stateVector{
+		numAvailable: numAvailable,
+	}
+	// At the start, NumAvailable should be the same as numKeys
+	// as none of the keys have been used
+	if sv.GetNumAvailable() != numAvailable {
+		t.Errorf("expected %v available, actually %v available", numAvailable, sv.GetNumAvailable())
+	}
+}
+
+// Shows that GetNumUsed returns the number of slots used in the state vector
+func TestStateVector_GetNumUsed(t *testing.T) {
+	const numAvailable = 23
+	const numKeys = 50
+	sv := &stateVector{
+		numkeys:      numKeys,
+		numAvailable: numAvailable,
+	}
+
+	if sv.GetNumUsed() != numKeys-numAvailable {
+		t.Errorf("Expected %v used, got %v", numKeys-numAvailable, sv.GetNumUsed())
+	}
+}
+
+func TestStateVector_GetNumKeys(t *testing.T) {
+	const numKeys = 32
+	sv, err := newStateVector(versioned.NewKV(make(ekv.Memstore)), "key", numKeys)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// GetNumKeys should always be the same as numKeys
+	if sv.GetNumKeys() != numKeys {
+		t.Errorf("expected %v available, actually %v available", numKeys, sv.GetNumAvailable())
+	}
+}
+
+// Shows that Next mutates vector state as expected
+// Shows that Next can find key indexes all throughout the bitfield
+func TestStateVector_Next(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 all bits to dirty to start
+	for i := range sv.vect {
+		sv.vect[i] = 0xffffffffffffffff
+	}
+
+	// Set a few clean bits randomly
+	const numBitsSet = 10
+	for i := 0; i < numBitsSet; i++ {
+		keyNum := expectedFirstAvail[i]
+		// Set a bit clean in the state vector
+		vectIndex := keyNum / 64
+		bitIndex := keyNum % 64
+		sv.vect[vectIndex] &= ^bits.RotateLeft64(uint64(1), int(bitIndex))
+	}
+
+	sv.numAvailable = numBitsSet
+	sv.nextAvailable()
+
+	// Calling Next ten times should give all of the keyNums we set
+	// It should change firstAvailable, but doesn't mutate the bit field itself
+	//  (that should be done with Use)
+	for numCalls := 0; numCalls < numBitsSet; numCalls++ {
+		keyNum, err := sv.Next()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if keyNum != expectedFirstAvail[numCalls] {
+			t.Errorf("keynum %v didn't match expected %v at index %v", keyNum, expectedFirstAvail[numCalls], numCalls)
+		}
+	}
+
+	// One more call should cause an error
+	_, err = sv.Next()
+	if err == nil {
+		t.Error("Calling Next() after all keys have been found should result in error, as firstAvailable is more than numKeys")
+	}
+	// firstAvailable should now be beyond the end of the bitfield
+	if sv.firstAvailable < numKeys {
+		t.Error("Last Next() call should have set firstAvailable beyond numKeys")
+	}
+}
+
+// Shows that Use() mutates the state vector itself
+func TestStateVector_Use(t *testing.T) {
+	// These keyNums will be set to dirty with Use
+	keyNums := []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)
+	}
+
+	// Expected vector states as bits are set
+	var expectedVect [][]uint64
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x1000000000, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x81000000000, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0})
+	expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0x800000000})
+
+	for numCalls := range keyNums {
+		// These calls to Use won't set nextAvailable, because the first keyNum set
+		sv.Use(keyNums[numCalls])
+		if !reflect.DeepEqual(expectedVect[numCalls], sv.vect) {
+			t.Errorf("sv.vect differed from expected at index %v", numCalls)
+			fmt.Println(sv.vect)
+		}
+	}
+}
+
+func TestStateVector_Used(t *testing.T) {
+	// These keyNums should be used
+	keyNums := []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)
+	}
+	sv.vect = []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0x800000000}
+
+	for i := uint32(0); i < numKeys; i++ {
+		// if i is in keyNums, Used should be true
+		// otherwise, it should be false
+		found := false
+		for j := range keyNums {
+			if i == keyNums[j] {
+				found = true
+				break
+			}
+		}
+		if sv.Used(i) != found {
+			t.Errorf("at keynum %v Used should have been %v but was %v", i, found, sv.Used(i))
+		}
+	}
+}
+
+// Shows that the GetUsedKeyNums method returns the correct keynums
+func TestStateVector_GetUsedKeyNums(t *testing.T) {
+	// These keyNums should be used
+	keyNums := []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)
+	}
+	sv.vect = []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0x800000000}
+	sv.numAvailable = uint32(numKeys - len(keyNums))
+
+	usedKeyNums := sv.GetUsedKeyNums()
+	for i := range keyNums {
+		if usedKeyNums[i] != keyNums[i] {
+			t.Errorf("used keynums at %v: expected %v, got %v", i, keyNums[i], usedKeyNums[i])
+		}
+	}
+}
+
+// Shows that GetUnusedKeyNums gets all clean keynums
+func TestStateVector_GetUnusedKeyNums(t *testing.T) {
+	// These keyNums should not be used
+	keyNums := []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)
+	}
+	sv.vect = []uint64{0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffdf7ff, 0xffffffffffffffff, 0xffffefffffffffff, 0xfffffeffffffffff, 0xffffffefffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfdffffffffffffff, 0xffffffffffffffff, 0xdffff7efffffffff, 0xffffffffffffffff, 0xfffffff7ffffffff}
+	sv.numAvailable = uint32(len(keyNums))
+	sv.firstAvailable = keyNums[0]
+
+	unusedKeyNums := sv.GetUnusedKeyNums()
+	for i := range keyNums {
+		if unusedKeyNums[i] != keyNums[i] {
+			t.Errorf("unused keynums at %v: expected %v, got %v", i, keyNums[i], unusedKeyNums[i])
+		}
+	}
+	if len(keyNums) != len(unusedKeyNums) {
+		t.Error("array lengths differed, so arrays must be different")
+	}
+}
+
+// Serializing and deserializing should result in the same state vector
+func TestLoadStateVector(t *testing.T) {
+	keyNums := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995}
+	const numKeys = 1000
+
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	sv, err := newStateVector(kv, "key", numKeys)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sv.vect = []uint64{0xffffffffffffffff, 0xffffffffffffffff,
+		0xfffffffffffdf7ff, 0xffffffffffffffff, 0xffffefffffffffff,
+		0xfffffeffffffffff, 0xffffffefffffffff, 0xffffffffffffffff,
+		0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff,
+		0xfdffffffffffffff, 0xffffffffffffffff, 0xdffff7efffffffff,
+		0xffffffffffffffff, 0xfffffff7ffffffff}
+	sv.numAvailable = uint32(len(keyNums))
+	sv.firstAvailable = keyNums[0]
+
+	err = sv.save()
+	if err != nil {
+		t.Fatal(err)
+	}
+	sv2, err := loadStateVector(kv, "key")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(sv.vect, sv2.vect) {
+		t.Error("state vectors different after deserialization")
+	}
+}
diff --git a/storage/e2e/status.go b/storage/e2e/status.go
new file mode 100644
index 0000000000000000000000000000000000000000..63633724d0ad1b3faa2488198fdd4280041b028c
--- /dev/null
+++ b/storage/e2e/status.go
@@ -0,0 +1,38 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import "fmt"
+
+type Status uint8
+
+const (
+	// Active sessions have keys remaining that can be used for messages
+	Active Status = iota
+	// RekeyNeeded sessions have keys remaining for messages, but should be rekeyed immediately
+	RekeyNeeded
+	// Empty sessions can't be used for more messages, but can be used for rekeys
+	Empty
+	// RekeyEmpty sessions are totally empty and no longer have enough keys left for a rekey, much less messages
+	RekeyEmpty
+)
+
+func (a Status) String() string {
+	switch a {
+	case Active:
+		return "Active"
+	case RekeyNeeded:
+		return "Rekey Needed"
+	case Empty:
+		return "Empty"
+	case RekeyEmpty:
+		return "Rekey Empty"
+	default:
+		return fmt.Sprintf("Unknown: %v", int(a))
+	}
+}
diff --git a/storage/e2e/status_test.go b/storage/e2e/status_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a81894bae53b808ddc632a7881d5945b5b0c2d24
--- /dev/null
+++ b/storage/e2e/status_test.go
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+// Testing file for the status.go functions
+
+import "testing"
+
+// Test that Status_String returns the right strings for a status
+func Test_Status_String(t *testing.T) {
+	if Status(Active).String() != "Active" {
+		t.Errorf("Testing Active returned mismatch.\r\tGot: %s\r\tExpected: %s", Status(Active).String(), "Active")
+	}
+	if Status(RekeyNeeded).String() != "Rekey Needed" {
+		t.Errorf("Testing RekeyNeeded returned mismatch.\r\tGot: %s\r\tExpected: %s", Status(RekeyNeeded).String(), "Rekey Needed")
+	}
+	if Status(Empty).String() != "Empty" {
+		t.Errorf("Testing Empty returned mismatch.\r\tGot: %s\r\tExpected: %s", Status(Empty).String(), "Empty")
+	}
+	if Status(RekeyEmpty).String() != "Rekey Empty" {
+		t.Errorf("Testing RekeyEmpty returned mismatch.\r\tGot: %s\r\tExpected: %s", Status(RekeyEmpty).String(), "Rekey Empty")
+	}
+}
diff --git a/storage/e2e/store.go b/storage/e2e/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..ae06cb56b5112fa39c2812de23d69b00966e8f18
--- /dev/null
+++ b/storage/e2e/store.go
@@ -0,0 +1,349 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+const (
+	currentStoreVersion = 0
+	packagePrefix       = "e2eSession"
+	storeKey            = "Store"
+	pubKeyKey           = "DhPubKey"
+	privKeyKey          = "DhPrivKey"
+	grpKey              = "Group"
+)
+
+var NoPartnerErrorStr = "No relationship with partner found"
+
+type Store struct {
+	managers map[id.ID]*Manager
+	mux      sync.RWMutex
+
+	dhPrivateKey *cyclic.Int
+	dhPublicKey  *cyclic.Int
+	grp          *cyclic.Group
+
+	kv *versioned.KV
+
+	*fingerprints
+
+	*context
+
+	e2eParams params.E2ESessionParams
+}
+
+func NewStore(grp *cyclic.Group, kv *versioned.KV, privKey *cyclic.Int,
+	myID *id.ID, rng *fastRNG.StreamGenerator) (*Store, error) {
+	// Generate public key
+	pubKey := diffieHellman.GeneratePublicKey(privKey, grp)
+
+	// Modify the prefix of the KV
+	kv = kv.Prefix(packagePrefix)
+
+	// Create new fingerprint map
+	fingerprints := newFingerprints()
+
+	s := &Store{
+		managers: make(map[id.ID]*Manager),
+
+		dhPrivateKey: privKey,
+		dhPublicKey:  pubKey,
+		grp:          grp,
+
+		fingerprints: &fingerprints,
+
+		kv: kv,
+
+		context: &context{
+			fa:   &fingerprints,
+			grp:  grp,
+			rng:  rng,
+			myID: myID,
+		},
+
+		e2eParams: params.GetDefaultE2ESessionParams(),
+	}
+
+	err := utility.StoreCyclicKey(kv, pubKey, pubKeyKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to store e2e DH public key")
+	}
+
+	err = utility.StoreCyclicKey(kv, privKey, privKeyKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to store e2e DH private key")
+	}
+
+	err = utility.StoreGroup(kv, grp, grpKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to store e2e group")
+	}
+
+	return s, s.save()
+}
+
+func LoadStore(kv *versioned.KV, myID *id.ID, rng *fastRNG.StreamGenerator) (*Store, error) {
+	fingerprints := newFingerprints()
+	kv = kv.Prefix(packagePrefix)
+
+	grp, err := utility.LoadGroup(kv, grpKey)
+	if err != nil {
+		return nil, err
+	}
+
+	s := &Store{
+		managers: make(map[id.ID]*Manager),
+
+		fingerprints: &fingerprints,
+
+		kv:  kv,
+		grp: grp,
+
+		context: &context{
+			fa:   &fingerprints,
+			rng:  rng,
+			myID: myID,
+			grp:  grp,
+		},
+
+		e2eParams: params.GetDefaultE2ESessionParams(),
+	}
+
+	obj, err := kv.Get(storeKey, currentStoreVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	err = s.unmarshal(obj.Data)
+	if err != nil {
+		return nil, err
+	}
+
+	s.context.grp = s.grp
+
+	return s, nil
+}
+
+func (s *Store) save() error {
+	now := time.Now()
+
+	data, err := s.marshal()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentStoreVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return s.kv.Set(storeKey, currentStoreVersion, &obj)
+}
+
+func (s *Store) AddPartner(partnerID *id.ID, partnerPubKey, myPrivKey *cyclic.Int,
+	sendParams, receiveParams params.E2ESessionParams) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	jww.INFO.Printf("Adding Partner %s:\n\tMy Private Key: %s"+
+		"\n\tPartner Public Key: %s",
+		partnerID,
+		myPrivKey.TextVerbose(16, 0),
+		partnerPubKey.TextVerbose(16, 0))
+
+	if _, ok := s.managers[*partnerID]; ok {
+		return errors.New("Cannot overwrite existing partner")
+	}
+
+	m := newManager(s.context, s.kv, partnerID, myPrivKey, partnerPubKey,
+		sendParams, receiveParams)
+
+	s.managers[*partnerID] = m
+	if err := s.save(); err != nil {
+		jww.FATAL.Printf("Failed to add Parter %s: Save of store failed: %s",
+			partnerID, err)
+	}
+
+	return nil
+}
+
+func (s *Store) GetPartner(partnerID *id.ID) (*Manager, error) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+
+	m, ok := s.managers[*partnerID]
+
+	if !ok {
+		return nil, errors.New(NoPartnerErrorStr)
+	}
+
+	return m, nil
+}
+
+// PopKey pops a key for use based upon its fingerprint.
+func (s *Store) PopKey(f format.Fingerprint) (*Key, bool) {
+	return s.fingerprints.Pop(f)
+}
+
+// CheckKey checks that a key exists for the key fingerprint.
+func (s *Store) CheckKey(f format.Fingerprint) bool {
+	return s.fingerprints.Check(f)
+}
+
+// GetDHPrivateKey returns the diffie hellman private key.
+func (s *Store) GetDHPrivateKey() *cyclic.Int {
+	return s.dhPrivateKey
+}
+
+// GetDHPublicKey returns the diffie hellman public key.
+func (s *Store) GetDHPublicKey() *cyclic.Int {
+	return s.dhPublicKey
+}
+
+// GetGroup returns the cyclic group used for cMix.
+func (s *Store) GetGroup() *cyclic.Group {
+	return s.grp
+}
+
+// ekv functions
+
+func (s *Store) marshal() ([]byte, error) {
+	contacts := make([]id.ID, len(s.managers))
+
+	index := 0
+	for partnerID := range s.managers {
+		contacts[index] = partnerID
+		index++
+	}
+
+	return json.Marshal(&contacts)
+}
+
+func (s *Store) unmarshal(b []byte) error {
+
+	var contacts []id.ID
+
+	err := json.Unmarshal(b, &contacts)
+
+	if err != nil {
+		return err
+	}
+
+	for _, partnerID := range contacts {
+		// Load the relationship. The relationship handles adding the fingerprints via the
+		// context object
+		manager, err := loadManager(s.context, s.kv, &partnerID)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to load relationship for partner %s: %s",
+				&partnerID, err.Error())
+		}
+
+		s.managers[partnerID] = manager
+	}
+
+	s.dhPrivateKey, err = utility.LoadCyclicKey(s.kv, privKeyKey)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to load e2e DH private key")
+	}
+
+	s.dhPublicKey, err = utility.LoadCyclicKey(s.kv, pubKeyKey)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to load e2e DH public key")
+	}
+
+	return nil
+}
+
+// GetE2ESessionParams returns a copy of the session params object
+func (s *Store) GetE2ESessionParams() params.E2ESessionParams {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	jww.DEBUG.Printf("Using Session Params: %s", s.e2eParams)
+	return s.e2eParams
+}
+
+// SetE2ESessionParams overwrites the current session params
+func (s *Store) SetE2ESessionParams(newParams params.E2ESessionParams) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	jww.DEBUG.Printf("Setting Session Params: %s", newParams)
+	s.e2eParams = newParams
+}
+
+type fingerprints struct {
+	toKey map[format.Fingerprint]*Key
+	mux   sync.RWMutex
+}
+
+// newFingerprints creates a new fingerprints with an empty map.
+func newFingerprints() fingerprints {
+	return fingerprints{
+		toKey: make(map[format.Fingerprint]*Key),
+	}
+}
+
+// fingerprints adheres to the fingerprintAccess interface.
+
+func (f *fingerprints) add(keys []*Key) {
+	f.mux.Lock()
+	defer f.mux.Unlock()
+
+	for _, k := range keys {
+		f.toKey[k.Fingerprint()] = k
+	}
+}
+
+func (f *fingerprints) remove(keys []*Key) {
+	f.mux.Lock()
+	defer f.mux.Unlock()
+
+	for _, k := range keys {
+		delete(f.toKey, k.Fingerprint())
+	}
+}
+
+func (f *fingerprints) Check(fingerprint format.Fingerprint) bool {
+	f.mux.RLock()
+	defer f.mux.RUnlock()
+
+	_, ok := f.toKey[fingerprint]
+	return ok
+}
+
+func (f *fingerprints) Pop(fingerprint format.Fingerprint) (*Key, bool) {
+	f.mux.Lock()
+	defer f.mux.Unlock()
+	key, ok := f.toKey[fingerprint]
+
+	if !ok {
+		return nil, false
+	}
+
+	delete(f.toKey, fingerprint)
+
+	key.denoteUse()
+
+	key.fp = &fingerprint
+
+	return key, true
+}
diff --git a/storage/e2e/store_test.go b/storage/e2e/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..944b2517df666e536db085d6ce3f0a001316aa2d
--- /dev/null
+++ b/storage/e2e/store_test.go
@@ -0,0 +1,297 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package e2e
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// Tests happy path of NewStore.
+func TestNewStore(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
+	privKey := grp.NewInt(57)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	fingerprints := newFingerprints()
+	rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	e2eP := params.GetDefaultE2ESessionParams()
+	expectedStore := &Store{
+		managers:     make(map[id.ID]*Manager),
+		dhPrivateKey: privKey,
+		dhPublicKey:  diffieHellman.GeneratePublicKey(privKey, grp),
+		grp:          grp,
+		kv:           kv.Prefix(packagePrefix),
+		fingerprints: &fingerprints,
+		context: &context{
+			fa:   &fingerprints,
+			grp:  grp,
+			rng:  rng,
+			myID: &id.ID{},
+		},
+		e2eParams: e2eP,
+	}
+	expectedData, err := expectedStore.marshal()
+	if err != nil {
+		t.Fatalf("marshal() produced an error: %v", err)
+	}
+
+	store, err := NewStore(grp, kv, privKey, &id.ID{}, rng)
+	if err != nil {
+		t.Errorf("NewStore() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedStore, store) {
+		t.Errorf("NewStore() returned incorrect Store."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedStore, store)
+	}
+
+	key, err := expectedStore.kv.Get(storeKey, 0)
+	if err != nil {
+		t.Errorf("Get() encoutnered an error when getting Store from KV: %v", err)
+	}
+
+	if !bytes.Equal(expectedData, key.Data) {
+		t.Errorf("NewStore() returned incorrect Store."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedData, key.Data)
+	}
+}
+
+// Tests happy path of LoadStore.
+func TestLoadStore(t *testing.T) {
+	expectedStore, kv, rng := makeTestStore()
+
+	store, err := LoadStore(kv, &id.ID{}, rng)
+	if err != nil {
+		t.Errorf("LoadStore() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedStore, store) {
+		t.Errorf("LoadStore() returned incorrect Store."+
+			"\n\texpected: %#v\n\treceived: %#v", expectedStore, store)
+	}
+}
+
+// Tests happy path of Store.AddPartner.
+func TestStore_AddPartner(t *testing.T) {
+	s, _, _ := makeTestStore()
+	partnerID := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	pubKey := diffieHellman.GeneratePublicKey(s.dhPrivateKey, s.grp)
+	p := params.GetDefaultE2ESessionParams()
+	expectedManager := newManager(s.context, s.kv, partnerID, s.dhPrivateKey,
+		pubKey, p, p)
+
+	s.AddPartner(partnerID, pubKey, s.dhPrivateKey, p, p)
+
+	m, exists := s.managers[*partnerID]
+	if !exists {
+		t.Errorf("Manager does not exist in map.\n\tmap: %+v", s.managers)
+	}
+
+	if !reflect.DeepEqual(expectedManager, m) {
+		t.Errorf("Added Manager not expected.\n\texpected: %v\n\treceived: %v",
+			expectedManager, m)
+	}
+}
+
+// Tests happy path of Store.GetPartner.
+func TestStore_GetPartner(t *testing.T) {
+	s, _, _ := makeTestStore()
+	partnerID := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	pubKey := diffieHellman.GeneratePublicKey(s.dhPrivateKey, s.grp)
+	p := params.GetDefaultE2ESessionParams()
+	expectedManager := newManager(s.context, s.kv, partnerID, s.dhPrivateKey,
+		pubKey, p, p)
+	s.AddPartner(partnerID, pubKey, s.dhPrivateKey, p, p)
+
+	m, err := s.GetPartner(partnerID)
+	if err != nil {
+		t.Errorf("GetPartner() produced an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedManager, m) {
+		t.Errorf("GetPartner() returned wrong Manager."+
+			"\n\texpected: %v\n\treceived: %v", expectedManager, m)
+	}
+}
+
+// Tests that Store.GetPartner returns an error for non existent partnerID.
+func TestStore_GetPartner_Error(t *testing.T) {
+	s, _, _ := makeTestStore()
+	partnerID := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+
+	m, err := s.GetPartner(partnerID)
+	if err == nil {
+		t.Error("GetPartner() did not produce an error.")
+	}
+
+	if m != nil {
+		t.Errorf("GetPartner() did not return a nil relationship."+
+			"\n\texpected: %v\n\treceived: %v", nil, m)
+	}
+}
+
+// Tests happy path of Store.PopKey.
+func TestStore_PopKey(t *testing.T) {
+	s, _, _ := makeTestStore()
+	se, _ := makeTestSession()
+
+	// Pop Key that does not exist
+	fp := format.Fingerprint{0xF, 0x6, 0x2}
+	key, exists := s.PopKey(fp)
+	if exists {
+		t.Errorf("PopKey() popped a Key with fingerprint %v that should not "+
+			"exist.", fp)
+	}
+	if key != nil {
+		t.Errorf("PopKey() did not return a nil Key when it should not exist."+
+			"\n\texpected: +%v\n\treceived: %+v", nil, key)
+	}
+
+	// Add a Key
+	keys := []*Key{newKey(se, 0), newKey(se, 1), newKey(se, 2)}
+	s.add(keys)
+	fp = keys[0].Fingerprint()
+
+	// Pop a Key that does exist
+	key, exists = s.PopKey(fp)
+	if !exists {
+		t.Errorf("PopKey() could not find Key with fingerprint %v.", fp)
+	}
+
+	if !reflect.DeepEqual(keys[0], key) {
+		t.Errorf("PopKey() did not return the correct Key."+
+			"\n\texpected: %+v\n\trecieved: %+v", keys[0], key)
+	}
+}
+
+// Tests happy path of Store.CheckKey.
+func TestStore_CheckKey(t *testing.T) {
+	s, _, _ := makeTestStore()
+	se, _ := makeTestSession()
+
+	// Check for a Key that does not exist
+	fp := format.Fingerprint{0xF, 0x6, 0x2}
+	exists := s.CheckKey(fp)
+	if exists {
+		t.Errorf("CheckKey() found a Key with fingerprint %v.", fp)
+	}
+
+	// Add Keys
+	keys := []*Key{newKey(se, 0), newKey(se, 1), newKey(se, 2)}
+	s.add(keys)
+	fp = keys[0].Fingerprint()
+
+	// Check for a Key that does exist
+	exists = s.CheckKey(fp)
+	if !exists {
+		t.Errorf("CheckKey() could not find Key with fingerprint %v.", fp)
+	}
+}
+
+// Tests happy path of Store.GetDHPrivateKey.
+func TestStore_GetDHPrivateKey(t *testing.T) {
+	s, _, _ := makeTestStore()
+
+	if s.dhPrivateKey != s.GetDHPrivateKey() {
+		t.Errorf("GetDHPrivateKey() returned incorrect key."+
+			"\n\texpected: %v\n\treceived: %v",
+			s.dhPrivateKey, s.GetDHPrivateKey())
+	}
+}
+
+// Tests happy path of Store.GetDHPublicKey.
+func TestStore_GetDHPublicKey(t *testing.T) {
+	s, _, _ := makeTestStore()
+
+	if s.dhPublicKey != s.GetDHPublicKey() {
+		t.Errorf("GetDHPublicKey() returned incorrect key."+
+			"\n\texpected: %v\n\treceived: %v",
+			s.dhPublicKey, s.GetDHPublicKey())
+	}
+}
+
+// Tests happy path of Store.GetGroup.
+func TestStore_GetGroup(t *testing.T) {
+	s, _, _ := makeTestStore()
+
+	if s.grp != s.GetGroup() {
+		t.Errorf("GetGroup() returned incorrect key."+
+			"\n\texpected: %v\n\treceived: %v",
+			s.grp, s.GetGroup())
+	}
+}
+
+// Tests happy path of newFingerprints.
+func Test_newFingerprints(t *testing.T) {
+	expectedFp := fingerprints{toKey: make(map[format.Fingerprint]*Key)}
+	fp := newFingerprints()
+
+	if !reflect.DeepEqual(&expectedFp, &fp) {
+		t.Errorf("newFingerprints() returned incorrect fingerprints."+
+			"\n\texpected: %+v\n\treceived: %+v", &expectedFp, &fp)
+	}
+}
+
+// Tests happy path of fingerprints.add.
+func TestFingerprints_add(t *testing.T) {
+	se, _ := makeTestSession()
+	keys := []*Key{newKey(se, 0), newKey(se, 1), newKey(se, 2)}
+	fps := newFingerprints()
+	fps.add(keys)
+
+	for i, key := range keys {
+		testKey, exists := fps.toKey[key.Fingerprint()]
+		if !exists {
+			t.Errorf("add() failed to add key with fingerprint %v (round %d).",
+				key.Fingerprint(), i)
+		}
+
+		if !reflect.DeepEqual(key, testKey) {
+			t.Errorf("add() did not add the correct Key for fingerprint %v "+
+				"(round %d).\n\texpected: %v\n\treceived: %v",
+				key.Fingerprint(), i, key, testKey)
+		}
+	}
+}
+
+// Tests happy path of fingerprints.remove.
+func TestFingerprints_remove(t *testing.T) {
+	se, _ := makeTestSession()
+	keys := []*Key{newKey(se, 0), newKey(se, 1), newKey(se, 2)}
+	fps := newFingerprints()
+	fps.add(keys)
+	fps.remove(keys)
+
+	if len(fps.toKey) != 0 {
+		t.Errorf("remove() failed to remove all the keys.\n\tmap: %v", fps.toKey)
+	}
+}
+
+func makeTestStore() (*Store, *versioned.KV, *fastRNG.StreamGenerator) {
+	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
+	privKey := grp.NewInt(57)
+	kv := versioned.NewKV(make(ekv.Memstore))
+	rng := fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG)
+	s, err := NewStore(grp, kv, privKey, &id.ID{}, rng)
+	if err != nil {
+		panic("NewStore() produced an error: " + err.Error())
+	}
+	return s, kv, rng
+}
diff --git a/storage/messages.go b/storage/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..31f518fbd6433278b994d72192edc4bcdc69897e
--- /dev/null
+++ b/storage/messages.go
@@ -0,0 +1,15 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+const (
+	criticalMessagesKey    = "CriticalMessages"
+	criticalRawMessagesKey = "CriticalRawMessages"
+	garbledMessagesKey     = "GarbledMessages"
+	checkedRoundsKey       = "CheckedRounds"
+)
diff --git a/storage/ndf.go b/storage/ndf.go
new file mode 100644
index 0000000000000000000000000000000000000000..14cb4147b89a4fa53de52e505627c12dbd3abc15
--- /dev/null
+++ b/storage/ndf.go
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/xx_network/primitives/ndf"
+)
+
+const baseNdfKey = "baseNdf"
+
+func (s *Session) SetBaseNDF(def *ndf.NetworkDefinition) {
+	err := utility.SaveNDF(s.kv, baseNdfKey, def)
+	if err != nil {
+		jww.FATAL.Printf("Failed to dave the base NDF: %s", err)
+	}
+	s.baseNdf = def
+}
+
+func (s *Session) GetBaseNDF() *ndf.NetworkDefinition {
+	if s.baseNdf != nil {
+		return s.baseNdf
+	}
+	def, err := utility.LoadNDF(s.kv, baseNdfKey)
+	if err != nil {
+		jww.FATAL.Printf("Could not load the base NDF: %s", err)
+	}
+
+	s.baseNdf = def
+	return def
+}
diff --git a/storage/partition/multiPartMessage.go b/storage/partition/multiPartMessage.go
new file mode 100644
index 0000000000000000000000000000000000000000..7dc6cbb886136577caee703337485cab0367369f
--- /dev/null
+++ b/storage/partition/multiPartMessage.go
@@ -0,0 +1,219 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package partition
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+const currentMultiPartMessageVersion = 0
+const messageKey = "MultiPart"
+
+type multiPartMessage struct {
+	Sender       *id.ID
+	MessageID    uint64
+	NumParts     uint8
+	PresentParts uint8
+	Timestamp    time.Time
+	MessageType  message.Type
+
+	parts [][]byte
+	kv    *versioned.KV
+	mux   sync.Mutex
+}
+
+// loadOrCreateMultiPartMessage loads an extant multipart message store or
+// creates a new one and saves it if one does not exist.
+func loadOrCreateMultiPartMessage(sender *id.ID, messageID uint64,
+	kv *versioned.KV) *multiPartMessage {
+	kv = kv.Prefix(versioned.MakePartnerPrefix(sender)).Prefix(fmt.Sprintf("MessageID:%d", messageID))
+
+	obj, err := kv.Get(messageKey, currentMultiPartMessageVersion)
+	if err != nil {
+		if !ekv.Exists(err) {
+			mpm := &multiPartMessage{
+				Sender:       sender,
+				MessageID:    messageID,
+				NumParts:     0,
+				PresentParts: 0,
+				Timestamp:    time.Time{},
+				MessageType:  0,
+				kv:           kv,
+			}
+			if err = mpm.save(); err != nil {
+				jww.FATAL.Panicf("Failed to save new multi part "+
+					"message from %s messageID %v: %s", sender, messageID, err)
+			}
+			return mpm
+		}
+		jww.FATAL.Panicf("Failed to open multi part "+
+			"message from %s messageID %v: %s", sender, messageID, err)
+	}
+
+	mpm := &multiPartMessage{
+		kv: kv,
+	}
+
+	if err = json.Unmarshal(obj.Data, mpm); err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal multi part "+
+			"message from %s messageID %v: %s", sender, messageID, err)
+	}
+
+	return mpm
+}
+
+func (mpm *multiPartMessage) save() error {
+	data, err := json.Marshal(mpm)
+	if err != nil {
+		return errors.Wrap(err, "Failed to unmarshal multi-part message")
+	}
+
+	obj := versioned.Object{
+		Version:   currentMultiPartMessageVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	}
+
+	return mpm.kv.Set(messageKey, currentMultiPartMessageVersion, &obj)
+}
+
+func (mpm *multiPartMessage) Add(partNumber uint8, part []byte) {
+	mpm.mux.Lock()
+	defer mpm.mux.Unlock()
+
+	// Extend the list if needed
+	if len(mpm.parts) <= int(partNumber) {
+		mpm.parts = append(mpm.parts, make([][]byte, int(partNumber)-len(mpm.parts)+1)...)
+	}
+
+	mpm.parts[partNumber] = part
+	mpm.PresentParts++
+
+	if err := savePart(mpm.kv, partNumber, part); err != nil {
+		jww.FATAL.Panicf("Failed to save multi part "+
+			"message part %v from %s messageID %v: %s", partNumber, mpm.Sender,
+			mpm.MessageID, err)
+	}
+
+	if err := mpm.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save multi part "+
+			"message after adding part %v from %s messageID %v: %s", partNumber,
+			mpm.Sender, mpm.MessageID, err)
+	}
+}
+
+func (mpm *multiPartMessage) AddFirst(mt message.Type, partNumber uint8,
+	numParts uint8, timestamp time.Time, part []byte) {
+	mpm.mux.Lock()
+	defer mpm.mux.Unlock()
+
+	// Extend the list if needed
+	if len(mpm.parts) <= int(partNumber) {
+		mpm.parts = append(mpm.parts, make([][]byte, int(partNumber)-len(mpm.parts)+1)...)
+	}
+
+	mpm.NumParts = numParts
+	mpm.Timestamp = timestamp
+	mpm.MessageType = mt
+	mpm.parts[partNumber] = part
+	mpm.PresentParts++
+
+	if err := savePart(mpm.kv, partNumber, part); err != nil {
+		jww.FATAL.Panicf("Failed to save multi part "+
+			"message part %v from %s messageID %v: %s", partNumber, mpm.Sender,
+			mpm.MessageID, err)
+	}
+
+	if err := mpm.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save multi part message after adding part "+
+			"%v from %s messageID %v: %s",
+			partNumber, mpm.Sender, mpm.MessageID, err)
+	}
+}
+
+func (mpm *multiPartMessage) IsComplete(relationshipFingerprint []byte) (message.Receive, bool) {
+	mpm.mux.Lock()
+	if mpm.NumParts == 0 || mpm.NumParts != mpm.PresentParts {
+		mpm.mux.Unlock()
+		return message.Receive{}, false
+	}
+
+	// Make sure the parts buffer is large enough to load all parts from disk
+	if len(mpm.parts) < int(mpm.NumParts) {
+		mpm.parts = append(mpm.parts, make([][]byte, int(mpm.NumParts)-len(mpm.parts))...)
+	}
+
+	var err error
+	lenMsg := 0
+	// Load all parts from disk, deleting files from disk as we go along
+	for i := uint8(0); i < mpm.NumParts; i++ {
+		if mpm.parts[i] == nil {
+			if mpm.parts[i], err = loadPart(mpm.kv, i); err != nil {
+				jww.FATAL.Panicf("Failed to load multi part "+
+					"message part %v from %s messageID %v: %s", i, mpm.Sender,
+					mpm.MessageID, err)
+			}
+			if err = deletePart(mpm.kv, i); err != nil {
+				jww.FATAL.Panicf("Failed to delete  multi part "+
+					"message part %v from %s messageID %v: %s", i, mpm.Sender,
+					mpm.MessageID, err)
+			}
+		}
+		lenMsg += len(mpm.parts[i])
+	}
+
+	// delete the multipart message
+	mpm.delete()
+	mpm.mux.Unlock()
+
+	// Reconstruct the message
+	partOffset := 0
+	reconstructed := make([]byte, lenMsg)
+	for _, part := range mpm.parts {
+		copy(reconstructed[partOffset:partOffset+len(part)], part)
+		partOffset += len(part)
+	}
+
+	var mid e2e.MessageID
+	if len(relationshipFingerprint) != 0 {
+		mid = e2e.NewMessageID(relationshipFingerprint, mpm.MessageID)
+	}
+
+	// Return the message
+	m := message.Receive{
+		Payload:     reconstructed,
+		MessageType: mpm.MessageType,
+		Sender:      mpm.Sender,
+		Timestamp:   mpm.Timestamp,
+		// Encryption will be set externally
+		Encryption: 0,
+		ID:         mid,
+	}
+
+	return m, true
+}
+
+func (mpm *multiPartMessage) delete() {
+	//key := makeMultiPartMessageKey(mpm.MessageID)
+	if err := mpm.kv.Delete(messageKey,
+		currentMultiPartMessageVersion); err != nil {
+		jww.FATAL.Panicf("Failed to delete multi part "+
+			"message from %s messageID %v: %s", mpm.Sender,
+			mpm.MessageID, err)
+	}
+}
diff --git a/storage/partition/multiPartMessage_test.go b/storage/partition/multiPartMessage_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec871783b1e935a0653283102a29b7b8e7b9b939
--- /dev/null
+++ b/storage/partition/multiPartMessage_test.go
@@ -0,0 +1,273 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package partition
+
+import (
+	"bytes"
+	"encoding/json"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/e2e"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests the creation part of loadOrCreateMultiPartMessage().
+func Test_loadOrCreateMultiPartMessage_Create(t *testing.T) {
+	// Set up expected test value
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	expectedMpm := &multiPartMessage{
+		Sender:       id.NewIdFromUInt(prng.Uint64(), id.User, t),
+		MessageID:    prng.Uint64(),
+		NumParts:     0,
+		PresentParts: 0,
+		Timestamp:    time.Time{},
+		MessageType:  0,
+		kv:           versioned.NewKV(make(ekv.Memstore)),
+	}
+	expectedData, err := json.Marshal(expectedMpm)
+	if err != nil {
+		t.Fatalf("Failed to marshal expected multiPartMessage: %v", err)
+	}
+
+	// Make new multiPartMessage
+	mpm := loadOrCreateMultiPartMessage(expectedMpm.Sender,
+		expectedMpm.MessageID, expectedMpm.kv)
+
+	CheckMultiPartMessages(expectedMpm, mpm, t)
+
+	obj, err := mpm.kv.Get(messageKey, 0)
+	if err != nil {
+		t.Errorf("Get() failed to get multiPartMessage from key value store: %v", err)
+	}
+
+	if !bytes.Equal(expectedData, obj.Data) {
+		t.Errorf("loadOrCreateMultiPartMessage() did not save the "+
+			"multiPartMessage correctly.\n\texpected: %+v\n\treceived: %+v",
+			expectedData, obj.Data)
+	}
+}
+
+// Tests the loading part of loadOrCreateMultiPartMessage().
+func Test_loadOrCreateMultiPartMessage_Load(t *testing.T) {
+	// Set up expected test value
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	expectedMpm := &multiPartMessage{
+		Sender:       id.NewIdFromUInt(prng.Uint64(), id.User, t),
+		MessageID:    prng.Uint64(),
+		NumParts:     0,
+		PresentParts: 0,
+		Timestamp:    time.Time{},
+		MessageType:  0,
+		kv:           versioned.NewKV(make(ekv.Memstore)),
+	}
+	err := expectedMpm.save()
+	if err != nil {
+		t.Fatalf("Failed to save multiPartMessage: %v", err)
+	}
+
+	// Make new multiPartMessage
+	mpm := loadOrCreateMultiPartMessage(expectedMpm.Sender,
+		expectedMpm.MessageID, expectedMpm.kv)
+
+	CheckMultiPartMessages(expectedMpm, mpm, t)
+}
+
+func CheckMultiPartMessages(expectedMpm *multiPartMessage, mpm *multiPartMessage, t *testing.T) {
+	// The kv differs because it has prefix called, so we compare fields individually
+	if expectedMpm.Timestamp != mpm.Timestamp {
+		t.Errorf("timestamps mismatch: expected %v, got %v", expectedMpm.Timestamp, mpm.Timestamp)
+	}
+	if expectedMpm.MessageType != mpm.MessageType {
+		t.Errorf("messagetype mismatch: expected %v, got %v", expectedMpm.MessageID, mpm.MessageID)
+	}
+	if expectedMpm.MessageID != mpm.MessageID {
+		t.Errorf("messageid mismatch: expected %v, got %v", expectedMpm.MessageID, mpm.MessageID)
+	}
+	if expectedMpm.NumParts != mpm.NumParts {
+		t.Errorf("numparts mismatch: expected %v, got %v", expectedMpm.NumParts, mpm.NumParts)
+	}
+	if expectedMpm.PresentParts != mpm.PresentParts {
+		t.Errorf("presentparts mismatch: expected %v, got %v", expectedMpm.PresentParts, mpm.PresentParts)
+	}
+	if !expectedMpm.Sender.Cmp(mpm.Sender) {
+		t.Errorf("sender mismatch: expected %v, got %v", expectedMpm.Sender, mpm.Sender)
+	}
+	if len(expectedMpm.parts) != len(mpm.parts) {
+		t.Error("parts different length")
+	}
+	for i := range expectedMpm.parts {
+		if !bytes.Equal(expectedMpm.parts[i], mpm.parts[i]) {
+			t.Errorf("parts differed at index %v", i)
+		}
+	}
+}
+
+// Tests happy path of multiPartMessage.Add().
+func TestMultiPartMessage_Add(t *testing.T) {
+	// Generate test values
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	mpm := loadOrCreateMultiPartMessage(id.NewIdFromUInt(prng.Uint64(), id.User, t),
+		prng.Uint64(), versioned.NewKV(make(ekv.Memstore)))
+	partNums, parts := generateParts(prng, 0)
+
+	for i := range partNums {
+		mpm.Add(partNums[i], parts[i])
+	}
+
+	for i, p := range partNums {
+		if !bytes.Equal(mpm.parts[p], parts[i]) {
+			t.Errorf("Incorrect part at index %d (%d)."+
+				"\n\texpected: %v\n\treceived: %v", p, i, parts[i], mpm.parts[p])
+		}
+	}
+
+	if len(partNums) != int(mpm.PresentParts) {
+		t.Errorf("Incorrect PresentParts.\n\texpected: %d\n\treceived: %d",
+			len(partNums), int(mpm.PresentParts))
+	}
+
+	expectedData, err := json.Marshal(mpm)
+	if err != nil {
+		t.Fatalf("Failed to marshal expected multiPartMessage: %v", err)
+	}
+
+	obj, err := mpm.kv.Get(messageKey, 0)
+	if err != nil {
+		t.Errorf("Get() failed to get multiPartMessage from key value store: %v", err)
+	}
+
+	if !bytes.Equal(expectedData, obj.Data) {
+		t.Errorf("loadOrCreateMultiPartMessage() did not save the "+
+			"multiPartMessage correctly.\n\texpected: %+v\n\treceived: %+v",
+			expectedData, obj.Data)
+	}
+}
+
+// Tests happy path of multiPartMessage.AddFirst().
+func TestMultiPartMessage_AddFirst(t *testing.T) {
+	// Generate test values
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	expectedMpm := &multiPartMessage{
+		Sender:       id.NewIdFromUInt(prng.Uint64(), id.User, t),
+		MessageID:    prng.Uint64(),
+		NumParts:     uint8(prng.Uint32()),
+		PresentParts: 1,
+		Timestamp:    time.Now(),
+		MessageType:  message.NoType,
+		parts:        make([][]byte, 3),
+		kv:           versioned.NewKV(make(ekv.Memstore)),
+	}
+	expectedMpm.parts[2] = []byte{5, 8, 78, 9}
+	npm := loadOrCreateMultiPartMessage(expectedMpm.Sender,
+		expectedMpm.MessageID, expectedMpm.kv)
+
+	npm.AddFirst(expectedMpm.MessageType, 2, expectedMpm.NumParts,
+		expectedMpm.Timestamp, expectedMpm.parts[2])
+
+	CheckMultiPartMessages(expectedMpm, npm, t)
+
+	data, err := loadPart(npm.kv, 2)
+	if err != nil {
+		t.Errorf("loadPart() produced an error: %v", err)
+	}
+
+	if !bytes.Equal(data, expectedMpm.parts[2]) {
+		t.Errorf("AddFirst() did not save multiPartMessage correctly."+
+			"\n\texpected: %#v\n\treceived: %#v", expectedMpm.parts[2], data)
+	}
+}
+
+// Tests happy path of multiPartMessage.IsComplete().
+func TestMultiPartMessage_IsComplete(t *testing.T) {
+	// Create multiPartMessage and fill with random parts
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	mid := prng.Uint64()
+	mpm := loadOrCreateMultiPartMessage(id.NewIdFromUInt(prng.Uint64(), id.User, t),
+		mid, versioned.NewKV(make(ekv.Memstore)))
+	partNums, parts := generateParts(prng, 75)
+
+	// Check that IsComplete() is false where there are no parts
+	msg, complete := mpm.IsComplete([]byte{0})
+	if complete {
+		t.Error("IsComplete() returned true when NumParts == 0.")
+	}
+
+	mpm.AddFirst(message.Text, partNums[0], 75, time.Now(), parts[0])
+	for i := range partNums {
+		if i > 0 {
+			mpm.Add(partNums[i], parts[i])
+		}
+	}
+
+	msg, complete = mpm.IsComplete([]byte{0})
+	if !complete {
+		t.Error("IsComplete() returned false when the message should be complete.")
+	}
+
+	var payload []byte
+	for _, b := range mpm.parts {
+		payload = append(payload, b...)
+	}
+
+	expectedMsg := message.Receive{
+		Payload:     payload,
+		MessageType: mpm.MessageType,
+		Sender:      mpm.Sender,
+		Timestamp:   msg.Timestamp,
+		Encryption:  0,
+		ID:          e2e.NewMessageID([]byte{0}, mid),
+	}
+
+	if !reflect.DeepEqual(expectedMsg, msg) {
+		t.Errorf("IsComplete() did not return the expected message."+
+			"\n\texpected: %v\n\treceived: %v", expectedMsg, msg)
+	}
+
+}
+
+// Tests happy path of multiPartMessage.delete().
+func TestMultiPartMessage_delete(t *testing.T) {
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	kv := versioned.NewKV(make(ekv.Memstore))
+	mpm := loadOrCreateMultiPartMessage(id.NewIdFromUInt(prng.Uint64(), id.User, t),
+		prng.Uint64(), kv)
+
+	mpm.delete()
+	obj, err := kv.Get(messageKey, 0)
+	if ekv.Exists(err) {
+		t.Errorf("delete() did not properly delete key %s."+
+			"\n\tobject received: %+v", messageKey, obj)
+	}
+}
+
+// generateParts generates a list of test part numbers and a list of test parts.
+func generateParts(r *rand.Rand, numParts uint8) ([]uint8, [][]byte) {
+	if numParts == 0 {
+		numParts = uint8(25 + r.Intn(150))
+	}
+	partNums := make([]uint8, numParts)
+	parts := make([][]byte, len(partNums))
+	nums := map[uint8]bool{}
+
+	for i := range partNums {
+		n := uint8(r.Intn(int(numParts)))
+		for ; nums[n] == true; n = uint8(r.Intn(int(numParts))) {
+		}
+		nums[n] = true
+		partNums[i] = n
+		parts[i] = make([]byte, r.Int31n(100))
+		_, _ = r.Read(parts[i])
+	}
+
+	return partNums, parts
+}
diff --git a/storage/partition/part.go b/storage/partition/part.go
new file mode 100644
index 0000000000000000000000000000000000000000..66206fd4345d19ba233cfbd185f4251fb4bd74a6
--- /dev/null
+++ b/storage/partition/part.go
@@ -0,0 +1,54 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package partition
+
+import (
+	"fmt"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"time"
+)
+
+const currentMultiPartMessagePartVersion = 0
+
+func loadPart(kv *versioned.KV, partNum uint8) ([]byte, error) {
+	key := makeMultiPartMessagePartKey(partNum)
+
+	obj, err := kv.Get(key, currentMultiPartMessageVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	return obj.Data, nil
+}
+
+func savePart(kv *versioned.KV, partNum uint8, part []byte) error {
+	key := makeMultiPartMessagePartKey(partNum)
+
+	obj := versioned.Object{
+		Version:   currentMultiPartMessagePartVersion,
+		Timestamp: time.Now(),
+		Data:      part,
+	}
+
+	return kv.Set(key, currentMultiPartMessageVersion, &obj)
+}
+
+func deletePart(kv *versioned.KV, partNum uint8) error {
+	key := makeMultiPartMessagePartKey(partNum)
+	return kv.Delete(key, currentMultiPartMessageVersion)
+}
+
+// Make the key for a part
+func makeMultiPartMessagePartKey(part uint8) string {
+	return fmt.Sprintf("part:%v", part)
+}
+
+//func multiPartMessagePartPrefix(kv *versioned.KV, id uint64) *versioned.KV {
+//	return kv.prefix(keyMultiPartMessagePartPrefix).
+//		prefix(strconv.FormatUint(id, 32))
+//}
diff --git a/storage/partition/part_test.go b/storage/partition/part_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..22dfa11156024976b1d96c50988c464f1ec00612
--- /dev/null
+++ b/storage/partition/part_test.go
@@ -0,0 +1,126 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package partition
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+// Tests happy path of savePart().
+func Test_savePart(t *testing.T) {
+	// Set up test values
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partNum := uint8(prng.Uint32())
+	part := make([]byte, prng.Int31n(500))
+	prng.Read(part)
+	key := makeMultiPartMessagePartKey(partNum)
+
+	// Save part
+	err := savePart(kv, partNum, part)
+	if err != nil {
+		t.Errorf("savePart() produced an error: %v", err)
+	}
+
+	// Attempt to get from key value store
+	obj, err := kv.Get(key, 0)
+	if err != nil {
+		t.Errorf("Get() produced an error: %v", err)
+	}
+
+	// Check if the data is correct
+	if !bytes.Equal(part, obj.Data) {
+		t.Errorf("Part retrieved from key value store is not expected."+
+			"\n\texpected: %v\n\treceived: %v", part, obj.Data)
+	}
+}
+
+// Tests happy path of loadPart().
+func Test_loadPart(t *testing.T) {
+	// Set up test values
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	rootKv := versioned.NewKV(make(ekv.Memstore))
+	partNum := uint8(prng.Uint32())
+	part := make([]byte, prng.Int31n(500))
+	prng.Read(part)
+	key := makeMultiPartMessagePartKey(partNum)
+
+	// Save part to key value store
+	err := rootKv.Set(key, 0, &versioned.Object{Timestamp: time.Now(), Data: part})
+	if err != nil {
+		t.Fatalf("Failed to set object: %v", err)
+	}
+
+	// Load part from key value store
+	data, err := loadPart(rootKv, partNum)
+	if err != nil {
+		t.Errorf("loadPart() produced an error: %v", err)
+	}
+
+	// Check if the data is correct
+	if !bytes.Equal(part, data) {
+		t.Errorf("Part loaded from key value store is not expected."+
+			"\n\texpected: %v\n\treceived: %v", part, data)
+	}
+}
+
+// Tests that loadPart() returns an error that an item was not found for unsaved
+// key.
+func Test_loadPart_NotFoundError(t *testing.T) {
+	// Set up test values
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partNum := uint8(prng.Uint32())
+	part := make([]byte, prng.Int31n(500))
+	prng.Read(part)
+
+	// Load part from key value store
+	data, err := loadPart(kv, partNum)
+	if ekv.Exists(err) {
+		t.Errorf("loadPart() found an item for the key: %v", err)
+	}
+
+	// Check if the data is correct
+	if !bytes.Equal([]byte{}, data) {
+		t.Errorf("Part loaded from key value store is not expected."+
+			"\n\texpected: %v\n\treceived: %v", []byte{}, data)
+	}
+}
+
+// Test happy path of deletePart().
+func TestDeletePart(t *testing.T) {
+	// Set up test values
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	kv := versioned.NewKV(make(ekv.Memstore))
+	partNum := uint8(prng.Uint32())
+	part := make([]byte, prng.Int31n(500))
+	prng.Read(part)
+
+	// Save part
+	err := savePart(kv, partNum, part)
+	if err != nil {
+		t.Fatalf("savePart() produced an error: %v", err)
+	}
+
+	// Attempt to delete part
+	err = deletePart(kv, partNum)
+	if err != nil {
+		t.Errorf("deletePart() produced an error: %v", err)
+	}
+
+	// Check if part was deleted
+	_, err = loadPart(kv, partNum)
+	if ekv.Exists(err) {
+		t.Errorf("part was found in key value store: %v", err)
+	}
+}
diff --git a/storage/partition/store.go b/storage/partition/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..de601693828ccd159dbdaefad11139942df2c15c
--- /dev/null
+++ b/storage/partition/store.go
@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package partition
+
+import (
+	"crypto/md5"
+	"encoding/binary"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+type multiPartID [16]byte
+
+const packagePrefix = "Partition"
+
+type Store struct {
+	multiParts map[multiPartID]*multiPartMessage
+	kv         *versioned.KV
+	mux        sync.Mutex
+}
+
+func New(kv *versioned.KV) *Store {
+	return &Store{
+		multiParts: make(map[multiPartID]*multiPartMessage),
+		kv:         kv.Prefix(packagePrefix),
+	}
+}
+
+func (s *Store) AddFirst(partner *id.ID, mt message.Type, messageID uint64,
+	partNum, numParts uint8, timestamp time.Time,
+	part []byte, relationshipFingerprint []byte) (message.Receive, bool) {
+
+	mpm := s.load(partner, messageID)
+
+	mpm.AddFirst(mt, partNum, numParts, timestamp, part)
+
+	return mpm.IsComplete(relationshipFingerprint)
+}
+
+func (s *Store) Add(partner *id.ID, messageID uint64, partNum uint8,
+	part []byte, relationshipFingerprint []byte) (message.Receive, bool) {
+
+	mpm := s.load(partner, messageID)
+
+	mpm.Add(partNum, part)
+
+	return mpm.IsComplete(relationshipFingerprint)
+}
+
+func (s *Store) load(partner *id.ID, messageID uint64) *multiPartMessage {
+	mpID := getMultiPartID(partner, messageID)
+	s.mux.Lock()
+	mpm, ok := s.multiParts[mpID]
+	if !ok {
+		mpm = loadOrCreateMultiPartMessage(partner, messageID, s.kv)
+		s.multiParts[mpID] = mpm
+	}
+	s.mux.Unlock()
+
+	return mpm
+}
+
+func getMultiPartID(partner *id.ID, messageID uint64) multiPartID {
+	b := make([]byte, 8)
+	binary.BigEndian.PutUint64(b, messageID)
+	return md5.Sum(append(partner[:], b...))
+}
diff --git a/storage/partition/store_test.go b/storage/partition/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c150d026d55389793dd32952b2fe209a29192e8b
--- /dev/null
+++ b/storage/partition/store_test.go
@@ -0,0 +1,81 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package partition
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Tests happy path of New().
+func TestNew(t *testing.T) {
+	rootKv := versioned.NewKV(make(ekv.Memstore))
+	expectedStore := &Store{
+		multiParts: make(map[multiPartID]*multiPartMessage),
+		kv:         rootKv.Prefix(packagePrefix),
+	}
+
+	store := New(rootKv)
+
+	if !reflect.DeepEqual(expectedStore, store) {
+		t.Errorf("New() did not return the expecte Store."+
+			"\n\texpected: %v\n\treceived: %v", expectedStore, store)
+	}
+}
+
+// Tests happy path of Store.AddFirst().
+func TestStore_AddFirst(t *testing.T) {
+	part := []byte("Test message.")
+	s := New(versioned.NewKV(ekv.Memstore{}))
+
+	msg, complete := s.AddFirst(id.NewIdFromString("User", id.User, t),
+		message.Text, 5, 0, 1, time.Now(), part,
+		[]byte{0})
+
+	if !complete {
+		t.Errorf("AddFirst() returned that the message was not complete.")
+	}
+
+	if !bytes.Equal(part, msg.Payload) {
+		t.Errorf("AddFirst() returned message with invalid payload."+
+			"\n\texpected: %v\n\treceived: %v", part, msg.Payload)
+	}
+}
+
+// Tests happy path of Store.Add().
+func TestStore_Add(t *testing.T) {
+	part1 := []byte("Test message.")
+	part2 := []byte("Second Sentence.")
+	s := New(versioned.NewKV(ekv.Memstore{}))
+
+	msg, complete := s.AddFirst(id.NewIdFromString("User", id.User, t),
+		message.Text, 5, 0, 2, time.Now(), part1,
+		[]byte{0})
+
+	if complete {
+		t.Errorf("AddFirst() returned that the message was complete.")
+	}
+
+	msg, complete = s.Add(id.NewIdFromString("User", id.User, t),
+		5, 1, part2, []byte{0})
+	if !complete {
+		t.Errorf("AddFirst() returned that the message was not complete.")
+	}
+
+	part := append(part1, part2...)
+	if !bytes.Equal(part, msg.Payload) {
+		t.Errorf("AddFirst() returned message with invalid payload."+
+			"\n\texpected: %v\n\treceived: %v", part, msg.Payload)
+	}
+}
diff --git a/storage/reception/IdentityUse.go b/storage/reception/IdentityUse.go
new file mode 100644
index 0000000000000000000000000000000000000000..37432d9d1e8ea40b6da4e92a723ae99b96353f73
--- /dev/null
+++ b/storage/reception/IdentityUse.go
@@ -0,0 +1,48 @@
+package reception
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/xx_network/crypto/randomness"
+	"io"
+	"math/big"
+	"time"
+)
+
+type IdentityUse struct {
+	Identity
+
+	// Randomly generated time to poll between
+	StartRequest time.Time // Timestamp to request the start of bloom filters
+	EndRequest   time.Time // Timestamp to request the End of bloom filters
+
+	// Denotes if the identity is fake, in which case we do not process messages
+	Fake bool
+
+	// rounds data
+	UR *UnknownRound
+}
+
+// setSamplingPeriod add the Request mask as a random buffer around the sampling
+// time to obfuscate it.
+func (iu IdentityUse) setSamplingPeriod(rng io.Reader) (IdentityUse, error) {
+
+	// Generate the seed
+	seed := make([]byte, 32)
+	if _, err := rng.Read(seed); err != nil {
+		return IdentityUse{}, errors.WithMessage(err, "Failed to choose ID "+
+			"due to rng failure")
+	}
+
+	h, err := hash.NewCMixHash()
+	if err != nil {
+		return IdentityUse{}, err
+	}
+
+	// Calculate the period offset
+	periodOffset := randomness.RandInInterval(
+		big.NewInt(iu.RequestMask.Nanoseconds()), seed, h).Int64()
+	iu.StartRequest = iu.StartValid.Add(-time.Duration(periodOffset))
+	iu.EndRequest = iu.EndValid.Add(iu.RequestMask - time.Duration(periodOffset))
+	return iu, nil
+}
diff --git a/storage/reception/fake.go b/storage/reception/fake.go
new file mode 100644
index 0000000000000000000000000000000000000000..38cb63a241cc25122b98ff8b5a1762da0f3994a5
--- /dev/null
+++ b/storage/reception/fake.go
@@ -0,0 +1,45 @@
+package reception
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"io"
+	"time"
+)
+
+// generateFakeIdentity generates a fake identity of the given size with the
+// given random number generator
+func generateFakeIdentity(rng io.Reader, idSize uint, now time.Time) (IdentityUse, error) {
+	// Randomly generate an identity
+	randIdBytes := make([]byte, id.ArrIDLen-1)
+	if _, err := rng.Read(randIdBytes); err != nil {
+		return IdentityUse{}, errors.WithMessage(err, "failed to "+
+			"generate a random identity when none is available")
+	}
+
+	randID := &id.ID{}
+	copy(randID[:id.ArrIDLen-1], randIdBytes)
+	randID.SetType(id.User)
+
+	// Generate the current ephemeral ID from the random identity
+	ephID, start, end, err := ephemeral.GetId(randID, idSize, now.UnixNano())
+	if err != nil {
+		return IdentityUse{}, errors.WithMessage(err, "failed to generate an "+
+			"ephemeral ID for random identity when none is available")
+	}
+
+	return IdentityUse{
+		Identity: Identity{
+			EphId:       ephID,
+			Source:      randID,
+			End:         end,
+			ExtraChecks: 0,
+			StartValid:  start,
+			EndValid:    end,
+			RequestMask: 24 * time.Hour,
+			Ephemeral:   true,
+		},
+		Fake: true,
+	}, nil
+}
diff --git a/storage/reception/fake_test.go b/storage/reception/fake_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1327f84a03f6f779a63f652584fdb4e17c2cb72b
--- /dev/null
+++ b/storage/reception/fake_test.go
@@ -0,0 +1,66 @@
+package reception
+
+import (
+	"encoding/json"
+	"math"
+	"math/rand"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Tests Generate Fake identity is consistent and returns a correct result.
+func Test_generateFakeIdentity(t *testing.T) {
+	rng := rand.New(rand.NewSource(42))
+
+	end, _ := json.Marshal(time.Unix(0, 1258494203759765625))
+	startValid, _ := json.Marshal(time.Unix(0, 1258407803759765625))
+	endValid, _ := json.Marshal(time.Unix(0, 1258494203759765625))
+	expected := "{\"EphId\":[0,0,0,0,0,0,46,197]," +
+		"\"Source\":[83,140,127,150,177,100,191,27,151,187,159,75,180,114," +
+		"232,159,91,20,132,242,82,9,201,217,52,62,146,186,9,221,157,82,3]," +
+		"\"End\":" + string(end) + ",\"ExtraChecks\":0," +
+		"\"StartValid\":" + string(startValid) + "," +
+		"\"EndValid\":" + string(endValid) + "," +
+		"\"RequestMask\":86400000000000,\"Ephemeral\":true," +
+		"\"StartRequest\":\"0001-01-01T00:00:00Z\"," +
+		"\"EndRequest\":\"0001-01-01T00:00:00Z\",\"Fake\":true,\"UR\":null}"
+
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+
+	received, err := generateFakeIdentity(rng, 15, timestamp)
+	if err != nil {
+		t.Errorf("generateFakeIdentity() returned an error: %+v", err)
+	}
+
+	receivedJson, _ := json.Marshal(received)
+
+	if expected != string(receivedJson) {
+		t.Errorf("The fake identity was generated incorrectly.\n "+
+			"expected: %s\nreceived: %s", expected, receivedJson)
+	}
+}
+
+// Error path: fails to generate random bytes.
+func Test_generateFakeIdentity_RngError(t *testing.T) {
+	rng := strings.NewReader("")
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+
+	_, err := generateFakeIdentity(rng, 15, timestamp)
+	if err == nil || !strings.Contains(err.Error(), "failed to generate a random identity") {
+		t.Errorf("generateFakeIdentity() did not return the correct error on "+
+			"failure to generate random bytes: %+v", err)
+	}
+}
+
+// Error path: fails to get the ephemeral ID.
+func Test_generateFakeIdentity_GetEphemeralIdError(t *testing.T) {
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+
+	_, err := generateFakeIdentity(rng, math.MaxUint64, timestamp)
+	if err == nil || !strings.Contains(err.Error(), "ephemeral ID") {
+		t.Errorf("generateFakeIdentity() did not return the correct error on "+
+			"failure to generate ephemeral ID: %+v", err)
+	}
+}
diff --git a/storage/reception/identity.go b/storage/reception/identity.go
new file mode 100644
index 0000000000000000000000000000000000000000..e4fa2556c6564226c7226689b30478e9a693493c
--- /dev/null
+++ b/storage/reception/identity.go
@@ -0,0 +1,90 @@
+package reception
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"strconv"
+	"time"
+)
+
+const identityStorageKey = "IdentityStorage"
+const identityStorageVersion = 0
+
+type Identity struct {
+	// Identity
+	EphId  ephemeral.Id
+	Source *id.ID
+
+	// Usage variables
+	End         time.Time // Timestamp when active polling will stop
+	ExtraChecks uint      // Number of extra checks executed as active after the
+	// ID exits active
+
+	// Polling parameters
+	StartValid  time.Time     // Timestamp when the ephID begins being valid
+	EndValid    time.Time     // Timestamp when the ephID stops being valid
+	RequestMask time.Duration // Amount of extra time requested for the poll in
+	// order to mask the exact valid time for the ID
+
+	// Makes the identity not store on disk
+	Ephemeral bool
+}
+
+func loadIdentity(kv *versioned.KV) (Identity, error) {
+	obj, err := kv.Get(identityStorageKey, identityStorageVersion)
+	if err != nil {
+		return Identity{}, errors.WithMessage(err, "Failed to load Identity")
+	}
+
+	r := Identity{}
+	err = json.Unmarshal(obj.Data, &r)
+	if err != nil {
+		return Identity{}, errors.WithMessage(err, "Failed to unmarshal Identity")
+	}
+	return r, nil
+}
+
+func (i Identity) store(kv *versioned.KV) error {
+	// Marshal the registration
+	regStr, err := json.Marshal(&i)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to marshal Identity")
+	}
+
+	// Create versioned object with data
+	obj := &versioned.Object{
+		Version:   identityStorageVersion,
+		Timestamp: time.Now(),
+		Data:      regStr,
+	}
+
+	// Store the data
+	err = kv.Set(identityStorageKey, identityStorageVersion, obj)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to store Identity")
+	}
+
+	return nil
+}
+
+func (i Identity) delete(kv *versioned.KV) error {
+	return kv.Delete(identityStorageKey, identityStorageVersion)
+}
+
+func (i *Identity) String() string {
+	return strconv.FormatInt(i.EphId.Int64(), 16) + " " + i.Source.String()
+}
+
+func (i Identity) Equal(b Identity) bool {
+	return i.EphId == b.EphId &&
+		i.Source.Cmp(b.Source) &&
+		i.End.Equal(b.End) &&
+		i.ExtraChecks == b.ExtraChecks &&
+		i.StartValid.Equal(b.StartValid) &&
+		i.EndValid.Equal(b.EndValid) &&
+		i.RequestMask == b.RequestMask &&
+		i.Ephemeral == b.Ephemeral
+}
diff --git a/storage/reception/identityUse_test.go b/storage/reception/identityUse_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e54d73e723a4daa8365f34e446c60916a43ca3dd
--- /dev/null
+++ b/storage/reception/identityUse_test.go
@@ -0,0 +1,67 @@
+package reception
+
+import (
+	"math/rand"
+	"testing"
+	"time"
+)
+
+func TestIdentityUse_SetSamplingPeriod(t *testing.T) {
+	rng := rand.New(rand.NewSource(42))
+
+	const numTests = 1000
+
+	for i := 0; i < numTests; i++ {
+		// Generate an identity use
+		start := randate()
+		end := start.Add(time.Duration(rand.Uint64() % uint64(92*time.Hour)))
+		mask := time.Duration(rand.Uint64() % uint64(92*time.Hour))
+		iu := IdentityUse{
+			Identity: Identity{
+				StartValid:  start,
+				EndValid:    end,
+				RequestMask: mask,
+			},
+		}
+
+		// Generate the sampling period
+		var err error
+		iu, err = iu.setSamplingPeriod(rng)
+		if err != nil {
+			t.Errorf("Errored in generatign sampling "+
+				"period on interation %v: %+v", i, err)
+		}
+
+		// Test that the range between the periods is correct
+		resultRange := iu.EndRequest.Sub(iu.StartRequest)
+		expectedRange := iu.EndValid.Sub(iu.StartValid) + iu.RequestMask
+
+		if resultRange != expectedRange {
+			t.Errorf("The generated sampling period is of the wrong "+
+				"size: Expecterd: %s, Received: %s", expectedRange, resultRange)
+		}
+
+		// Test the sampling range does not exceed a reasonable lower bound
+		lowerBound := iu.StartValid.Add(-iu.RequestMask)
+		if !iu.StartRequest.After(lowerBound) {
+			t.Errorf("Start request exceeds the reasonable lower "+
+				"bound: \n\t Bound: %s\n\t Start: %s", lowerBound, iu.StartValid)
+		}
+
+		// Test the sampling range does not exceed a reasonable upper bound
+		upperBound := iu.EndValid.Add(iu.RequestMask - time.Millisecond)
+		if iu.EndRequest.After(upperBound) {
+			t.Errorf("End request exceeds the reasonable upper bound")
+		}
+	}
+
+}
+
+func randate() time.Time {
+	min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix()
+	max := time.Date(2070, 1, 0, 0, 0, 0, 0, time.UTC).Unix()
+	delta := max - min
+
+	sec := rand.Int63n(delta) + min
+	return time.Unix(sec, 0)
+}
diff --git a/storage/reception/identity_test.go b/storage/reception/identity_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8499050e9e988b1a48b9e59ad8501ddd211a74c6
--- /dev/null
+++ b/storage/reception/identity_test.go
@@ -0,0 +1,99 @@
+package reception
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+func TestIdentity_EncodeDecode(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	r := Identity{
+		EphId:       ephemeral.Id{},
+		Source:      &id.Permissioning,
+		End:         time.Now().Round(0),
+		ExtraChecks: 12,
+		StartValid:  time.Now().Round(0),
+		EndValid:    time.Now().Round(0),
+		RequestMask: 2 * time.Hour,
+		Ephemeral:   false,
+	}
+
+	err := r.store(kv)
+	if err != nil {
+		t.Errorf("Failed to store: %+v", err)
+	}
+
+	rLoad, err := loadIdentity(kv)
+	if err != nil {
+		t.Errorf("Failed to load: %+v", err)
+	}
+
+	if !r.Equal(rLoad) {
+		t.Errorf("Registrations are not the same\nsaved:  %+v\nloaded: %+v",
+			r, rLoad)
+	}
+}
+
+func TestIdentity_Delete(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	r := Identity{
+		EphId:       ephemeral.Id{},
+		Source:      &id.Permissioning,
+		End:         time.Now().Round(0),
+		ExtraChecks: 12,
+		StartValid:  time.Now().Round(0),
+		EndValid:    time.Now().Round(0),
+		RequestMask: 2 * time.Hour,
+		Ephemeral:   false,
+	}
+
+	err := r.store(kv)
+	if err != nil {
+		t.Errorf("Failed to store: %s", err)
+	}
+
+	err = r.delete(kv)
+	if err != nil {
+		t.Errorf("Failed to delete: %s", err)
+	}
+
+	_, err = loadIdentity(kv)
+	if err == nil {
+		t.Error("Load after delete succeeded.")
+	}
+}
+
+func TestIdentity_String(t *testing.T) {
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	received, _ := generateFakeIdentity(rng, 15, timestamp)
+	expected := "-1763 U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID"
+
+	s := received.String()
+	if s != expected {
+		t.Errorf("String did not return the correct value."+
+			"\nexpected: %s\nreceived: %s", expected, s)
+	}
+}
+
+func TestIdentity_Equal(t *testing.T) {
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	a, _ := generateFakeIdentity(rand.New(rand.NewSource(42)), 15, timestamp)
+	b, _ := generateFakeIdentity(rand.New(rand.NewSource(42)), 15, timestamp)
+	c, _ := generateFakeIdentity(rand.New(rand.NewSource(42)), 15, time.Now())
+
+	if !a.Identity.Equal(b.Identity) {
+		t.Errorf("Equal() found two equal identities as unequal."+
+			"\na: %s\nb: %s", a.String(), b.String())
+	}
+
+	if a.Identity.Equal(c.Identity) {
+		t.Errorf("Equal() found two unequal identities as equal."+
+			"\na: %s\nc: %s", a.String(), c.String())
+	}
+}
diff --git a/storage/reception/registration.go b/storage/reception/registration.go
new file mode 100644
index 0000000000000000000000000000000000000000..2365cdea68701671620ffe9b86eddef8e901c7a7
--- /dev/null
+++ b/storage/reception/registration.go
@@ -0,0 +1,94 @@
+package reception
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"strconv"
+	"time"
+)
+
+const knownRoundsStorageKey = "krStorage"
+
+type registration struct {
+	Identity
+	ur *UnknownRound
+	kv *versioned.KV
+}
+
+func newRegistration(reg Identity, kv *versioned.KV) (*registration, error) {
+	// Round the times to remove the monotonic clocks for future saving
+	reg.StartValid = reg.StartValid.Round(0)
+	reg.EndValid = reg.EndValid.Round(0)
+	reg.End = reg.End.Round(0)
+
+	now := time.Now()
+
+	// Do edge checks to determine if the identity is valid
+	if now.After(reg.End) && reg.ExtraChecks < 1 {
+		return nil, errors.New("Cannot create a registration for an " +
+			"identity which has expired")
+	}
+
+	// Set the prefix
+	kv = kv.Prefix(regPrefix(reg.EphId, reg.Source, reg.StartValid))
+
+	r := &registration{
+		Identity: reg,
+		ur:       NewUnknownRound(!reg.Ephemeral, kv),
+		kv:       kv,
+	}
+
+	// If this is not ephemeral, then store everything
+	if !reg.Ephemeral {
+		// Store known rounds
+		var err error
+		// Store the registration
+		if err = reg.store(kv); err != nil {
+			return nil, errors.WithMessage(err, "failed to store registration")
+		}
+	}
+
+	return r, nil
+}
+
+func loadRegistration(EphId ephemeral.Id, Source *id.ID, startValid time.Time,
+	kv *versioned.KV) (*registration, error) {
+
+	kv = kv.Prefix(regPrefix(EphId, Source, startValid))
+
+	reg, err := loadIdentity(kv)
+	if err != nil {
+		return nil, errors.WithMessagef(err, "Failed to load identity "+
+			"for %s", regPrefix(EphId, Source, startValid))
+	}
+
+	ur := LoadUnknownRound(kv)
+
+	r := &registration{
+		Identity: reg,
+		ur:       ur,
+		kv:       kv,
+	}
+
+	return r, nil
+}
+
+func (r *registration) Delete() error {
+	if !r.Ephemeral {
+		r.ur.delete()
+		if err := r.delete(r.kv); err != nil {
+			return errors.WithMessagef(err, "Failed to delete registration "+
+				"public data %s", r)
+		}
+	}
+
+	return nil
+}
+
+func regPrefix(EphId ephemeral.Id, Source *id.ID, startTime time.Time) string {
+	return "receptionRegistration_" +
+		strconv.FormatInt(EphId.Int64(), 16) + Source.String() +
+		strconv.FormatInt(startTime.Round(0).UnixNano(), 10)
+}
diff --git a/storage/reception/registration_test.go b/storage/reception/registration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..77475258af4ef6030b222b5b58523bc82e2e530b
--- /dev/null
+++ b/storage/reception/registration_test.go
@@ -0,0 +1,140 @@
+package reception
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"math/rand"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestNewRegistration_Failed(t *testing.T) {
+	// Generate an identity for use
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	idu, _ := generateFakeIdentity(rng, 15, timestamp)
+	id := idu.Identity
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	id.End = time.Time{}
+	id.ExtraChecks = 0
+
+	_, err := newRegistration(id, kv)
+	if err == nil || !strings.Contains(err.Error(), "Cannot create a registration for an identity which has expired") {
+		t.Error("Registration creation succeeded with expired identity.")
+	}
+}
+
+func TestNewRegistration_Ephemeral(t *testing.T) {
+	// Generate an identity for use
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	idu, _ := generateFakeIdentity(rng, 15, timestamp)
+	id := idu.Identity
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	id.End = time.Now().Add(1 * time.Hour)
+	id.ExtraChecks = 2
+	id.Ephemeral = true
+
+	reg, err := newRegistration(id, kv)
+	if err != nil {
+		t.Fatalf("Registration creation failed when it should have "+
+			"succeeded: %+v", err)
+	}
+
+	if reg.ur == nil {
+		t.Error("Ephemeral identity does not have a known rounds.")
+	}
+
+	if _, err = reg.kv.Get(identityStorageKey, 0); err == nil {
+		t.Error("Ephemeral identity stored the identity when it should not have.")
+	}
+}
+
+func TestNewRegistration_Persistent(t *testing.T) {
+	// Generate an identity for use
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	idu, _ := generateFakeIdentity(rng, 15, timestamp)
+	id := idu.Identity
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	id.End = time.Now().Add(1 * time.Hour)
+	id.ExtraChecks = 2
+	id.Ephemeral = false
+
+	reg, err := newRegistration(id, kv)
+	if err != nil {
+		t.Fatalf("Registration creation failed when it should have "+
+			"succeeded: %+v", err)
+	}
+
+	if reg.ur == nil {
+		t.Error("Persistent identity does not have a known rounds.")
+	}
+
+	// Check if the known rounds is stored, it should not be. this will panic
+	// if it isnt
+	LoadUnknownRound(reg.kv)
+
+	if _, err = reg.kv.Get(identityStorageKey, 0); err != nil {
+		t.Errorf("Persistent identity did not store the identity when "+
+			"it should: %+v.", err)
+	}
+}
+
+func TestLoadRegistration(t *testing.T) {
+	// Generate an identity for use
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	idu, _ := generateFakeIdentity(rng, 15, timestamp)
+	id := idu.Identity
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	id.End = time.Now().Add(1 * time.Hour)
+	id.ExtraChecks = 2
+	id.Ephemeral = false
+
+	_, err := newRegistration(id, kv)
+	if err != nil {
+		t.Fatalf("Registration creation failed when it should have "+
+			"succeeded: %+v", err)
+	}
+
+	reg, err := loadRegistration(idu.EphId, idu.Source, idu.StartValid, kv)
+	if err != nil {
+		t.Fatalf("Registration loading failed: %+v", err)
+	}
+
+	if reg.ur == nil {
+		t.Error("Loading should have a UR.")
+	}
+
+}
+
+// TODO: finish
+func Test_registration_Delete(t *testing.T) {
+	// Generate an identity for use
+	rng := rand.New(rand.NewSource(42))
+	timestamp := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
+	idu, _ := generateFakeIdentity(rng, 15, timestamp)
+	id := idu.Identity
+	kv := versioned.NewKV(make(ekv.Memstore))
+
+	id.End = time.Now().Add(1 * time.Hour)
+	id.ExtraChecks = 2
+	id.Ephemeral = false
+
+	r, err := newRegistration(id, kv)
+	if err != nil {
+		t.Fatalf("Registration creation failed when it should have "+
+			"succeeded: %+v", err)
+	}
+
+	err = r.Delete()
+	if err != nil {
+		t.Errorf("delete() returned an error: %+v", err)
+	}
+}
diff --git a/storage/reception/store.go b/storage/reception/store.go
new file mode 100644
index 0000000000000000000000000000000000000000..dcf7dcb796bd1a4232d877cad825cd4b197c6939
--- /dev/null
+++ b/storage/reception/store.go
@@ -0,0 +1,384 @@
+package reception
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"io"
+	"strconv"
+	"sync"
+	"time"
+)
+
+const receptionPrefix = "reception"
+const receptionStoreStorageKey = "receptionStoreKey"
+const receptionStoreStorageVersion = 0
+const receptionIDSizeStorageKey = "receptionIDSizeKey"
+const receptionIDSizeStorageVersion = 0
+const defaultIDSize = 12
+
+type Store struct {
+	// Identities which are being actively checked
+	active      []*registration
+	present     map[idHash]interface{}
+	idSize      int
+	idSizeCond  *sync.Cond
+	isIdSizeSet bool
+
+	kv *versioned.KV
+
+	mux sync.Mutex
+}
+
+type storedReference struct {
+	Eph        ephemeral.Id
+	Source     *id.ID
+	StartValid time.Time
+}
+
+type idHash [16]byte
+
+func makeIdHash(ephID ephemeral.Id, source *id.ID) idHash {
+	h := md5.New()
+	h.Write(ephID[:])
+	h.Write(source.Bytes())
+	idHashBytes := h.Sum(nil)
+	idH := idHash{}
+	copy(idH[:], idHashBytes)
+	return idH
+}
+
+// NewStore creates a new reception store that starts empty.
+func NewStore(kv *versioned.KV) *Store {
+	kv = kv.Prefix(receptionPrefix)
+	s := &Store{
+		active:     make([]*registration, 0),
+		present:    make(map[idHash]interface{}),
+		idSize:     defaultIDSize * 2,
+		kv:         kv,
+		idSizeCond: sync.NewCond(&sync.Mutex{}),
+	}
+
+	// Store the empty list
+	if err := s.save(); err != nil {
+		jww.FATAL.Panicf("Failed to save new reception store: %+v", err)
+	}
+
+	// Update the size so queries can be made
+	s.UpdateIdSize(defaultIDSize)
+
+	return s
+}
+
+func LoadStore(kv *versioned.KV) *Store {
+	kv = kv.Prefix(receptionPrefix)
+	s := &Store{
+		kv:         kv,
+		present:    make(map[idHash]interface{}),
+		idSizeCond: sync.NewCond(&sync.Mutex{}),
+	}
+
+	// Load the versioned object for the reception list
+	vo, err := kv.Get(receptionStoreStorageKey,
+		receptionStoreStorageVersion)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to get the reception storage list: %+v", err)
+	}
+
+	identities := make([]storedReference, len(s.active))
+	err = json.Unmarshal(vo.Data, &identities)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal the reception storage list: %+v", err)
+	}
+
+	s.active = make([]*registration, len(identities))
+	for i, sr := range identities {
+		s.active[i], err = loadRegistration(sr.Eph, sr.Source, sr.StartValid, s.kv)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to load registration for %s: %+v",
+				regPrefix(sr.Eph, sr.Source, sr.StartValid), err)
+		}
+		s.present[makeIdHash(sr.Eph, sr.Source)] = nil
+	}
+
+	// Load the ephemeral ID length
+	vo, err = kv.Get(receptionIDSizeStorageKey,
+		receptionIDSizeStorageVersion)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to get the reception ID size: %+v", err)
+	}
+
+	if s.idSize, err = strconv.Atoi(string(vo.Data)); err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal the reception ID size: %+v",
+			err)
+	}
+
+	return s
+}
+
+func (s *Store) save() error {
+	identities := s.makeStoredReferences()
+
+	data, err := json.Marshal(&identities)
+	if err != nil {
+		return errors.WithMessage(err, "failed to store reception store")
+	}
+
+	// Create versioned object with data
+	obj := &versioned.Object{
+		Version:   receptionStoreStorageVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	}
+
+	err = s.kv.Set(receptionStoreStorageKey, receptionStoreStorageVersion, obj)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to store reception store")
+	}
+
+	return nil
+}
+
+// makeStoredReferences generates a reference of any non-ephemeral identities
+// for storage.
+func (s *Store) makeStoredReferences() []storedReference {
+	identities := make([]storedReference, len(s.active))
+
+	i := 0
+	for _, reg := range s.active {
+		if !reg.Ephemeral {
+			identities[i] = storedReference{
+				Eph:        reg.EphId,
+				Source:     reg.Source,
+				StartValid: reg.StartValid.Round(0),
+			}
+			i++
+		}
+	}
+
+	return identities[:i]
+}
+
+func (s *Store) GetIdentity(rng io.Reader) (IdentityUse, error) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	now := time.Now()
+
+	// Remove any now expired identities
+	s.prune(now)
+
+	var identity IdentityUse
+	var err error
+
+	// If the list is empty, then we return a randomly generated identity to
+	// poll with so we can continue tracking the network and to further
+	// obfuscate network identities.
+	if len(s.active) == 0 {
+		identity, err = generateFakeIdentity(rng, uint(s.idSize), now)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to generate a new ID when none "+
+				"available: %+v", err)
+		}
+	} else {
+		identity, err = s.selectIdentity(rng, now)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to select an ID: %+v", err)
+		}
+	}
+
+	// Calculate the sampling period
+	identity, err = identity.setSamplingPeriod(rng)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to calculate the sampling period: "+
+			"%+v", err)
+	}
+
+	return identity, nil
+}
+
+func (s *Store) AddIdentity(identity Identity) error {
+
+	idH := makeIdHash(identity.EphId, identity.Source)
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	//do not make duplicates of IDs
+	if _, ok := s.present[idH]; ok {
+		jww.DEBUG.Printf("Ignoring duplicate identity for %d (%s)",
+			identity.EphId, identity.Source)
+		return nil
+	}
+
+	if identity.StartValid.After(identity.EndValid) {
+		return errors.Errorf("Cannot add an identity which start valid "+
+			"time (%s) is after its end valid time(%s)", identity.StartValid,
+			identity.EndValid)
+	}
+
+	reg, err := newRegistration(identity, s.kv)
+	if err != nil {
+		return errors.WithMessage(err, "failed to add new identity to "+
+			"reception store")
+	}
+
+	s.active = append(s.active, reg)
+	s.present[idH] = nil
+	if !identity.Ephemeral {
+		if err := s.save(); err != nil {
+			jww.FATAL.Panicf("Failed to save reception store after identity " +
+				"addition")
+		}
+	}
+
+	return nil
+}
+
+func (s *Store) RemoveIdentity(ephID ephemeral.Id) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	for i := 0; i < len(s.active); i++ {
+		inQuestion := s.active[i]
+		if bytes.Equal(inQuestion.EphId[:], ephID[:]) {
+			s.active = append(s.active[:i], s.active[i+1:]...)
+			err := inQuestion.Delete()
+			if err != nil {
+				jww.FATAL.Panicf("Failed to delete identity: %+v", err)
+			}
+			if !inQuestion.Ephemeral {
+				if err := s.save(); err != nil {
+					jww.FATAL.Panicf("Failed to save reception store after " +
+						"identity removal")
+				}
+			}
+			return
+		}
+	}
+}
+
+// Returns whether idSize is set to default
+func (s *Store) IsIdSizeDefault() bool {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	return s.isIdSizeSet
+}
+
+// Updates idSize boolean and broadcasts to any waiting
+// idSize readers that id size is now updated with the network
+func (s *Store) MarkIdSizeAsSet() {
+	s.mux.Lock()
+	s.idSizeCond.L.Lock()
+	defer s.mux.Unlock()
+	defer s.idSizeCond.L.Unlock()
+	s.isIdSizeSet = true
+	s.idSizeCond.Broadcast()
+}
+
+// Wrapper function which calls a
+// sync.Cond wait. Used on any reader of idSize
+// who cannot use the default id size
+func (s *Store) WaitForIdSizeUpdate() {
+	s.idSizeCond.L.Lock()
+	defer s.idSizeCond.L.Unlock()
+	for !s.IsIdSizeDefault() {
+
+		s.idSizeCond.Wait()
+	}
+}
+
+func (s *Store) UpdateIdSize(idSize uint) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if s.idSize == int(idSize) {
+		return
+	}
+	jww.INFO.Printf("Updating address space size to %v", idSize)
+
+	s.idSize = int(idSize)
+
+	// Store the ID size
+	obj := &versioned.Object{
+		Version:   receptionIDSizeStorageVersion,
+		Timestamp: time.Now(),
+		Data:      []byte(strconv.Itoa(s.idSize)),
+	}
+
+	err := s.kv.Set(receptionIDSizeStorageKey,
+		receptionIDSizeStorageVersion, obj)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to store reception ID size: %+v", err)
+	}
+}
+
+func (s *Store) GetIDSize() uint {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	return uint(s.idSize)
+}
+
+func (s *Store) prune(now time.Time) {
+	lengthBefore := len(s.active)
+
+	// Prune the list
+	for i := 0; i < len(s.active); i++ {
+		inQuestion := s.active[i]
+		if now.After(inQuestion.End) && inQuestion.ExtraChecks == 0 {
+			if err := inQuestion.Delete(); err != nil {
+				jww.ERROR.Printf("Failed to delete Identity for %s: "+
+					"%+v", inQuestion, err)
+			}
+
+			s.active = append(s.active[:i], s.active[i+1:]...)
+
+			i--
+		}
+	}
+
+	// Save the list if it changed
+	if lengthBefore != len(s.active) {
+		jww.INFO.Printf("Pruned %d identities", lengthBefore-len(s.active))
+		if err := s.save(); err != nil {
+			jww.FATAL.Panicf("Failed to store reception storage")
+		}
+	}
+}
+
+func (s *Store) selectIdentity(rng io.Reader, now time.Time) (IdentityUse, error) {
+	// Choose a member from the list
+	var selected *registration
+
+	if len(s.active) == 1 {
+		selected = s.active[0]
+	} else {
+		seed := make([]byte, 32)
+		if _, err := rng.Read(seed); err != nil {
+			return IdentityUse{}, errors.WithMessage(err, "Failed to "+
+				"choose ID due to rng failure")
+		}
+
+		selectedNum := large.NewInt(1).Mod(large.NewIntFromBytes(seed), large.NewInt(int64(len(s.active))))
+		selected = s.active[selectedNum.Uint64()]
+	}
+
+	if now.After(selected.End) {
+		selected.ExtraChecks--
+	}
+
+	jww.TRACE.Printf("Selected identity: EphId: %d  ID: %s  End: %s  StartValid: %s  EndValid: %s",
+		selected.EphId.Int64(), selected.Source, selected.End.Format("01/02/06 03:04:05 pm"),
+		selected.StartValid.Format("01/02/06 03:04:05 pm"), selected.EndValid.Format("01/02/06 03:04:05 pm"))
+
+	return IdentityUse{
+		Identity: selected.Identity,
+		Fake:     false,
+		UR:       selected.ur,
+	}, nil
+}
diff --git a/storage/reception/store_test.go b/storage/reception/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f5dcd089127db9939a205e2d8e6ea4733c65bf0
--- /dev/null
+++ b/storage/reception/store_test.go
@@ -0,0 +1,291 @@
+package reception
+
+import (
+	"bytes"
+	"encoding/json"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestNewStore(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	expected := &Store{
+		active: make([]*registration, 0),
+		idSize: defaultIDSize,
+		kv:     kv,
+	}
+
+	s := NewStore(kv)
+
+	if !reflect.DeepEqual([]*registration{}, s.active) || s.idSize != defaultIDSize {
+		t.Errorf("NewStore() failed to return the expected Store."+
+			"\nexpected: %+v\nreceived: %+v", expected, s)
+	}
+
+	obj, err := s.kv.Get(receptionStoreStorageKey, 0)
+	if err != nil {
+		t.Fatalf("Failed to load store from KV: %+v", err)
+	}
+
+	var testStoredReference []storedReference
+	err = json.Unmarshal(obj.Data, &testStoredReference)
+	if err != nil {
+		t.Errorf("Failed to unmarshal []storedReference: %+v", err)
+	}
+	if !reflect.DeepEqual([]storedReference{}, testStoredReference) {
+		t.Errorf("Failed to retreive expected storedReference from KV."+
+			"\nexpected: %#v\nreceived: %#v", []storedReference{}, testStoredReference)
+	}
+}
+
+func TestLoadStore(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+
+	// Fill active registration with fake identities
+	for i := 0; i < 10; i++ {
+		testID, err := generateFakeIdentity(prng, 15, time.Now())
+		if err != nil {
+			t.Fatalf("Failed to generate fake ID: %+v", err)
+		}
+		testID.Ephemeral = false
+		if s.AddIdentity(testID.Identity) != nil {
+			t.Fatalf("Failed to AddIdentity: %+v", err)
+		}
+	}
+
+	err := s.save()
+	if err != nil {
+		t.Errorf("save() produced an error: %+v", err)
+	}
+
+	testStore := LoadStore(kv)
+	for i, active := range testStore.active {
+		s.active[i].ur = nil
+		if !s.active[i].Equal(active.Identity) {
+			t.Errorf("Failed to generate expected Store."+
+				"\nexpected: %#v\nreceived: %#v", s.active[i], active)
+		}
+	}
+}
+
+func TestStore_save(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+
+	// Fill active registration with fake identities
+	for i := 0; i < 10; i++ {
+		testID, err := generateFakeIdentity(prng, 15, time.Now())
+		if err != nil {
+			t.Fatalf("Failed to generate fake ID: %+v", err)
+		}
+		testID.Ephemeral = false
+		s.active = append(s.active, &registration{Identity: testID.Identity})
+	}
+
+	expected := s.makeStoredReferences()
+
+	err := s.save()
+	if err != nil {
+		t.Errorf("save() produced an error: %+v", err)
+	}
+
+	obj, err := kv.Prefix(receptionPrefix).Get(receptionStoreStorageKey, 0)
+	if err != nil {
+		t.Errorf("Get() produced an error: %+v", err)
+	}
+
+	expectedData, err := json.Marshal(expected)
+	if obj.Version != receptionStoreStorageVersion {
+		t.Errorf("Rectrieved version incorrect.\nexpected: %d\nreceived: %d",
+			receptionStoreStorageVersion, obj.Version)
+	}
+
+	if !bytes.Equal(expectedData, obj.Data) {
+		t.Errorf("Rectrieved data incorrect.\nexpected: %s\nreceived: %s",
+			expectedData, obj.Data)
+	}
+}
+
+func TestStore_makeStoredReferences(t *testing.T) {
+	s := NewStore(versioned.NewKV(make(ekv.Memstore)))
+	prng := rand.New(rand.NewSource(42))
+	expected := make([]storedReference, 0)
+
+	// Fill active registration with fake identities
+	for i := 0; i < 10; i++ {
+		testID, err := generateFakeIdentity(prng, 15, time.Now())
+		if err != nil {
+			t.Fatalf("Failed to generate fake ID: %+v", err)
+		}
+		if i%2 == 0 {
+			testID.Ephemeral = false
+			expected = append(expected, storedReference{
+				Eph:        testID.EphId,
+				Source:     testID.Source,
+				StartValid: testID.StartValid.Round(0),
+			})
+		}
+		s.active = append(s.active, &registration{Identity: testID.Identity})
+	}
+
+	sr := s.makeStoredReferences()
+	if !reflect.DeepEqual(expected, sr) {
+		t.Errorf("Failed to generate expected list of identities."+
+			"\nexpected: %+v\nreceived: %+v", expected, sr)
+	}
+}
+
+func TestStore_GetIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+	testID, err := generateFakeIdentity(prng, 15, time.Now())
+	if err != nil {
+		t.Fatalf("Failed to generate fake ID: %+v", err)
+	}
+	if s.AddIdentity(testID.Identity) != nil {
+		t.Errorf("AddIdentity() produced an error: %+v", err)
+	}
+
+	idu, err := s.GetIdentity(prng)
+	if err != nil {
+		t.Errorf("GetIdentity() produced an error: %+v", err)
+	}
+
+	if !testID.Equal(idu.Identity) {
+		t.Errorf("GetIdentity() did not return the expected Identity."+
+			"\nexpected: %s\nreceived: %s", testID.String(), idu.String())
+	}
+}
+
+func TestStore_AddIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+	testID, err := generateFakeIdentity(prng, 15, time.Now())
+	if err != nil {
+		t.Fatalf("Failed to generate fake ID: %+v", err)
+	}
+
+	err = s.AddIdentity(testID.Identity)
+	if err != nil {
+		t.Errorf("AddIdentity() produced an error: %+v", err)
+	}
+
+	if !s.active[0].Identity.Equal(testID.Identity) {
+		t.Errorf("Failed to get expected Identity.\nexpected: %s\nreceived: %s",
+			testID.Identity.String(), s.active[0])
+	}
+}
+
+func TestStore_RemoveIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+	testID, err := generateFakeIdentity(prng, 15, time.Now())
+	if err != nil {
+		t.Fatalf("Failed to generate fake ID: %+v", err)
+	}
+	err = s.AddIdentity(testID.Identity)
+	if err != nil {
+		t.Fatalf("AddIdentity() produced an error: %+v", err)
+	}
+	s.RemoveIdentity(testID.EphId)
+
+	if len(s.active) != 0 {
+		t.Errorf("RemoveIdentity() failed to remove: %+v", s.active)
+	}
+}
+
+func TestStore_UpdateIdSize(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	newSize := s.idSize * 2
+
+	s.UpdateIdSize(uint(newSize))
+
+	if s.idSize != newSize {
+		t.Errorf("UpdateIdSize() failed to update the size."+
+			"\nexpected: %d\nrecieved: %d", newSize, s.idSize)
+	}
+}
+
+func TestStore_prune(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+	runs := 10
+	expected := make([]*registration, runs/2)
+
+	for i := 0; i < runs; i++ {
+		timestamp := time.Now()
+		if i%2 == 0 {
+			timestamp = timestamp.Add(24 * time.Hour)
+		}
+		testID, err := generateFakeIdentity(prng, 15, timestamp)
+		if err != nil {
+			t.Fatalf("Failed to generate fake ID: %+v", err)
+		}
+		err = s.AddIdentity(testID.Identity)
+		if err != nil {
+			t.Fatalf("AddIdentity() produced an error: %+v", err)
+		}
+		if i%2 == 0 {
+			expected[i/2] = s.active[i]
+		}
+	}
+
+	s.prune(time.Now().Add(24 * time.Hour))
+
+	for i, reg := range s.active {
+		if !reg.Equal(expected[i].Identity) {
+			t.Errorf("Unexpected identity (%d).\nexpected: %+v\nreceived: %+v",
+				i, expected[i], reg)
+		}
+	}
+}
+
+func TestStore_selectIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	s := NewStore(kv)
+	prng := rand.New(rand.NewSource(42))
+	runs := 10
+	expectedReg := make([]*registration, runs)
+
+	for i := 0; i < runs; i++ {
+		testID, err := generateFakeIdentity(prng, 15, time.Now())
+		if err != nil {
+			t.Fatalf("Failed to generate fake ID: %+v", err)
+		}
+		err = s.AddIdentity(testID.Identity)
+		if err != nil {
+			t.Fatalf("AddIdentity() produced an error: %+v", err)
+		}
+		expectedReg[i] = s.active[i]
+	}
+
+	for i := 0; i < runs; i++ {
+		idu, err := s.selectIdentity(prng, time.Now())
+		if err != nil {
+			t.Errorf("selectIdentity() produced an error: %+v", err)
+		}
+
+		var found bool
+		for _, expected := range expectedReg {
+			if idu.Equal(expected.Identity) {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Errorf("Unexpected Identity returned.\nreceived: %+v", idu)
+		}
+	}
+}
diff --git a/storage/reception/unknownRound.go b/storage/reception/unknownRound.go
new file mode 100644
index 0000000000000000000000000000000000000000..94feb079bf41ad2b770fa9688cf56267612a4e9a
--- /dev/null
+++ b/storage/reception/unknownRound.go
@@ -0,0 +1,97 @@
+package reception
+
+import (
+	"encoding/json"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+const unknownRoundStorageKey = "unknownRoundStorage"
+const unknownRoundStorageVersion = 0
+
+type UnknownRound struct {
+	stored bool
+	kv     *versioned.KV
+	rid    id.Round
+	mux    sync.Mutex
+}
+
+func NewUnknownRound(stored bool, kv *versioned.KV) *UnknownRound {
+	ur := &UnknownRound{
+		stored: stored,
+		kv:     kv,
+		rid:    0,
+	}
+	ur.save()
+	return ur
+}
+
+func LoadUnknownRound(kv *versioned.KV) *UnknownRound {
+	ur := &UnknownRound{
+		stored: true,
+		kv:     kv,
+		rid:    0,
+	}
+
+	obj, err := kv.Get(unknownRoundStorageKey, unknownRoundStorageVersion)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to get the unknown round: %+v", err)
+	}
+
+	err = json.Unmarshal(obj.Data, &ur.rid)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal the unknown round: %+v", err)
+	}
+	return ur
+}
+
+func (ur *UnknownRound) save() {
+	if ur.stored {
+		urStr, err := json.Marshal(&ur.rid)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to marshal the unknown round: %+v", err)
+		}
+
+		// Create versioned object with data
+		obj := &versioned.Object{
+			Version:   unknownRoundStorageVersion,
+			Timestamp: time.Now(),
+			Data:      urStr,
+		}
+
+		err = ur.kv.Set(unknownRoundStorageKey,
+			unknownRoundStorageVersion, obj)
+		if err != nil {
+			jww.FATAL.Panicf("Failed to store the unknown round: %+v", err)
+		}
+
+	}
+}
+
+func (ur *UnknownRound) Set(rid id.Round) id.Round {
+	ur.mux.Lock()
+	defer ur.mux.Unlock()
+	if rid > ur.rid {
+		ur.rid = rid
+		ur.save()
+	}
+	return ur.rid
+}
+
+func (ur *UnknownRound) Get() id.Round {
+	ur.mux.Lock()
+	defer ur.mux.Unlock()
+	return ur.rid
+}
+
+func (ur *UnknownRound) delete() {
+	ur.mux.Lock()
+	defer ur.mux.Unlock()
+	err := ur.kv.Delete(unknownRoundStorageKey, unknownRoundStorageVersion)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to delete unknownRound storage: %+v", err)
+	}
+}
diff --git a/storage/regCode.go b/storage/regCode.go
new file mode 100644
index 0000000000000000000000000000000000000000..8e9533395ede948ca495b6660e8d9c4a17cdb212
--- /dev/null
+++ b/storage/regCode.go
@@ -0,0 +1,39 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"time"
+)
+
+const regCodeKey = "regCode"
+const regCodeVersion = 0
+
+// SetNDF stores a network definition json file
+func (s *Session) SetRegCode(regCode string) {
+	if err := s.Set(regCodeKey,
+		&versioned.Object{
+			Version:   regCodeVersion,
+			Data:      []byte(regCode),
+			Timestamp: time.Now(),
+		}); err != nil {
+		jww.FATAL.Panicf("Failed to set the registration code: %s", err)
+	}
+}
+
+// Returns the stored network definition json file
+func (s *Session) GetRegCode() (string, error) {
+	regCode, err := s.Get(regCodeKey)
+	if err != nil {
+		return "", errors.WithMessage(err, "Failed to load the regcode")
+	}
+	return string(regCode.Data), nil
+}
diff --git a/storage/regStatus.go b/storage/regStatus.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec18a0cfcb92aaa9c154f23528a40a96f202273f
--- /dev/null
+++ b/storage/regStatus.go
@@ -0,0 +1,124 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+import (
+	"encoding/binary"
+	"fmt"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"time"
+)
+
+const currentRegistrationStatusVersion = 0
+const registrationStatusKey = "regStatusKey"
+
+type RegistrationStatus uint32
+
+const (
+	NotStarted            RegistrationStatus = 0     // Set on session creation
+	KeyGenComplete        RegistrationStatus = 10000 // Set upon generation of session information
+	PermissioningComplete RegistrationStatus = 20000 // Set upon completion of RegisterWithPermissioning
+	UDBComplete           RegistrationStatus = 30000 // Set upon completion of RegisterWithUdb
+)
+
+// stringer for Identity Status
+func (rs RegistrationStatus) String() string {
+	switch rs {
+	case NotStarted:
+		return "Not Started"
+	case KeyGenComplete:
+		return "Key Generation Complete"
+	case PermissioningComplete:
+		return "Permissioning Identity Complete"
+	case UDBComplete:
+		return "User Discovery Identity Complete"
+	default:
+		return fmt.Sprintf("Unknown registration state %v", uint32(rs))
+	}
+}
+
+// creates a registration status from binary data
+func regStatusUnmarshalBinary(b []byte) RegistrationStatus {
+	return RegistrationStatus(binary.BigEndian.Uint32(b))
+}
+
+// returns the binary representation of the registration status
+func (rs RegistrationStatus) marshalBinary() []byte {
+	b := make([]byte, 8)
+	binary.BigEndian.PutUint32(b, uint32(rs))
+	return b
+}
+
+// creates a new registration status and stores it
+func (s *Session) newRegStatus() error {
+	s.regStatus = NotStarted
+
+	now := time.Now()
+
+	obj := versioned.Object{
+		Version:   currentRegistrationStatusVersion,
+		Timestamp: now,
+		Data:      s.regStatus.marshalBinary(),
+	}
+
+	err := s.Set(registrationStatusKey, &obj)
+	if err != nil {
+		return errors.WithMessagef(err, "Failed to store new "+
+			"registration status")
+	}
+
+	return nil
+}
+
+// loads registration status from disk.
+func (s *Session) loadRegStatus() error {
+	obj, err := s.Get(registrationStatusKey)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to load registration status")
+	}
+	s.regStatus = regStatusUnmarshalBinary(obj.Data)
+	return nil
+}
+
+// sets the registration status to the passed status if it is greater than the
+// current stats, otherwise returns an error
+func (s *Session) ForwardRegistrationStatus(regStatus RegistrationStatus) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if regStatus <= s.regStatus {
+		return errors.Errorf("Cannot set registration status to a "+
+			"status before the current stats: Current: %s, New: %s",
+			s.regStatus, regStatus)
+	}
+
+	now := time.Now()
+
+	obj := versioned.Object{
+		Version:   currentRegistrationStatusVersion,
+		Timestamp: now,
+		Data:      regStatus.marshalBinary(),
+	}
+
+	err := s.Set(registrationStatusKey, &obj)
+	if err != nil {
+		return errors.WithMessagef(err, "Failed to store registration status")
+	}
+
+	s.regStatus = regStatus
+	return nil
+}
+
+// sets the registration status to the passed status if it is greater than the
+// current stats, otherwise returns an error
+func (s *Session) GetRegistrationStatus() RegistrationStatus {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.regStatus
+}
diff --git a/storage/session.go b/storage/session.go
new file mode 100644
index 0000000000000000000000000000000000000000..be1837de7fb32adf9d13549c8f42bfe00683c05f
--- /dev/null
+++ b/storage/session.go
@@ -0,0 +1,367 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Session object definition
+
+package storage
+
+import (
+	"sync"
+	"testing"
+
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	userInterface "gitlab.com/elixxir/client/interfaces/user"
+	"gitlab.com/elixxir/client/storage/auth"
+	"gitlab.com/elixxir/client/storage/clientVersion"
+	"gitlab.com/elixxir/client/storage/cmix"
+	"gitlab.com/elixxir/client/storage/conversation"
+	"gitlab.com/elixxir/client/storage/e2e"
+	"gitlab.com/elixxir/client/storage/partition"
+	"gitlab.com/elixxir/client/storage/reception"
+	"gitlab.com/elixxir/client/storage/user"
+	"gitlab.com/elixxir/client/storage/utility"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/version"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+)
+
+// Number of rounds to store in the CheckedRound buffer
+const CheckRoundsMaxSize = 1000000 / 64
+const currentSessionVersion = 0
+
+// Session object, backed by encrypted filestore
+type Session struct {
+	kv  *versioned.KV
+	mux sync.RWMutex
+
+	//memoized data
+	regStatus RegistrationStatus
+	baseNdf   *ndf.NetworkDefinition
+
+	//sub-stores
+	e2e                 *e2e.Store
+	cmix                *cmix.Store
+	user                *user.User
+	conversations       *conversation.Store
+	partition           *partition.Store
+	auth                *auth.Store
+	criticalMessages    *utility.E2eMessageBuffer
+	criticalRawMessages *utility.CmixMessageBuffer
+	garbledMessages     *utility.MeteredCmixMessageBuffer
+	reception           *reception.Store
+	clientVersion       *clientVersion.Store
+}
+
+// Initialize a new Session object
+func initStore(baseDir, password string) (*Session, error) {
+	fs, err := ekv.NewFilestore(baseDir, password)
+	var s *Session
+	if err != nil {
+		return nil, errors.WithMessage(err,
+			"Failed to create storage session")
+	}
+
+	s = &Session{
+		kv: versioned.NewKV(fs),
+	}
+
+	return s, nil
+}
+
+// Creates new UserData in the session
+func New(baseDir, password string, u userInterface.User, currentVersion version.Version,
+	cmixGrp, e2eGrp *cyclic.Group, rng *fastRNG.StreamGenerator) (*Session, error) {
+
+	s, err := initStore(baseDir, password)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create session")
+	}
+
+	err = s.newRegStatus()
+	if err != nil {
+		return nil, errors.WithMessage(err,
+			"Create new session")
+	}
+
+	s.user, err = user.NewUser(s.kv, u.TransmissionID, u.ReceptionID, u.TransmissionSalt, u.ReceptionSalt, u.TransmissionRSA, u.ReceptionRSA, u.Precanned)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create user")
+	}
+	uid := s.user.GetCryptographicIdentity().GetReceptionID()
+
+	s.cmix, err = cmix.NewStore(cmixGrp, s.kv, u.CmixDhPrivateKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create cmix store")
+	}
+
+	s.e2e, err = e2e.NewStore(e2eGrp, s.kv, u.E2eDhPrivateKey, uid, rng)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create e2e store")
+	}
+
+	s.auth, err = auth.NewStore(s.kv, e2eGrp, []*cyclic.Int{u.E2eDhPrivateKey})
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create auth store")
+	}
+
+	s.garbledMessages, err = utility.NewMeteredCmixMessageBuffer(s.kv, garbledMessagesKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create garbledMessages buffer")
+	}
+
+	s.criticalMessages, err = utility.NewE2eMessageBuffer(s.kv, criticalMessagesKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create e2e critical message buffer")
+	}
+
+	s.criticalRawMessages, err = utility.NewCmixMessageBuffer(s.kv, criticalRawMessagesKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create raw critical message buffer")
+	}
+
+	s.conversations = conversation.NewStore(s.kv)
+	s.partition = partition.New(s.kv)
+
+	s.reception = reception.NewStore(s.kv)
+
+	s.clientVersion, err = clientVersion.NewStore(currentVersion, s.kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to create client version store.")
+	}
+
+	return s, nil
+}
+
+// Loads existing user data into the session
+func Load(baseDir, password string, currentVersion version.Version,
+	rng *fastRNG.StreamGenerator) (*Session, error) {
+
+	s, err := initStore(baseDir, password)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load Session")
+	}
+
+	err = s.loadRegStatus()
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load Session")
+	}
+
+	s.clientVersion, err = clientVersion.LoadStore(s.kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load client version store.")
+	}
+
+	// Determine if the storage needs to be updated to the current version
+	_, _, err = s.clientVersion.CheckUpdateRequired(currentVersion)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load client version store.")
+	}
+
+	s.user, err = user.LoadUser(s.kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load Session")
+	}
+
+	s.cmix, err = cmix.LoadStore(s.kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load Session")
+	}
+
+	uid := s.user.GetCryptographicIdentity().GetReceptionID()
+
+	s.e2e, err = e2e.LoadStore(s.kv, uid, rng)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load Session")
+	}
+
+	s.auth, err = auth.LoadStore(s.kv, s.e2e.GetGroup(),
+		[]*cyclic.Int{s.e2e.GetDHPrivateKey()})
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load auth store")
+	}
+
+	s.criticalMessages, err = utility.LoadE2eMessageBuffer(s.kv, criticalMessagesKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load session")
+	}
+
+	s.criticalRawMessages, err = utility.LoadCmixMessageBuffer(s.kv, criticalRawMessagesKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load raw critical message buffer")
+	}
+
+	s.garbledMessages, err = utility.LoadMeteredCmixMessageBuffer(s.kv, garbledMessagesKey)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load session")
+	}
+
+	s.conversations = conversation.NewStore(s.kv)
+	s.partition = partition.New(s.kv)
+
+	s.reception = reception.LoadStore(s.kv)
+
+	return s, nil
+}
+
+func (s *Session) User() *user.User {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.user
+}
+
+func (s *Session) Cmix() *cmix.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.cmix
+}
+
+func (s *Session) E2e() *e2e.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.e2e
+}
+
+func (s *Session) Auth() *auth.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.auth
+}
+
+func (s *Session) Reception() *reception.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.reception
+}
+
+func (s *Session) GetCriticalMessages() *utility.E2eMessageBuffer {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.criticalMessages
+}
+
+func (s *Session) GetCriticalRawMessages() *utility.CmixMessageBuffer {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.criticalRawMessages
+}
+
+func (s *Session) GetGarbledMessages() *utility.MeteredCmixMessageBuffer {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.garbledMessages
+}
+
+// GetClientVersion returns the version of the client storage.
+func (s *Session) GetClientVersion() version.Version {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.clientVersion.Get()
+}
+
+func (s *Session) Conversations() *conversation.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.conversations
+}
+
+func (s *Session) Partition() *partition.Store {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.partition
+}
+
+// Get an object from the session
+func (s *Session) Get(key string) (*versioned.Object, error) {
+	return s.kv.Get(key, currentSessionVersion)
+}
+
+// Set a value in the session
+func (s *Session) Set(key string, object *versioned.Object) error {
+	return s.kv.Set(key, currentSessionVersion, object)
+}
+
+// delete a value in the session
+func (s *Session) Delete(key string) error {
+	return s.kv.Delete(key, currentSessionVersion)
+}
+
+// Initializes a Session object wrapped around a MemStore object.
+// FOR TESTING ONLY
+func InitTestingSession(i interface{}) *Session {
+	switch i.(type) {
+	case *testing.T, *testing.M, *testing.B, *testing.PB:
+		break
+	default:
+		jww.FATAL.Panicf("InitTestingSession is restricted to testing only. Got %T", i)
+	}
+
+	privKey, _ := rsa.LoadPrivateKeyFromPem([]byte("-----BEGIN PRIVATE KEY-----\nMIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC7Dkb6VXFn4cdp\nU0xh6ji0nTDQUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZr\ntzujFPBRFp9O14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfI\nTVCv8CLE0t1ibiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGes\nkWEFa2VttHqF910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq\n6/OAXCU1JLi3kW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzf\nrarmsGM0LZh6JY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYI\nCqldpt79gaET9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8V\nMKbrCaOkzD5zgnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4S\no9AppDQB41SH3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenP\nel2ApMXp+LVRdDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/u\nSALsU2v9UHBzprdrLSZk2YpozJb+CQIDAQABAoICAARjDFUYpeU6zVNyCauOM7BA\ns4FfQdHReg+zApTfWHosDQ04NIc9CGbM6e5E9IFlb3byORzyevkllf5WuMZVWmF8\nd1YBBeTftKYBn2Gwa42Ql9dl3eD0wQ1gUWBBeEoOVZQ0qskr9ynpr0o6TfciWZ5m\nF50UWmUmvc4ppDKhoNwogNU/pKEwwF3xOv2CW2hB8jyLQnk3gBZlELViX3UiFKni\n/rCfoYYvDFXt+ABCvx/qFNAsQUmerurQ3Ob9igjXRaC34D7F9xQ3CMEesYJEJvc9\nGjvr5DbnKnjx152HS56TKhK8gp6vGHJz17xtWECXD3dIUS/1iG8bqXuhdg2c+2aW\nm3MFpa5jgpAawUWc7c32UnqbKKf+HI7/x8J1yqJyNeU5SySyYSB5qtwTShYzlBW/\nyCYD41edeJcmIp693nUcXzU+UAdtpt0hkXS59WSWlTrB/huWXy6kYXLNocNk9L7g\niyx0cOmkuxREMHAvK0fovXdVyflQtJYC7OjJxkzj2rWO+QtHaOySXUyinkuTb5ev\nxNhs+ROWI/HAIE9buMqXQIpHx6MSgdKOL6P6AEbBan4RAktkYA6y5EtH/7x+9V5E\nQTIz4LrtI6abaKb4GUlZkEsc8pxrkNwCqOAE/aqEMNh91Na1TOj3f0/a6ckGYxYH\npyrvwfP2Ouu6e5FhDcCBAoIBAQDcN8mK99jtrH3q3Q8vZAWFXHsOrVvnJXyHLz9V\n1Rx/7TnMUxvDX1PIVxhuJ/tmHtxrNIXOlps80FCZXGgxfET/YFrbf4H/BaMNJZNP\nag1wBV5VQSnTPdTR+Ijice+/ak37S2NKHt8+ut6yoZjD7sf28qiO8bzNua/OYHkk\nV+RkRkk68Uk2tFMluQOSyEjdsrDNGbESvT+R1Eotupr0Vy/9JRY/TFMc4MwJwOoy\ns7wYr9SUCq/cYn7FIOBTI+PRaTx1WtpfkaErDc5O+nLLEp1yOrfktl4LhU/r61i7\nfdtafUACTKrXG2qxTd3w++mHwTwVl2MwhiMZfxvKDkx0L2gxAoIBAQDZcxKwyZOy\ns6Aw7igw1ftLny/dpjPaG0p6myaNpeJISjTOU7HKwLXmlTGLKAbeRFJpOHTTs63y\ngcmcuE+vGCpdBHQkaCev8cve1urpJRcxurura6+bYaENO6ua5VzF9BQlDYve0YwY\nlbJiRKmEWEAyULjbIebZW41Z4UqVG3MQI750PRWPW4WJ2kDhksFXN1gwSnaM46KR\nPmVA0SL+RCPcAp/VkImCv0eqv9exsglY0K/QiJfLy3zZ8QvAn0wYgZ3AvH3lr9rJ\nT7pg9WDb+OkfeEQ7INubqSthhaqCLd4zwbMRlpyvg1cMSq0zRvrFpwVlSY85lW4F\ng/tgjJ99W9VZAoIBAH3OYRVDAmrFYCoMn+AzA/RsIOEBqL8kaz/Pfh9K4D01CQ/x\naqryiqqpFwvXS4fLmaClIMwkvgq/90ulvuCGXeSG52D+NwW58qxQCxgTPhoA9yM9\nVueXKz3I/mpfLNftox8sskxl1qO/nfnu15cXkqVBe4ouD+53ZjhAZPSeQZwHi05h\nCbJ20gl66M+yG+6LZvXE96P8+ZQV80qskFmGdaPozAzdTZ3xzp7D1wegJpTz3j20\n3ULKAiIb5guZNU0tEZz5ikeOqsQt3u6/pVTeDZR0dxnyFUf/oOjmSorSG75WT3sA\n0ZiR0SH5mhFR2Nf1TJ4JHmFaQDMQqo+EG6lEbAECggEAA7kGnuQ0lSCiI3RQV9Wy\nAa9uAFtyE8/XzJWPaWlnoFk04jtoldIKyzHOsVU0GOYOiyKeTWmMFtTGANre8l51\nizYiTuVBmK+JD/2Z8/fgl8dcoyiqzvwy56kX3QUEO5dcKO48cMohneIiNbB7PnrM\nTpA3OfkwnJQGrX0/66GWrLYP8qmBDv1AIgYMilAa40VdSyZbNTpIdDgfP6bU9Ily\nG7gnyF47HHPt5Cx4ouArbMvV1rof7ytCrfCEhP21Lc46Ryxy81W5ZyzoQfSxfdKb\nGyDR+jkryVRyG69QJf5nCXfNewWbFR4ohVtZ78DNVkjvvLYvr4qxYYLK8PI3YMwL\nsQKCAQB9lo7JadzKVio+C18EfNikOzoriQOaIYowNaaGDw3/9KwIhRsKgoTs+K5O\ngt/gUoPRGd3M2z4hn5j4wgeuFi7HC1MdMWwvgat93h7R1YxiyaOoCTxH1klbB/3K\n4fskdQRxuM8McUebebrp0qT5E0xs2l+ABmt30Dtd3iRrQ5BBjnRc4V//sQiwS1aC\nYi5eNYCQ96BSAEo1dxJh5RI/QxF2HEPUuoPM8iXrIJhyg9TEEpbrEJcxeagWk02y\nOMEoUbWbX07OzFVvu+aJaN/GlgiogMQhb6IiNTyMlryFUleF+9OBA8xGHqGWA6nR\nOaRA5ZbdE7g7vxKRV36jT3wvD7W+\n-----END PRIVATE KEY-----\n"))
+	store := make(ekv.Memstore)
+	kv := versioned.NewKV(store)
+	s := &Session{kv: kv}
+	uid := id.NewIdFromString("zezima", id.User, i)
+	u, err := user.NewUser(kv, uid, uid, []byte("salt"), []byte("salt"), privKey, privKey, false)
+	if err != nil {
+		jww.FATAL.Panicf("InitTestingSession failed to create dummy user: %+v", err)
+	}
+	u.SetTransmissionRegistrationValidationSignature([]byte("sig"))
+	u.SetReceptionRegistrationValidationSignature([]byte("sig"))
+	s.user = u
+	cmixGrp := cyclic.NewGroup(
+		large.NewIntFromString("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48"+
+			"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F"+
+			"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5"+
+			"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2"+
+			"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41"+
+			"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE"+
+			"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15"+
+			"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", 16),
+		large.NewIntFromString("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613"+
+			"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4"+
+			"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472"+
+			"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5"+
+			"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA"+
+			"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
+			"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
+			"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
+	cmixStore, err := cmix.NewStore(cmixGrp, kv, cmixGrp.NewInt(2))
+	if err != nil {
+		jww.FATAL.Panicf("InitTestingSession failed to create dummy cmix session: %+v", err)
+	}
+	s.cmix = cmixStore
+
+	e2eStore, err := e2e.NewStore(cmixGrp, kv, cmixGrp.NewInt(2), uid,
+		fastRNG.NewStreamGenerator(7, 3, csprng.NewSystemRNG))
+	if err != nil {
+		jww.FATAL.Panicf("InitTestingSession failed to create dummy cmix session: %+v", err)
+	}
+	s.e2e = e2eStore
+
+	s.criticalMessages, err = utility.NewE2eMessageBuffer(s.kv, criticalMessagesKey)
+	if err != nil {
+		jww.FATAL.Panicf("InitTestingSession failed to create dummy critical messages: %+v", err)
+	}
+
+	s.garbledMessages, err = utility.NewMeteredCmixMessageBuffer(s.kv, garbledMessagesKey)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to create garbledMessages buffer: %+v", err)
+	}
+
+	s.conversations = conversation.NewStore(s.kv)
+	s.partition = partition.New(s.kv)
+
+	s.reception = reception.NewStore(s.kv)
+	return s
+}
diff --git a/storage/session_test.go b/storage/session_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7b0d5ebc78c3f888d800d0506169f65a64c289f4
--- /dev/null
+++ b/storage/session_test.go
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+/*
+func initTest(t *testing.T) *Session {
+	err := os.RemoveAll(".session_testdir")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+	s, err := New(".session_testdir", "test")
+	if err != nil {
+		t.Log(s)
+		t.Errorf("failed to init: %+v", err)
+	}
+	return s
+}
+
+// Smoke test for session object init/set/get methods
+func TestSession_Smoke(t *testing.T) {
+	s := initTest(t)
+
+	err := s.Set("testkey", &versioned.Object{
+		Version:   0,
+		Timestamp: time.Now(),
+		Data:      []byte("test"),
+	})
+	if err != nil {
+		t.Errorf("Failed to set: %+v", err)
+	}
+	o, err := s.Get("testkey")
+	if err != nil {
+		t.Errorf("Failed to get key")
+	}
+	if o == nil {
+		t.Errorf("Got nil return from get")
+	}
+
+	if bytes.Compare(o.Data, []byte("test")) != 0 {
+		t.Errorf("Failed to get data")
+	}
+}*/
diff --git a/storage/user.go b/storage/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..df1d9055e3490a3290d9ad378b3857bc16ca7cc4
--- /dev/null
+++ b/storage/user.go
@@ -0,0 +1,36 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+import "gitlab.com/elixxir/client/interfaces/user"
+
+func (s *Session) GetUser() user.User {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	ci := s.user.GetCryptographicIdentity()
+	return user.User{
+		TransmissionID:   ci.GetTransmissionID().DeepCopy(),
+		TransmissionSalt: copySlice(ci.GetTransmissionSalt()),
+		TransmissionRSA:  ci.GetReceptionRSA(),
+		ReceptionID:      ci.GetReceptionID().DeepCopy(),
+		ReceptionSalt:    copySlice(ci.GetReceptionSalt()),
+		ReceptionRSA:     ci.GetReceptionRSA(),
+		Precanned:        ci.IsPrecanned(),
+		CmixDhPrivateKey: s.cmix.GetDHPrivateKey().DeepCopy(),
+		CmixDhPublicKey:  s.cmix.GetDHPublicKey().DeepCopy(),
+		E2eDhPrivateKey:  s.e2e.GetDHPrivateKey().DeepCopy(),
+		E2eDhPublicKey:   s.e2e.GetDHPublicKey().DeepCopy(),
+	}
+
+}
+
+func copySlice(s []byte) []byte {
+	n := make([]byte, len(s))
+	copy(n, s)
+	return n
+}
diff --git a/storage/user/cryptographic.go b/storage/user/cryptographic.go
new file mode 100644
index 0000000000000000000000000000000000000000..c01b6ffa1bc1e835f2cae169f678de566b572778
--- /dev/null
+++ b/storage/user/cryptographic.go
@@ -0,0 +1,149 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"bytes"
+	"encoding/gob"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const currentCryptographicIdentityVersion = 0
+const cryptographicIdentityKey = "cryptographicIdentity"
+
+type CryptographicIdentity struct {
+	transmissionID     *id.ID
+	transmissionSalt   []byte
+	transmissionRsaKey *rsa.PrivateKey
+	receptionID        *id.ID
+	receptionSalt      []byte
+	receptionRsaKey    *rsa.PrivateKey
+	isPrecanned        bool
+}
+
+type ciDisk struct {
+	TransmissionID     *id.ID
+	TransmissionSalt   []byte
+	TransmissionRsaKey *rsa.PrivateKey
+	ReceptionID        *id.ID
+	ReceptionSalt      []byte
+	ReceptionRsaKey    *rsa.PrivateKey
+	IsPrecanned        bool
+}
+
+func newCryptographicIdentity(transmissionID, receptionID *id.ID, transmissionSalt, receptionSalt []byte, transmissionRsa, receptionRsa *rsa.PrivateKey,
+	isPrecanned bool, kv *versioned.KV) *CryptographicIdentity {
+
+	ci := &CryptographicIdentity{
+		transmissionID:     transmissionID,
+		transmissionSalt:   transmissionSalt,
+		transmissionRsaKey: transmissionRsa,
+		receptionID:        receptionID,
+		receptionSalt:      receptionSalt,
+		receptionRsaKey:    receptionRsa,
+		isPrecanned:        isPrecanned,
+	}
+
+	if err := ci.save(kv); err != nil {
+		jww.FATAL.Panicf("Failed to store the new Cryptographic"+
+			" Identity: %s", err)
+	}
+
+	return ci
+}
+
+func loadCryptographicIdentity(kv *versioned.KV) (*CryptographicIdentity, error) {
+	obj, err := kv.Get(cryptographicIdentityKey,
+		currentCryptographicIdentityVersion)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to get user "+
+			"cryptographic identity from EKV")
+	}
+
+	var resultBuffer bytes.Buffer
+	result := &CryptographicIdentity{}
+	decodable := &ciDisk{}
+
+	resultBuffer.Write(obj.Data)
+	dec := gob.NewDecoder(&resultBuffer)
+	err = dec.Decode(decodable)
+
+	if decodable != nil {
+		result.isPrecanned = decodable.IsPrecanned
+		result.receptionRsaKey = decodable.ReceptionRsaKey
+		result.transmissionRsaKey = decodable.TransmissionRsaKey
+		result.transmissionSalt = decodable.TransmissionSalt
+		result.transmissionID = decodable.TransmissionID
+		result.receptionID = decodable.ReceptionID
+		result.receptionSalt = decodable.ReceptionSalt
+	}
+
+	return result, err
+}
+
+func (ci *CryptographicIdentity) save(kv *versioned.KV) error {
+	var userDataBuffer bytes.Buffer
+
+	encodable := &ciDisk{
+		TransmissionID:     ci.transmissionID,
+		TransmissionSalt:   ci.transmissionSalt,
+		TransmissionRsaKey: ci.transmissionRsaKey,
+		ReceptionID:        ci.receptionID,
+		ReceptionSalt:      ci.receptionSalt,
+		ReceptionRsaKey:    ci.receptionRsaKey,
+		IsPrecanned:        ci.isPrecanned,
+	}
+
+	enc := gob.NewEncoder(&userDataBuffer)
+	err := enc.Encode(encodable)
+	if err != nil {
+		return err
+	}
+
+	obj := &versioned.Object{
+		Version:   currentCryptographicIdentityVersion,
+		Timestamp: time.Now(),
+		Data:      userDataBuffer.Bytes(),
+	}
+
+	return kv.Set(cryptographicIdentityKey,
+		currentCryptographicIdentityVersion, obj)
+}
+
+func (ci *CryptographicIdentity) GetTransmissionID() *id.ID {
+	return ci.transmissionID.DeepCopy()
+}
+
+func (ci *CryptographicIdentity) GetTransmissionSalt() []byte {
+	return ci.transmissionSalt
+}
+
+func (ci *CryptographicIdentity) GetReceptionID() *id.ID {
+	return ci.receptionID.DeepCopy()
+}
+
+func (ci *CryptographicIdentity) GetReceptionSalt() []byte {
+	return ci.receptionSalt
+}
+
+func (ci *CryptographicIdentity) GetReceptionRSA() *rsa.PrivateKey {
+	return ci.receptionRsaKey
+}
+
+func (ci *CryptographicIdentity) GetTransmissionRSA() *rsa.PrivateKey {
+	return ci.transmissionRsaKey
+}
+
+func (ci *CryptographicIdentity) IsPrecanned() bool {
+	return ci.isPrecanned
+}
diff --git a/storage/user/cryptographic_test.go b/storage/user/cryptographic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d2fab2084c773f83053760fccb8dcb5ca794b2d
--- /dev/null
+++ b/storage/user/cryptographic_test.go
@@ -0,0 +1,149 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"bytes"
+	"crypto/rand"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+// Test for NewCryptographicIdentity function
+func TestNewCryptographicIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	salt := []byte("salt")
+	_ = newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+
+	_, err := kv.Get(cryptographicIdentityKey, 0)
+	if err != nil {
+		t.Errorf("Did not store cryptographic identity")
+	}
+}
+
+// Test loading cryptographic identity from KV store
+func TestLoadCryptographicIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+
+	err := ci.save(kv)
+	if err != nil {
+		t.Errorf("Did not store cryptographic identity: %+v", err)
+	}
+
+	newCi, err := loadCryptographicIdentity(kv)
+	if err != nil {
+		t.Errorf("Failed to load cryptographic identity: %+v", err)
+	}
+	if !ci.transmissionID.Cmp(newCi.transmissionID) {
+		t.Errorf("Did not load expected ci.  Expected: %+v, Received: %+v", ci.transmissionID, newCi.transmissionID)
+	}
+}
+
+// Happy path for GetReceptionRSA function
+func TestCryptographicIdentity_GetReceptionRSA(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	pk1, err := rsa.GenerateKey(rand.Reader, 64)
+	if err != nil {
+		t.Errorf("Failed to generate pk1")
+	}
+	pk2, err := rsa.GenerateKey(rand.Reader, 64)
+	if err != nil {
+		t.Errorf("Failed to generate pk2")
+	}
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(uid, uid, salt, salt, pk1, pk2, false, kv)
+	if ci.GetReceptionRSA().D != pk2.D {
+		t.Errorf("Did not receive expected RSA key.  Expected: %+v, Received: %+v", pk2, ci.GetReceptionRSA())
+	}
+}
+
+// Happy path for GetTransmissionRSA function
+func TestCryptographicIdentity_GetTransmissionRSA(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	pk1, err := rsa.GenerateKey(rand.Reader, 64)
+	if err != nil {
+		t.Errorf("Failed to generate pk1")
+	}
+	pk2, err := rsa.GenerateKey(rand.Reader, 64)
+	if err != nil {
+		t.Errorf("Failed to generate pk2")
+	}
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(uid, uid, salt, salt, pk1, pk2, false, kv)
+	if ci.GetTransmissionRSA().D != pk1.D {
+		t.Errorf("Did not receive expected RSA key.  Expected: %+v, Received: %+v", pk1, ci.GetTransmissionRSA())
+	}
+}
+
+// Happy path for GetSalt function
+func TestCryptographicIdentity_GetTransmissionSalt(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	ts := []byte("transmission salt")
+	rs := []byte("reception salt")
+	ci := newCryptographicIdentity(uid, uid, ts, rs, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+	if bytes.Compare(ci.GetTransmissionSalt(), ts) != 0 {
+		t.Errorf("Did not get expected salt.  Expected: %+v, Received: %+v", ts, ci.GetTransmissionSalt())
+	}
+}
+
+// Happy path for GetSalt function
+func TestCryptographicIdentity_GetReceptionSalt(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	ts := []byte("transmission salt")
+	rs := []byte("reception salt")
+	ci := newCryptographicIdentity(uid, uid, ts, rs, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+	if bytes.Compare(ci.GetReceptionSalt(), rs) != 0 {
+		t.Errorf("Did not get expected salt.  Expected: %+v, Received: %+v", rs, ci.GetReceptionSalt())
+	}
+}
+
+// Happy path for GetUserID function
+func TestCryptographicIdentity_GetTransmissionID(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	rid := id.NewIdFromString("zezima", id.User, t)
+	tid := id.NewIdFromString("jakexx360", id.User, t)
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(tid, rid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+	if !ci.GetTransmissionID().Cmp(tid) {
+		t.Errorf("Did not receive expected user ID.  Expected: %+v, Received: %+v", tid, ci.GetTransmissionID())
+	}
+}
+
+// Happy path for GetUserID function
+func TestCryptographicIdentity_GetReceptionID(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	rid := id.NewIdFromString("zezima", id.User, t)
+	tid := id.NewIdFromString("jakexx360", id.User, t)
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(tid, rid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+	if !ci.GetReceptionID().Cmp(rid) {
+		t.Errorf("Did not receive expected user ID.  Expected: %+v, Received: %+v", rid, ci.GetReceptionID())
+	}
+}
+
+// Happy path for IsPrecanned functions
+func TestCryptographicIdentity_IsPrecanned(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("zezima", id.User, t)
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, true, kv)
+	if !ci.IsPrecanned() {
+		t.Error("I really don't know how this could happen")
+	}
+}
diff --git a/storage/user/regValidationSig.go b/storage/user/regValidationSig.go
new file mode 100644
index 0000000000000000000000000000000000000000..4b5e09957e1597c899628fcecc58b0913afb575a
--- /dev/null
+++ b/storage/user/regValidationSig.go
@@ -0,0 +1,110 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"time"
+)
+
+const currentRegValidationSigVersion = 0
+const transmissionRegValidationSigKey = "transmissionRegistrationValidationSignature"
+const receptionRegValidationSigKey = "receptionRegistrationValidationSignature"
+
+// Returns the transmission Identity Validation Signature stored in RAM. May return
+// nil of no signature is stored
+func (u *User) GetTransmissionRegistrationValidationSignature() []byte {
+	u.rvsMux.RLock()
+	defer u.rvsMux.RUnlock()
+	return u.transmissionRegValidationSig
+}
+
+// Returns the reception Identity Validation Signature stored in RAM. May return
+// nil of no signature is stored
+func (u *User) GetReceptionRegistrationValidationSignature() []byte {
+	u.rvsMux.RLock()
+	defer u.rvsMux.RUnlock()
+	return u.receptionRegValidationSig
+}
+
+// Loads the transmission Identity Validation Signature if it exists in the ekv
+func (u *User) loadTransmissionRegistrationValidationSignature() {
+	u.rvsMux.Lock()
+	obj, err := u.kv.Get(transmissionRegValidationSigKey,
+		currentRegValidationSigVersion)
+	if err == nil {
+		u.transmissionRegValidationSig = obj.Data
+	}
+	u.rvsMux.Unlock()
+}
+
+// Loads the reception Identity Validation Signature if it exists in the ekv
+func (u *User) loadReceptionRegistrationValidationSignature() {
+	u.rvsMux.Lock()
+	obj, err := u.kv.Get(receptionRegValidationSigKey,
+		currentRegValidationSigVersion)
+	if err == nil {
+		u.receptionRegValidationSig = obj.Data
+	}
+	u.rvsMux.Unlock()
+}
+
+// Sets the Identity Validation Signature if it is not set and stores it in
+// the ekv
+func (u *User) SetTransmissionRegistrationValidationSignature(b []byte) {
+	u.rvsMux.Lock()
+	defer u.rvsMux.Unlock()
+
+	//check if the signature already exists
+	if u.transmissionRegValidationSig != nil {
+		jww.FATAL.Panicf("cannot overwrite existing transmission Identity Validation Signature")
+	}
+
+	obj := &versioned.Object{
+		Version:   currentRegValidationSigVersion,
+		Timestamp: time.Now(),
+		Data:      b,
+	}
+
+	err := u.kv.Set(transmissionRegValidationSigKey,
+		currentRegValidationSigVersion, obj)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to store the transmission Identity Validation "+
+			"Signature: %s", err)
+	}
+
+	u.transmissionRegValidationSig = b
+}
+
+// Sets the Identity Validation Signature if it is not set and stores it in
+// the ekv
+func (u *User) SetReceptionRegistrationValidationSignature(b []byte) {
+	u.rvsMux.Lock()
+	defer u.rvsMux.Unlock()
+
+	//check if the signature already exists
+	if u.receptionRegValidationSig != nil {
+		jww.FATAL.Panicf("cannot overwrite existing reception Identity Validation Signature")
+	}
+
+	obj := &versioned.Object{
+		Version:   currentRegValidationSigVersion,
+		Timestamp: time.Now(),
+		Data:      b,
+	}
+
+	err := u.kv.Set(receptionRegValidationSigKey,
+		currentRegValidationSigVersion, obj)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to store the reception Identity Validation "+
+			"Signature: %s", err)
+	}
+
+	u.receptionRegValidationSig = b
+}
diff --git a/storage/user/regValidationSig_test.go b/storage/user/regValidationSig_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c824ad8891075f71a7d9bbd5e90ab1ac91bb1263
--- /dev/null
+++ b/storage/user/regValidationSig_test.go
@@ -0,0 +1,139 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+// Test User GetRegistrationValidationSignature function
+func TestUser_GetRegistrationValidationSignature(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("test", id.User, t)
+	salt := []byte("salt")
+	u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	sig := []byte("testreceptionsignature")
+	u.SetReceptionRegistrationValidationSignature(sig)
+	if bytes.Compare(sig, u.receptionRegValidationSig) != 0 {
+		t.Errorf("Failed to set user object signature field.  Expected: %+v, Received: %+v",
+			sig, u.receptionRegValidationSig)
+	}
+
+	if bytes.Compare(u.GetReceptionRegistrationValidationSignature(), sig) != 0 {
+		t.Errorf("Did not receive expected result from GetRegistrationValidationSignature.  "+
+			"Expected: %+v, Received: %+v", sig, u.GetReceptionRegistrationValidationSignature())
+	}
+
+	sig = []byte("testtransmissionsignature")
+	u.SetTransmissionRegistrationValidationSignature(sig)
+	if bytes.Compare(sig, u.transmissionRegValidationSig) != 0 {
+		t.Errorf("Failed to set user object signature field.  Expected: %+v, Received: %+v",
+			sig, u.transmissionRegValidationSig)
+	}
+
+	if bytes.Compare(u.GetTransmissionRegistrationValidationSignature(), sig) != 0 {
+		t.Errorf("Did not receive expected result from GetRegistrationValidationSignature.  "+
+			"Expected: %+v, Received: %+v", sig, u.GetTransmissionRegistrationValidationSignature())
+	}
+}
+
+// Test SetRegistrationValidationSignature setter
+func TestUser_SetRegistrationValidationSignature(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("test", id.User, t)
+	salt := []byte("salt")
+	u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	sig := []byte("testtransmissionsignature")
+	u.SetTransmissionRegistrationValidationSignature(sig)
+	if bytes.Compare(sig, u.transmissionRegValidationSig) != 0 {
+		t.Errorf("Failed to set user object signature field.  Expected: %+v, Received: %+v",
+			sig, u.transmissionRegValidationSig)
+	}
+
+	obj, err := u.kv.Get(transmissionRegValidationSigKey, 0)
+	if err != nil {
+		t.Errorf("Failed to get reg vaildation signature key: %+v", err)
+	}
+	if bytes.Compare(obj.Data, sig) != 0 {
+		t.Errorf("Did not properly set reg validation signature key in kv store.\nExpected: %+v, Received: %+v",
+			sig, obj.Data)
+	}
+
+	sig = []byte("testreceptionsignature")
+	u.SetReceptionRegistrationValidationSignature(sig)
+	if bytes.Compare(sig, u.receptionRegValidationSig) != 0 {
+		t.Errorf("Failed to set user object signature field.  Expected: %+v, Received: %+v",
+			sig, u.receptionRegValidationSig)
+	}
+
+	obj, err = u.kv.Get(receptionRegValidationSigKey, 0)
+	if err != nil {
+		t.Errorf("Failed to get reg vaildation signature key: %+v", err)
+	}
+	if bytes.Compare(obj.Data, sig) != 0 {
+		t.Errorf("Did not properly set reg validation signature key in kv store.\nExpected: %+v, Received: %+v",
+			sig, obj.Data)
+	}
+}
+
+// Test loading registrationValidationSignature from the KV store
+func TestUser_loadRegistrationValidationSignature(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("test", id.User, t)
+	salt := []byte("salt")
+	u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	sig := []byte("transmissionsignature")
+	err = kv.Set(transmissionRegValidationSigKey,
+		currentRegValidationSigVersion, &versioned.Object{
+			Version:   currentRegValidationSigVersion,
+			Timestamp: time.Now(),
+			Data:      sig,
+		})
+	if err != nil {
+		t.Errorf("Failed to set reg validation sig key in kv store: %+v", err)
+	}
+
+	u.loadTransmissionRegistrationValidationSignature()
+	if bytes.Compare(u.transmissionRegValidationSig, sig) != 0 {
+		t.Errorf("Expected sig did not match loaded.  Expected: %+v, Received: %+v", sig, u.transmissionRegValidationSig)
+	}
+
+	sig = []byte("receptionsignature")
+	err = kv.Set(receptionRegValidationSigKey,
+		currentRegValidationSigVersion, &versioned.Object{
+			Version:   currentRegValidationSigVersion,
+			Timestamp: time.Now(),
+			Data:      sig,
+		})
+	if err != nil {
+		t.Errorf("Failed to set reg validation sig key in kv store: %+v", err)
+	}
+
+	u.loadReceptionRegistrationValidationSignature()
+	if bytes.Compare(u.receptionRegValidationSig, sig) != 0 {
+		t.Errorf("Expected sig did not match loaded.  Expected: %+v, Received: %+v", sig, u.receptionRegValidationSig)
+	}
+}
diff --git a/storage/user/user.go b/storage/user/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..a55830ff131620f7f9f499b2a6932d616675ccda
--- /dev/null
+++ b/storage/user/user.go
@@ -0,0 +1,57 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+)
+
+type User struct {
+	ci *CryptographicIdentity
+
+	transmissionRegValidationSig []byte
+	receptionRegValidationSig    []byte
+	rvsMux                       sync.RWMutex
+
+	username    string
+	usernameMux sync.RWMutex
+
+	kv *versioned.KV
+}
+
+// builds a new user.
+func NewUser(kv *versioned.KV, transmissionID, receptionID *id.ID, transmissionSalt,
+	receptionSalt []byte, transmissionRsa, receptionRsa *rsa.PrivateKey, isPrecanned bool) (*User, error) {
+
+	ci := newCryptographicIdentity(transmissionID, receptionID, transmissionSalt, receptionSalt, transmissionRsa, receptionRsa, isPrecanned, kv)
+
+	return &User{ci: ci, kv: kv}, nil
+}
+
+func LoadUser(kv *versioned.KV) (*User, error) {
+	ci, err := loadCryptographicIdentity(kv)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to load user "+
+			"due to failure to load cryptographic identity")
+	}
+
+	u := &User{ci: ci, kv: kv}
+	u.loadTransmissionRegistrationValidationSignature()
+	u.loadReceptionRegistrationValidationSignature()
+	u.loadUsername()
+
+	return u, nil
+}
+
+func (u *User) GetCryptographicIdentity() *CryptographicIdentity {
+	return u.ci
+}
diff --git a/storage/user/user_test.go b/storage/user/user_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0fd3d8d1819163dcadac0d7bed29f183f45acd32
--- /dev/null
+++ b/storage/user/user_test.go
@@ -0,0 +1,68 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+// Test loading user from a KV store
+func TestLoadUser(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	_, err := LoadUser(kv)
+
+	if err == nil {
+		t.Errorf("Should have failed to load identity from empty kv")
+	}
+
+	uid := id.NewIdFromString("test", id.User, t)
+	salt := []byte("salt")
+	ci := newCryptographicIdentity(uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false, kv)
+	err = ci.save(kv)
+	if err != nil {
+		t.Errorf("Failed to save ci to kv: %+v", err)
+	}
+
+	_, err = LoadUser(kv)
+	if err != nil {
+		t.Errorf("Failed to load user: %+v", err)
+	}
+}
+
+// Test NewUser function
+func TestNewUser(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("test", id.User, t)
+	salt := []byte("salt")
+	u, err := NewUser(kv, uid, uid, salt, salt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+}
+
+// Test GetCryptographicIdentity function from user
+func TestUser_GetCryptographicIdentity(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	uid := id.NewIdFromString("test", id.User, t)
+	rsalt := []byte("reception salt")
+	tsalt := []byte("transmission salt")
+	u, err := NewUser(kv, uid, uid, tsalt, rsalt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	ci := u.GetCryptographicIdentity()
+	if bytes.Compare(ci.transmissionSalt, tsalt) != 0 {
+		t.Errorf("Cryptographic Identity not retrieved properly")
+	}
+}
diff --git a/storage/user/username.go b/storage/user/username.go
new file mode 100644
index 0000000000000000000000000000000000000000..268025e2ea728645a3124165c0cd30f368a93942
--- /dev/null
+++ b/storage/user/username.go
@@ -0,0 +1,59 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"time"
+)
+
+const currentUsernameVersion = 0
+const usernameKey = "username"
+
+func (u *User) loadUsername() {
+	u.usernameMux.Lock()
+	obj, err := u.kv.Get(usernameKey, currentUsernameVersion)
+	if err == nil {
+		u.username = string(obj.Data)
+	}
+	u.usernameMux.Unlock()
+}
+
+func (u *User) SetUsername(username string) error {
+	u.usernameMux.Lock()
+	defer u.usernameMux.Unlock()
+	if u.username != "" {
+		return errors.New("Cannot set username when already set")
+	}
+
+	obj := &versioned.Object{
+		Version:   currentUsernameVersion,
+		Timestamp: time.Now(),
+		Data:      []byte(username),
+	}
+
+	err := u.kv.Set(usernameKey, currentUsernameVersion, obj)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to store the username: %s", err)
+	}
+
+	u.username = username
+
+	return nil
+}
+
+func (u *User) GetUsername() (string, error) {
+	u.usernameMux.RLock()
+	defer u.usernameMux.RUnlock()
+	if u.username == "" {
+		return "", errors.New("no username set")
+	}
+	return u.username, nil
+}
diff --git a/storage/user/username_test.go b/storage/user/username_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1da1ce0eb9dc96a8d85b965bbdb867bdc36846fe
--- /dev/null
+++ b/storage/user/username_test.go
@@ -0,0 +1,104 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package user
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+// Test normal function and errors for User's SetUsername function
+func TestUser_SetUsername(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	tid := id.NewIdFromString("trans", id.User, t)
+	rid := id.NewIdFromString("recv", id.User, t)
+	tsalt := []byte("tsalt")
+	rsalt := []byte("rsalt")
+	u, err := NewUser(kv, tid, rid, tsalt, rsalt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	u1 := "zezima"
+	u2 := "dunkey"
+	err = u.SetUsername(u1)
+	if err != nil {
+		t.Errorf("Failed to set username: %+v", err)
+	}
+
+	err = u.SetUsername(u2)
+	if err == nil {
+		t.Error("Did not error when attempting to set a new username")
+	}
+
+	o, err := u.kv.Get(usernameKey, 0)
+	if err != nil {
+		t.Errorf("Didn't get username from user kv store: %+v", err)
+	}
+
+	if string(o.Data) != u1 {
+		t.Errorf("Expected username was not stored.\nExpected: %s\tReceived: %s", u1, string(o.Data))
+	}
+}
+
+// Test functionality of User's GetUsername function
+func TestUser_GetUsername(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	tid := id.NewIdFromString("trans", id.User, t)
+	rid := id.NewIdFromString("recv", id.User, t)
+	tsalt := []byte("tsalt")
+	rsalt := []byte("rsalt")
+	u, err := NewUser(kv, tid, rid, tsalt, rsalt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	_, err = u.GetUsername()
+	if err == nil {
+		t.Error("GetUsername should return an error if username is not set")
+	}
+
+	u1 := "zezima"
+	u.username = u1
+	username, err := u.GetUsername()
+	if err != nil {
+		t.Errorf("Failed to get username when set: %+v", err)
+	}
+	if username != u1 {
+		t.Errorf("Somehow got the wrong username")
+	}
+}
+
+// Test the loadUsername helper function
+func TestUser_loadUsername(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	tid := id.NewIdFromString("trans", id.User, t)
+	rid := id.NewIdFromString("recv", id.User, t)
+	tsalt := []byte("tsalt")
+	rsalt := []byte("rsalt")
+	u, err := NewUser(kv, tid, rid, tsalt, rsalt, &rsa.PrivateKey{}, &rsa.PrivateKey{}, false)
+	if err != nil || u == nil {
+		t.Errorf("Failed to create new user: %+v", err)
+	}
+
+	u1 := "zezima"
+
+	err = u.kv.Set(usernameKey, currentUsernameVersion, &versioned.Object{
+		Version:   currentUsernameVersion,
+		Timestamp: time.Now(),
+		Data:      []byte(u1),
+	})
+	u.loadUsername()
+	if u.username != u1 {
+		t.Errorf("Username was not properly loaded from kv.\nExpected: %s, Received: %s", u1, u.username)
+	}
+}
diff --git a/storage/utility/NDF.go b/storage/utility/NDF.go
new file mode 100644
index 0000000000000000000000000000000000000000..728ec90c9fcf12db993e25b5566203c98783dbf4
--- /dev/null
+++ b/storage/utility/NDF.go
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/ndf"
+	"time"
+)
+
+const currentNDFVersion = 0
+
+func LoadNDF(kv *versioned.KV, key string) (*ndf.NetworkDefinition, error) {
+	vo, err := kv.Get(key, currentNDFVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	netDef, err := ndf.Unmarshal(vo.Data)
+	if err != nil {
+		return nil, err
+	}
+
+	return netDef, err
+}
+
+func SaveNDF(kv *versioned.KV, key string, ndf *ndf.NetworkDefinition) error {
+	marshaled, err := ndf.Marshal()
+	if err != nil {
+		return err
+	}
+
+	now := time.Now()
+
+	obj := versioned.Object{
+		Version:   currentNDFVersion,
+		Timestamp: now,
+		Data:      marshaled,
+	}
+
+	return kv.Set(key, currentNDFVersion, &obj)
+}
diff --git a/storage/utility/cmixMessageBuffer.go b/storage/utility/cmixMessageBuffer.go
new file mode 100644
index 0000000000000000000000000000000000000000..367d4fd534bad871472590ecf5ecf4fd7c017f95
--- /dev/null
+++ b/storage/utility/cmixMessageBuffer.go
@@ -0,0 +1,157 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"crypto/md5"
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const currentCmixMessageVersion = 0
+
+type cmixMessageHandler struct{}
+
+type storedMessage struct {
+	Msg       []byte
+	Recipient []byte
+}
+
+func (sm storedMessage) Marshal() []byte {
+
+	data, err := json.Marshal(&sm)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to marshal stored message: %s", err)
+	}
+
+	return data
+}
+
+// SaveMessage saves the message as a versioned object at the specified key
+// in the key value store.
+func (cmh *cmixMessageHandler) SaveMessage(kv *versioned.KV, m interface{}, key string) error {
+	sm := m.(storedMessage)
+
+	// Create versioned object
+	obj := versioned.Object{
+		Version:   currentCmixMessageVersion,
+		Timestamp: time.Now(),
+		Data:      sm.Marshal(),
+	}
+
+	// Save versioned object
+	return kv.Set(key, currentCmixMessageVersion, &obj)
+}
+
+// LoadMessage returns the message with the specified key from the key value
+// store. An empty message and error are returned if the message could not be
+// retrieved.
+func (cmh *cmixMessageHandler) LoadMessage(kv *versioned.KV, key string) (interface{}, error) {
+	// Load the versioned object
+	vo, err := kv.Get(key, currentCmixMessageVersion)
+	if err != nil {
+		return format.Message{}, err
+	}
+
+	sm := storedMessage{}
+	if err = json.Unmarshal(vo.Data, &sm); err != nil {
+		return nil, errors.Wrap(err, "Failed to unmarshal stored message")
+	}
+
+	// Create message from data
+	return sm, nil
+}
+
+// DeleteMessage deletes the message with the specified key from the key value
+// store.
+func (cmh *cmixMessageHandler) DeleteMessage(kv *versioned.KV, key string) error {
+	return kv.Delete(key, currentCmixMessageVersion)
+}
+
+// HashMessage generates a hash of the message.
+func (cmh *cmixMessageHandler) HashMessage(m interface{}) MessageHash {
+	sm := m.(storedMessage)
+	return md5.Sum(sm.Marshal())
+}
+
+// CmixMessageBuffer wraps the message buffer to store and load raw cmix
+// messages.
+type CmixMessageBuffer struct {
+	mb *MessageBuffer
+}
+
+func NewCmixMessageBuffer(kv *versioned.KV, key string) (*CmixMessageBuffer, error) {
+	mb, err := NewMessageBuffer(kv, &cmixMessageHandler{}, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &CmixMessageBuffer{mb: mb}, nil
+}
+
+func LoadCmixMessageBuffer(kv *versioned.KV, key string) (*CmixMessageBuffer, error) {
+	mb, err := LoadMessageBuffer(kv, &cmixMessageHandler{}, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &CmixMessageBuffer{mb: mb}, nil
+}
+
+func (cmb *CmixMessageBuffer) Add(msg format.Message, recipent *id.ID) {
+	sm := storedMessage{
+		Msg:       msg.Marshal(),
+		Recipient: recipent.Marshal(),
+	}
+	cmb.mb.Add(sm)
+}
+
+func (cmb *CmixMessageBuffer) AddProcessing(msg format.Message, recipent *id.ID) {
+	sm := storedMessage{
+		Msg:       msg.Marshal(),
+		Recipient: recipent.Marshal(),
+	}
+	cmb.mb.AddProcessing(sm)
+}
+
+func (cmb *CmixMessageBuffer) Next() (format.Message, *id.ID, bool) {
+	m, ok := cmb.mb.Next()
+	if !ok {
+		return format.Message{}, nil, false
+	}
+
+	sm := m.(storedMessage)
+	msg := format.Unmarshal(sm.Msg)
+	recpient, err := id.Unmarshal(sm.Recipient)
+	if err != nil {
+		jww.FATAL.Panicf("Could nto get an id for stored cmix "+
+			"message buffer: %+v", err)
+	}
+	return msg, recpient, true
+}
+
+func (cmb *CmixMessageBuffer) Succeeded(msg format.Message, recipent *id.ID) {
+	sm := storedMessage{
+		Msg:       msg.Marshal(),
+		Recipient: recipent.Marshal(),
+	}
+	cmb.mb.Succeeded(sm)
+}
+
+func (cmb *CmixMessageBuffer) Failed(msg format.Message, recipent *id.ID) {
+	sm := storedMessage{
+		Msg:       msg.Marshal(),
+		Recipient: recipent.Marshal(),
+	}
+	cmb.mb.Failed(sm)
+}
diff --git a/storage/utility/cmixMessageBuffer_test.go b/storage/utility/cmixMessageBuffer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f884d73d78629dbc51e88ce5443dfee8f3bde66e
--- /dev/null
+++ b/storage/utility/cmixMessageBuffer_test.go
@@ -0,0 +1,186 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Test happy path of cmixMessageHandler.SaveMessage().
+func TestCmixMessageHandler_SaveMessage(t *testing.T) {
+	// Set up test values
+	cmh := &cmixMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, ids, _ := makeTestCmixMessages(10)
+
+	for i := range testMsgs {
+		msg := storedMessage{
+			Msg:       testMsgs[i].Marshal(),
+			Recipient: ids[i].Marshal(),
+		}
+		key := makeStoredMessageKey("testKey", cmh.HashMessage(msg))
+
+		// Save message
+		err := cmh.SaveMessage(kv, msg, key)
+		if err != nil {
+			t.Errorf("SaveMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		// Try to get message
+		obj, err := kv.Get(key, 0)
+		if err != nil {
+			t.Errorf("Get() returned an error: %v", err)
+		}
+
+		// Test if message retrieved matches expected
+		if !bytes.Equal(msg.Marshal(), obj.Data) {
+			t.Errorf("SaveMessage() returned versioned object with incorrect data."+
+				"\n\texpected: %v\n\treceived: %v",
+				msg, obj.Data)
+		}
+	}
+}
+
+// Test happy path of cmixMessageHandler.LoadMessage().
+func TestCmixMessageHandler_LoadMessage(t *testing.T) {
+	// Set up test values
+	cmh := &cmixMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, ids, _ := makeTestCmixMessages(10)
+
+	for i := range testMsgs {
+		msg := storedMessage{
+			Msg:       testMsgs[i].Marshal(),
+			Recipient: ids[i].Marshal(),
+		}
+		key := makeStoredMessageKey("testKey", cmh.HashMessage(msg))
+
+		// Save message
+		if err := cmh.SaveMessage(kv, msg, key); err != nil {
+			t.Errorf("SaveMessage() returned an error: %v", err)
+		}
+
+		// Try to load message
+		testMsg, err := cmh.LoadMessage(kv, key)
+		if err != nil {
+			t.Errorf("LoadMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		// Test if message loaded matches expected
+		if !reflect.DeepEqual(msg, testMsg) {
+			t.Errorf("LoadMessage() returned an unexpected object."+
+				"\n\texpected: %v\n\treceived: %v",
+				msg, testMsg)
+		}
+	}
+}
+
+// Smoke test of cmixMessageHandler.
+func TestCmixMessageBuffer_Smoke(t *testing.T) {
+	// Set up test messages
+	testMsgs, ids, _ := makeTestCmixMessages(2)
+
+	// Create new buffer
+	cmb, err := NewCmixMessageBuffer(versioned.NewKV(make(ekv.Memstore)), "testKey")
+	if err != nil {
+		t.Errorf("NewCmixMessageBuffer() returned an error."+
+			"\n\texpected: %v\n\trecieved: %v", nil, err)
+	}
+
+	// Add two messages
+	cmb.Add(testMsgs[0], ids[0])
+	cmb.Add(testMsgs[1], ids[1])
+
+	if len(cmb.mb.messages) != 2 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			2, len(cmb.mb.messages))
+	}
+
+	msg, rid, exists := cmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	cmb.Succeeded(msg, rid)
+
+	if len(cmb.mb.messages) != 1 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			1, len(cmb.mb.messages))
+	}
+
+	msg, rid, exists = cmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	if len(cmb.mb.messages) != 0 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			0, len(cmb.mb.messages))
+	}
+	cmb.Failed(msg, rid)
+
+	if len(cmb.mb.messages) != 1 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			1, len(cmb.mb.messages))
+	}
+
+	msg, rid, exists = cmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	cmb.Succeeded(msg, rid)
+
+	msg, rid, exists = cmb.Next()
+	if exists {
+		t.Error("Next() found a message in the buffer when it should be empty.")
+	}
+
+	if len(cmb.mb.messages) != 0 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			0, len(cmb.mb.messages))
+	}
+
+}
+
+// makeTestCmixMessages creates a list of messages with random data and the
+// expected map after they are added to the buffer.
+func makeTestCmixMessages(n int) ([]format.Message, []*id.ID, map[MessageHash]struct{}) {
+	cmh := &cmixMessageHandler{}
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	mh := map[MessageHash]struct{}{}
+	msgs := make([]format.Message, n)
+	ids := make([]*id.ID, n)
+	for i := range msgs {
+		msgs[i] = format.NewMessage(128)
+		payload := make([]byte, 128)
+		prng.Read(payload)
+		msgs[i].SetPayloadA(payload)
+		prng.Read(payload)
+		msgs[i].SetPayloadB(payload)
+
+		rid := id.ID{}
+		prng.Read(rid[:32])
+		rid[32] = byte(id.User)
+		ids[i] = &rid
+		sm := storedMessage{
+			Msg:       msgs[i].Marshal(),
+			Recipient: ids[i].Marshal(),
+		}
+		mh[cmh.HashMessage(sm)] = struct{}{}
+	}
+
+	return msgs, ids, mh
+}
diff --git a/storage/utility/contact.go b/storage/utility/contact.go
new file mode 100644
index 0000000000000000000000000000000000000000..cef79e2ffc8748798908cfcedd87a3e96c0c0e7e
--- /dev/null
+++ b/storage/utility/contact.go
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"fmt"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+const currentContactVersion = 0
+
+func StoreContact(kv *versioned.KV, c contact.Contact) error {
+	now := time.Now()
+
+	obj := versioned.Object{
+		Version:   currentContactVersion,
+		Timestamp: now,
+		Data:      c.Marshal(),
+	}
+
+	return kv.Set(makeContactKey(c.ID), currentContactVersion, &obj)
+}
+
+func LoadContact(kv *versioned.KV, cid *id.ID) (contact.Contact, error) {
+	vo, err := kv.Get(makeContactKey(cid), currentContactVersion)
+	if err != nil {
+		return contact.Contact{}, err
+	}
+
+	return contact.Unmarshal(vo.Data)
+}
+
+func DeleteContact(kv *versioned.KV, cid *id.ID) error {
+	return kv.Delete(makeContactKey(cid), currentContactVersion)
+}
+
+func makeContactKey(cid *id.ID) string {
+	return fmt.Sprintf("Contact:%s", cid)
+}
diff --git a/storage/utility/dh.go b/storage/utility/dh.go
new file mode 100644
index 0000000000000000000000000000000000000000..ea2f6296d1289378189c4e4d85e6c344b9715aa1
--- /dev/null
+++ b/storage/utility/dh.go
@@ -0,0 +1,44 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"time"
+)
+
+const currentCyclicVersion = 0
+
+func StoreCyclicKey(kv *versioned.KV, cy *cyclic.Int, key string) error {
+	now := time.Now()
+
+	data, err := cy.GobEncode()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentCyclicVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return kv.Set(key, currentCyclicVersion, &obj)
+}
+
+func LoadCyclicKey(kv *versioned.KV, key string) (*cyclic.Int, error) {
+	vo, err := kv.Get(key, currentCyclicVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	cy := &cyclic.Int{}
+
+	return cy, cy.GobDecode(vo.Data)
+}
diff --git a/storage/utility/dh_test.go b/storage/utility/dh_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4d4a31941faee3467f029ffca4dbc57f8fa9dafe
--- /dev/null
+++ b/storage/utility/dh_test.go
@@ -0,0 +1,49 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"testing"
+)
+
+// Unit test for StoreCyclicKey
+func TestStoreCyclicKey(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := versioned.NewKV(kv)
+	grp := getTestGroup()
+	x := grp.NewInt(77)
+
+	err := StoreCyclicKey(vkv, x, "testKey")
+	if err != nil {
+		t.Error("Failed to store cyclic key")
+	}
+}
+
+// Unit test for LoadCyclicKey
+func TestLoadCyclicKey(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := versioned.NewKV(kv)
+	grp := getTestGroup()
+	x := grp.NewInt(77)
+
+	intKey := "testKey"
+	err := StoreCyclicKey(vkv, x, intKey)
+	if err != nil {
+		t.Errorf("Failed to store cyclic key: %+v", err)
+	}
+
+	loaded, err := LoadCyclicKey(vkv, intKey)
+	if err != nil {
+		t.Errorf("Failed to load cyclic key: %+v", err)
+	}
+	if loaded.Cmp(x) != 0 {
+		t.Errorf("Stored int did not match received.  Stored: %v, Received: %v", x, loaded)
+	}
+}
diff --git a/storage/utility/e2eMessageBuffer.go b/storage/utility/e2eMessageBuffer.go
new file mode 100644
index 0000000000000000000000000000000000000000..9f0d3f417e6fd9c84e66c71dc324d13df31cf0ac
--- /dev/null
+++ b/storage/utility/e2eMessageBuffer.go
@@ -0,0 +1,165 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"crypto/md5"
+	"encoding/binary"
+	"encoding/json"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/xx_network/primitives/id"
+
+	"time"
+)
+
+const currentE2EMessageVersion = 0
+
+type e2eMessageHandler struct{}
+
+type e2eMessage struct {
+	Recipient   []byte
+	Payload     []byte
+	MessageType uint32
+	Params      params.E2E
+}
+
+// SaveMessage saves the e2eMessage as a versioned object at the specified key
+// in the key value store.
+func (emh *e2eMessageHandler) SaveMessage(kv *versioned.KV, m interface{}, key string) error {
+	msg := m.(e2eMessage)
+
+	b, err := json.Marshal(&msg)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to marshal e2e message for storage: %s", err)
+	}
+
+	// Create versioned object
+	obj := versioned.Object{
+		Version:   currentE2EMessageVersion,
+		Timestamp: time.Now(),
+		Data:      b,
+	}
+
+	// Save versioned object
+	return kv.Set(key, currentE2EMessageVersion, &obj)
+}
+
+// LoadMessage returns the e2eMessage with the specified key from the key value
+// store. An empty message and error are returned if the message could not be
+// retrieved.
+func (emh *e2eMessageHandler) LoadMessage(kv *versioned.KV, key string) (interface{}, error) {
+	// Load the versioned object
+	vo, err := kv.Get(key, currentE2EMessageVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	// Unmarshal data into e2eMessage
+	msg := e2eMessage{}
+	if err := json.Unmarshal(vo.Data, &msg); err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal e2e message for storage: %s", err)
+	}
+
+	return msg, err
+}
+
+// DeleteMessage deletes the message with the specified key from the key value
+// store.
+func (emh *e2eMessageHandler) DeleteMessage(kv *versioned.KV, key string) error {
+	return kv.Delete(key, currentE2EMessageVersion)
+}
+
+// HashMessage generates a hash of the e2eMessage.
+// Do not include the params in the hash so it is not needed to resubmit the
+// message into succeeded or failed
+func (emh *e2eMessageHandler) HashMessage(m interface{}) MessageHash {
+	msg := m.(e2eMessage)
+
+	var digest []byte
+	digest = append(digest, msg.Recipient...)
+	digest = append(digest, msg.Payload...)
+
+	mtBytes := make([]byte, 4)
+	binary.BigEndian.PutUint32(mtBytes, msg.MessageType)
+	digest = append(digest, mtBytes...)
+
+	return md5.Sum(digest)
+}
+
+// E2eMessageBuffer wraps the message buffer to store and load raw e2eMessages.
+type E2eMessageBuffer struct {
+	mb *MessageBuffer
+}
+
+func NewE2eMessageBuffer(kv *versioned.KV, key string) (*E2eMessageBuffer, error) {
+	mb, err := NewMessageBuffer(kv, &e2eMessageHandler{}, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &E2eMessageBuffer{mb: mb}, nil
+}
+
+func LoadE2eMessageBuffer(kv *versioned.KV, key string) (*E2eMessageBuffer, error) {
+	mb, err := LoadMessageBuffer(kv, &e2eMessageHandler{}, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &E2eMessageBuffer{mb: mb}, nil
+}
+
+func (emb *E2eMessageBuffer) Add(m message.Send, p params.E2E) {
+	e2eMsg := e2eMessage{
+		Recipient:   m.Recipient.Marshal(),
+		Payload:     m.Payload,
+		MessageType: uint32(m.MessageType),
+		Params:      p,
+	}
+
+	emb.mb.Add(e2eMsg)
+}
+
+func (emb *E2eMessageBuffer) AddProcessing(m message.Send, p params.E2E) {
+	e2eMsg := e2eMessage{
+		Recipient:   m.Recipient.Marshal(),
+		Payload:     m.Payload,
+		MessageType: uint32(m.MessageType),
+		Params:      p,
+	}
+
+	emb.mb.AddProcessing(e2eMsg)
+}
+
+func (emb *E2eMessageBuffer) Next() (message.Send, params.E2E, bool) {
+	m, ok := emb.mb.Next()
+	if !ok {
+		return message.Send{}, params.E2E{}, false
+	}
+
+	msg := m.(e2eMessage)
+	recipient, err := id.Unmarshal(msg.Recipient)
+	if err != nil {
+		jww.FATAL.Panicf("Error unmarshaling Recipient: %v", err)
+	}
+	return message.Send{recipient, msg.Payload,
+		message.Type(msg.MessageType)}, msg.Params, true
+}
+
+func (emb *E2eMessageBuffer) Succeeded(m message.Send) {
+	emb.mb.Succeeded(e2eMessage{m.Recipient.Marshal(),
+		m.Payload, uint32(m.MessageType), params.E2E{}})
+}
+
+func (emb *E2eMessageBuffer) Failed(m message.Send) {
+	emb.mb.Failed(e2eMessage{m.Recipient.Marshal(),
+		m.Payload, uint32(m.MessageType), params.E2E{}})
+}
diff --git a/storage/utility/e2eMessageBuffer_test.go b/storage/utility/e2eMessageBuffer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..aa7366ced5e5964fc666b67b3015f34a46433d4c
--- /dev/null
+++ b/storage/utility/e2eMessageBuffer_test.go
@@ -0,0 +1,176 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"encoding/json"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Test happy path of e2eMessageHandler.SaveMessage().
+func TestE2EMessageHandler_SaveMessage(t *testing.T) {
+	// Set up test values
+	emg := &e2eMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, _ := makeTestE2EMessages(10, t)
+
+	for _, msg := range testMsgs {
+		key := makeStoredMessageKey("testKey", emg.HashMessage(msg))
+
+		// Save message
+		err := emg.SaveMessage(kv, msg, key)
+		if err != nil {
+			t.Errorf("SaveMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		// Try to get message
+		obj, err := kv.Get(key, 0)
+		if err != nil {
+			t.Errorf("Get() returned an error: %v", err)
+		}
+
+		// Test if message retrieved matches expected
+		testMsg := &e2eMessage{}
+		if err := json.Unmarshal(obj.Data, testMsg); err != nil {
+			t.Errorf("Failed to unmarshal message: %v", err)
+		}
+		if !reflect.DeepEqual(msg, *testMsg) {
+			t.Errorf("SaveMessage() returned versioned object with incorrect data."+
+				"\n\texpected: %v\n\treceived: %v",
+				msg, *testMsg)
+		}
+	}
+}
+
+// Test happy path of e2eMessageHandler.LoadMessage().
+func TestE2EMessageHandler_LoadMessage(t *testing.T) {
+	// Set up test values
+	cmh := &e2eMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, _ := makeTestE2EMessages(10, t)
+
+	for _, msg := range testMsgs {
+		key := makeStoredMessageKey("testKey", cmh.HashMessage(msg))
+
+		// Save message
+		if err := cmh.SaveMessage(kv, msg, key); err != nil {
+			t.Errorf("SaveMessage() returned an error: %v", err)
+		}
+
+		// Try to load message
+		testMsg, err := cmh.LoadMessage(kv, key)
+		if err != nil {
+			t.Errorf("LoadMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		// Test if message loaded matches expected
+		if !reflect.DeepEqual(msg, testMsg) {
+			t.Errorf("LoadMessage() returned an unexpected object."+
+				"\n\texpected: %v\n\treceived: %v",
+				msg, testMsg)
+		}
+	}
+}
+
+// Smoke test of e2eMessageHandler.
+func TestE2EMessageHandler_Smoke(t *testing.T) {
+	// Set up test messages
+	_, testMsgs := makeTestE2EMessages(2, t)
+
+	// Create new buffer
+	cmb, err := NewE2eMessageBuffer(versioned.NewKV(make(ekv.Memstore)), "testKey")
+	if err != nil {
+		t.Errorf("NewE2eMessageBuffer() returned an error."+
+			"\n\texpected: %v\n\trecieved: %v", nil, err)
+	}
+
+	// Add two messages
+	cmb.Add(testMsgs[0], params.E2E{})
+	cmb.Add(testMsgs[1], params.E2E{})
+
+	if len(cmb.mb.messages) != 2 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			2, len(cmb.mb.messages))
+	}
+
+	msg, _, exists := cmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	cmb.Succeeded(msg)
+
+	if len(cmb.mb.messages) != 1 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			1, len(cmb.mb.messages))
+	}
+
+	msg, _, exists = cmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	if len(cmb.mb.messages) != 0 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			0, len(cmb.mb.messages))
+	}
+	cmb.Failed(msg)
+
+	if len(cmb.mb.messages) != 1 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			1, len(cmb.mb.messages))
+	}
+
+	msg, _, exists = cmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	cmb.Succeeded(msg)
+
+	msg, _, exists = cmb.Next()
+	if exists {
+		t.Error("Next() found a message in the buffer when it should be empty.")
+	}
+
+	if len(cmb.mb.messages) != 0 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			0, len(cmb.mb.messages))
+	}
+
+}
+
+// makeTestE2EMessages creates a list of messages with random data and the
+// expected map after they are added to the buffer.
+func makeTestE2EMessages(n int, t *testing.T) ([]e2eMessage, []message.Send) {
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	msgs := make([]e2eMessage, n)
+	send := make([]message.Send, n)
+	for i := range msgs {
+		rngBytes := make([]byte, 128)
+		prng.Read(rngBytes)
+		msgs[i].Recipient = rngBytes
+		prng.Read(rngBytes)
+		msgs[i].Payload = rngBytes
+		prng.Read(rngBytes)
+		msgs[i].MessageType = uint32(rngBytes[0])
+
+		send[i].Recipient = id.NewIdFromString(string(msgs[i].Recipient), id.User, t)
+		send[i].Payload = msgs[i].Payload
+		send[i].MessageType = message.Type(msgs[i].MessageType)
+	}
+
+	return msgs, send
+}
diff --git a/storage/utility/group.go b/storage/utility/group.go
new file mode 100644
index 0000000000000000000000000000000000000000..20a05e8378fde158a0ec6b1b6009a6f9f68ebc55
--- /dev/null
+++ b/storage/utility/group.go
@@ -0,0 +1,44 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"time"
+)
+
+const currentGroupVersion = 0
+
+func StoreGroup(kv *versioned.KV, grp *cyclic.Group, key string) error {
+	now := time.Now()
+
+	data, err := grp.GobEncode()
+	if err != nil {
+		return err
+	}
+
+	obj := versioned.Object{
+		Version:   currentGroupVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	return kv.Set(key, currentE2EMessageVersion, &obj)
+}
+
+func LoadGroup(kv *versioned.KV, key string) (*cyclic.Group, error) {
+	vo, err := kv.Get(key, currentE2EMessageVersion)
+	if err != nil {
+		return nil, err
+	}
+
+	grp := &cyclic.Group{}
+
+	return grp, grp.GobDecode(vo.Data)
+}
diff --git a/storage/utility/group_test.go b/storage/utility/group_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ef8ed3ea22dead0c1764d094513a22ccf53e931f
--- /dev/null
+++ b/storage/utility/group_test.go
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/crypto/large"
+	"testing"
+)
+
+// Unit test for StoreGroup
+func TestStoreGroup(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := versioned.NewKV(kv)
+	grp := getTestGroup()
+	err := StoreGroup(vkv, grp, "testKey")
+	if err != nil {
+		t.Errorf("Failed to store group in kv: %+v", err)
+	}
+}
+
+// Unit test for LoadGroup
+func TestLoadGroup(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := versioned.NewKV(kv)
+	grp := getTestGroup()
+
+	grpKey := "testKey"
+	err := StoreGroup(vkv, grp, grpKey)
+	if err != nil {
+		t.Errorf("Failed to store group in kv: %+v", err)
+	}
+
+	loaded, err := LoadGroup(vkv, grpKey)
+	if err != nil {
+		t.Errorf("Failed to load stored group: %+v", err)
+	}
+	if grp.GetFingerprint() != loaded.GetFingerprint() {
+		t.Errorf("Stored & received group fingerprints don't match.  Stored: %v, Received: %v",
+			grp.GetFingerprint(), loaded.GetFingerprint())
+	}
+}
+
+func getTestGroup() *cyclic.Group {
+	return cyclic.NewGroup(
+		large.NewIntFromString("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48"+
+			"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F"+
+			"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5"+
+			"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2"+
+			"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41"+
+			"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE"+
+			"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15"+
+			"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B", 16),
+		large.NewIntFromString("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613"+
+			"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4"+
+			"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472"+
+			"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5"+
+			"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA"+
+			"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71"+
+			"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0"+
+			"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7", 16))
+}
diff --git a/storage/utility/knownRounds.go b/storage/utility/knownRounds.go
new file mode 100644
index 0000000000000000000000000000000000000000..5b57c29484b91570abd93d7149ed0e91ccd9e651
--- /dev/null
+++ b/storage/utility/knownRounds.go
@@ -0,0 +1,186 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+/*
+// Sub key used in building keys for saving the message to the key value store
+const knownRoundsPrefix = "knownRound"
+
+// Version of the file saved to the key value store
+const currentKnownRoundsVersion = 0
+
+// KnownRounds stores a buffer of which rounds have been checked and those
+// that have yet to be checked. The buffer is saved in a key value store so that
+// the values can be recovered if something happens to the buffer in memory.
+type KnownRounds struct {
+	rounds *knownRounds.KnownRounds
+	kv     *versioned.KV
+	key    string
+	mux    sync.RWMutex
+}
+
+// NewKnownRounds creates a new empty KnownRounds and saves it to the passed
+// in key value store at the specified key. An error is returned on an
+// unsuccessful save.
+func NewKnownRounds(kv *versioned.KV, key string, known *knownRounds.KnownRounds) (*KnownRounds, error) {
+	// Create new empty struct
+	kr := &KnownRounds{
+		rounds: known,
+		kv:     kv.Prefix(knownRoundsPrefix),
+		key:    key,
+	}
+
+	// Save the struct
+	err := kr.save()
+
+	// Return the new KnownRounds or an error if the saving failed
+	return kr, err
+}
+
+// LoadKnownRounds loads and existing KnownRounds from the key value store
+// into memory at the given key. Returns an error if it cannot be loaded.
+func LoadKnownRounds(kv *versioned.KV, key string, size int) (*KnownRounds, error) {
+	// Create new empty struct
+	kr := &KnownRounds{
+		rounds: knownRounds.NewKnownRound(size),
+		kv:     kv.Prefix(knownRoundsPrefix),
+		key:    key,
+	}
+
+	// Load the KnownRounds into the new buffer
+	err := kr.load()
+
+	// Return the loaded buffer or an error if loading failed
+	return kr, err
+}
+
+// save saves the round buffer as a versioned object to the key value store.
+func (kr *KnownRounds) save() error {
+	now := time.Now()
+
+	// Marshal list of rounds
+	data, err := kr.rounds.Marshal()
+	if err != nil {
+		return err
+	}
+
+	// Create versioned object with data
+	obj := versioned.Object{
+		Version:   currentKnownRoundsVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	// Save versioned object
+	return kr.kv.Set(kr.key, &obj)
+}
+
+// load retrieves the list of rounds from the key value store and stores them
+// in the buffer.
+func (kr *KnownRounds) load() error {
+
+	// Load the versioned object
+	vo, err := kr.kv.Get(kr.key)
+	if err != nil {
+		return err
+	}
+
+	// Unmarshal the list of rounds
+	err = kr.rounds.Unmarshal(vo.Data)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Deletes a known rounds object from disk and memory
+func (kr *KnownRounds) Delete() error {
+	err := kr.kv.Delete(kr.key)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Checked determines if the round has been checked.
+func (kr *KnownRounds) Checked(rid id.Round) bool {
+	kr.mux.RLock()
+	defer kr.mux.RUnlock()
+
+	return kr.rounds.Checked(rid)
+}
+
+// Check denotes a round has been checked.
+func (kr *KnownRounds) Check(rid id.Round) {
+	kr.mux.Lock()
+	defer kr.mux.Unlock()
+
+	kr.rounds.Check(rid)
+
+	err := kr.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error saving list of checked rounds: %v", err)
+	}
+}
+
+// Forward sets all rounds before the given round ID as checked.
+func (kr *KnownRounds) Forward(rid id.Round) {
+	kr.mux.Lock()
+	defer kr.mux.Unlock()
+
+	kr.rounds.Forward(rid)
+
+	err := kr.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error saving list of checked rounds: %v", err)
+	}
+}
+
+// RangeUnchecked runs the passed function over the range of all unchecked round
+// IDs up to the passed newestRound to determine if they should be checked.
+func (kr *KnownRounds) RangeUnchecked(newestRid id.Round,
+	roundCheck func(id id.Round) bool) {
+	kr.mux.Lock()
+	defer kr.mux.Unlock()
+
+	kr.rounds.RangeUnchecked(newestRid, roundCheck)
+
+	err := kr.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error saving list of checked rounds: %v", err)
+	}
+}
+
+// RangeUncheckedMasked checks rounds based off the provided mask.
+func (kr *KnownRounds) RangeUncheckedMasked(mask *knownRounds.KnownRounds,
+	roundCheck knownRounds.RoundCheckFunc, maxChecked int) {
+	kr.mux.Lock()
+	defer kr.mux.Unlock()
+
+	kr.rounds.RangeUncheckedMasked(mask, roundCheck, maxChecked)
+
+	err := kr.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error saving list of checked rounds: %v", err)
+	}
+}
+
+// RangeUncheckedMasked checks rounds based off the provided mask.
+func (kr *KnownRounds) RangeUncheckedMaskedRange(mask *knownRounds.KnownRounds,
+	roundCheck knownRounds.RoundCheckFunc, start, end id.Round, maxChecked int) {
+	kr.mux.Lock()
+	defer kr.mux.Unlock()
+
+	kr.rounds.RangeUncheckedMaskedRange(mask, roundCheck, start, end, maxChecked)
+
+	err := kr.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error saving list of checked rounds: %v", err)
+	}
+}*/
diff --git a/storage/utility/knownRounds_test.go b/storage/utility/knownRounds_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2f67d1d90a0272e47fc640d2e8f9781c43eac782
--- /dev/null
+++ b/storage/utility/knownRounds_test.go
@@ -0,0 +1,199 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+/*
+// Tests happy path of NewKnownRounds.
+func TestNewKnownRounds(t *testing.T) {
+	// Set up expected value
+	size := 10
+	rootKv := versioned.NewKV(make(ekv.Memstore))
+	expectedKR := &KnownRounds{
+		rounds: knownRounds.NewKnownRound(size),
+		kv:     rootKv.Prefix(knownRoundsPrefix),
+		key:    "testKey",
+	}
+
+	// Create new KnownRounds
+	k := knownRounds.NewKnownRound(size)
+	kr, err := NewKnownRounds(rootKv, expectedKR.key, k)
+	if err != nil {
+		t.Errorf("NewKnownRounds() returned an error."+
+			"\n\texpected: %v\n\treceived: %v", nil, err)
+	}
+
+	if !reflect.DeepEqual(expectedKR, kr) {
+		t.Errorf("NewKnownRounds() returned an incorrect KnownRounds."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedKR, kr)
+	}
+}
+
+// Tests happy path of LoadKnownRounds.
+func TestLoadKnownRounds(t *testing.T) {
+	// Set up expected value
+	size := 10
+	rootKv := versioned.NewKV(make(ekv.Memstore))
+	expectedKR := &KnownRounds{
+		rounds: knownRounds.NewKnownRound(size),
+		kv:     rootKv.Prefix(knownRoundsPrefix),
+		key:    "testKey",
+	}
+
+	// Check rounds in the buffer and save the key value store
+	expectedKR.rounds.Check(id.Round(0))
+	for i := 0; i < (size * 64); i++ {
+		if i%7 == 0 {
+			expectedKR.rounds.Check(id.Round(i))
+		}
+	}
+	err := expectedKR.save()
+	if err != nil {
+		t.Fatalf("Error saving KnownRounds: %v", err)
+	}
+
+	kr, err := LoadKnownRounds(rootKv, expectedKR.key, size)
+	if err != nil {
+		t.Errorf("LoadKnownRounds() returned an error."+
+			"\n\texpected: %v\n\treceived: %+v", nil, err)
+	}
+
+	if !reflect.DeepEqual(expectedKR, kr) {
+		t.Errorf("LoadKnownRounds() returned an incorrect KnownRounds."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedKR, kr)
+		t.Errorf("%+v != \n%+v",
+			expectedKR.rounds, kr.rounds)
+	}
+}
+
+// Tests happy path of KnownRounds.save().
+func TestKnownRounds_save(t *testing.T) {
+	// Set up expected value
+	size := 10
+	expectedKR := &KnownRounds{
+		rounds: knownRounds.NewKnownRound(size),
+		kv:     versioned.NewKV(make(ekv.Memstore)),
+		key:    "testKey",
+	}
+	for i := 0; i < (size * 64); i++ {
+		if i%7 == 0 {
+			expectedKR.rounds.Check(id.Round(i))
+		}
+	}
+	expectedData, err := expectedKR.rounds.Marshal()
+	if err != nil {
+		t.Fatalf("Marshal() returned an error: %v", err)
+	}
+	kr := &KnownRounds{
+		rounds: knownRounds.NewKnownRound(size),
+		kv:     expectedKR.kv,
+		key:    expectedKR.key,
+	}
+
+	err = expectedKR.save()
+	if err != nil {
+		t.Errorf("save() returned an error: %v", err)
+	}
+
+	obj, err := expectedKR.kv.Get(expectedKR.key)
+	if err != nil {
+		t.Errorf("Get() returned an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedData, obj.Data) {
+		t.Errorf("save() did not save the correct KnownRounds."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedData, kr)
+	}
+}
+
+// // Tests happy path of KnownRounds.load().
+// func TestKnownRounds_load(t *testing.T) {
+// 	// Set up expected value
+// 	size := 10
+// 	expectedKR := &KnownRounds{
+// 		rounds: knownRounds.NewKnownRound(size),
+// 		kv:     versioned.NewKV(make(ekv.Memstore)),
+// 		key:    "testKey",
+// 	}
+// 	for i := 0; i < (size * 64); i++ {
+// 		if i%7 == 0 {
+// 			expectedKR.rounds.Check(id.Round(i))
+// 		}
+// 	}
+// 	kr := &KnownRounds{
+// 		rounds: knownRounds.NewKnownRound(size * 64),
+// 		kv:     expectedKR.kv,
+// 		key:    expectedKR.key,
+// 	}
+
+// 	err := expectedKR.save()
+// 	if err != nil {
+// 		t.Errorf("save() returned an error: %v", err)
+// 	}
+
+// 	err = kr.load()
+// 	if err != nil {
+// 		t.Errorf("load() returned an error: %v", err)
+// 	}
+
+// 	if !reflect.DeepEqual(expectedKR, kr) {
+// 		t.Errorf("load() did not produce the correct KnownRounds."+
+// 			"\n\texpected: %+v\n\treceived: %+v", expectedKR, kr)
+// 	}
+// }
+
+func TestKnownRounds_Smoke(t *testing.T) {
+	k := knownRounds.NewKnownRound(10)
+	kr, err := NewKnownRounds(versioned.NewKV(make(ekv.Memstore)), "testKey", k)
+	if err != nil {
+		t.Fatalf("Failed to create new KnownRounds: %v", err)
+	}
+
+	if kr.Checked(10) {
+		t.Errorf("Checked() on round ID %d did not return the expected value."+
+			"\n\texpected: %v\n\treceived: %v", 10, false, kr.Checked(10))
+	}
+
+	kr.Check(10)
+
+	if !kr.Checked(10) {
+		t.Errorf("Checked() on round ID %d did not return the expected value."+
+			"\n\texpected: %v\n\treceived: %v", 10, true, kr.Checked(10))
+	}
+
+	roundCheck := func(id id.Round) bool {
+		return id%2 == 1
+	}
+	kr.RangeUnchecked(30, roundCheck)
+
+	newKR := &KnownRounds{
+		rounds: knownRounds.NewKnownRound(10),
+		kv:     kr.kv,
+		key:    kr.key,
+	}
+
+	err = newKR.load()
+	if err != nil {
+		t.Errorf("load() returned an error: %v", err)
+	}
+
+	if !reflect.DeepEqual(kr, newKR) {
+		t.Errorf("load() did not produce the correct KnownRounds."+
+			"\n\texpected: %+v\n\treceived: %+v", kr, newKR)
+	}
+
+	mask := knownRounds.NewKnownRound(1)
+	mask.Check(17)
+	kr.RangeUncheckedMasked(mask, roundCheck, 15)
+
+	kr.Forward(20)
+
+	if !kr.Checked(15) {
+		t.Errorf("Checked() on round ID %d did not return the expected value."+
+			"\n\texpected: %v\n\treceived: %v", 15, true, kr.Checked(15))
+	}
+}*/
diff --git a/storage/utility/messageBuffer.go b/storage/utility/messageBuffer.go
new file mode 100644
index 0000000000000000000000000000000000000000..59743c855072ec9343ecd6460c4e1044af2d45f5
--- /dev/null
+++ b/storage/utility/messageBuffer.go
@@ -0,0 +1,314 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"encoding/json"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/primitives/format"
+	"sync"
+	"time"
+)
+
+// MessageHash stores the hash of a message, which is used as the key for each
+// message stored in the buffer.
+type MessageHash [16]byte
+
+// Sub key used in building keys for saving the message to the key value store
+const messageSubKey = "bufferedMessage"
+
+// Version of the file saved to the key value store
+const currentMessageBufferVersion = 0
+
+// MessageHandler interface used to handle the passed in message type so the
+// buffer can be used at different layers of the stack.
+type MessageHandler interface {
+	// SaveMessage saves the message as a versioned object at the specified key
+	// in the key value store.
+	SaveMessage(kv *versioned.KV, m interface{}, key string) error
+
+	// LoadMessage returns the message with the specified key from the key value
+	// store.
+	LoadMessage(kv *versioned.KV, key string) (interface{}, error)
+
+	// DeleteMessage deletes the message with the specified key from the key
+	// value store.
+	DeleteMessage(kv *versioned.KV, key string) error
+
+	// HashMessage generates a hash of the message.
+	HashMessage(m interface{}) MessageHash
+}
+
+// MessageBuffer holds a list of messages in the "not processed" or "processing"
+// state both in memory. Messages in the "not processed" state are held in the
+// messages map and messages in the "processing" state are moved into the
+// processingMessages map. When the message is done being processed, it is
+// removed from the buffer. The actual messages are saved in the key value store
+// along with a copy of the buffer that is held in memory.
+type MessageBuffer struct {
+	messages           map[MessageHash]struct{}
+	processingMessages map[MessageHash]struct{}
+	kv                 *versioned.KV
+	handler            MessageHandler
+	key                string
+	mux                sync.RWMutex
+}
+
+// NewMessageBuffer creates a new empty buffer and saves it to the passed in key
+// value store at the specified key. An error is returned on an unsuccessful
+// save.
+func NewMessageBuffer(kv *versioned.KV, handler MessageHandler,
+	key string) (*MessageBuffer, error) {
+	// Create new empty buffer
+	mb := &MessageBuffer{
+		messages:           make(map[MessageHash]struct{}),
+		processingMessages: make(map[MessageHash]struct{}),
+		handler:            handler,
+		kv:                 kv,
+		key:                key,
+	}
+
+	// Save the buffer
+	err := mb.save()
+
+	// Return the new buffer or an error if saving failed
+	return mb, err
+}
+
+// LoadMessageBuffer loads an existing message buffer from the key value store
+// into memory at the given key. Returns an error if buffer cannot be loaded.
+func LoadMessageBuffer(kv *versioned.KV, handler MessageHandler,
+	key string) (*MessageBuffer, error) {
+	// Create new empty buffer
+	mb := &MessageBuffer{
+		messages:           make(map[MessageHash]struct{}),
+		processingMessages: make(map[MessageHash]struct{}),
+		handler:            handler,
+		kv:                 kv,
+		key:                key,
+	}
+
+	// Load rounds into buffer
+	err := mb.load()
+
+	// Return the filled buffer or an error if loading failed
+	return mb, err
+}
+
+// save saves the buffer as a versioned object. All messages, regardless if they
+// are in the "not processed" or "processing" state are stored together and
+// considered "not processed".
+func (mb *MessageBuffer) save() error {
+	now := time.Now()
+
+	// Build a combined list of message hashes in messages + processingMessages
+	allMessages := mb.getMessageList()
+
+	// Marshal list of message hashes into byte slice
+	data, err := json.Marshal(allMessages)
+	if err != nil {
+		return err
+	}
+
+	// Create versioned object with data
+	obj := versioned.Object{
+		Version:   currentMessageBufferVersion,
+		Timestamp: now,
+		Data:      data,
+	}
+
+	// Save versioned object
+	return mb.kv.Set(mb.key, currentMessageBufferVersion, &obj)
+}
+
+// getMessageList returns a list of all message hashes stored in messages and
+// processingMessages in a random order.
+func (mb *MessageBuffer) getMessageList() []MessageHash {
+	// Create new slice with a length to fit all messages in either list
+	msgs := make([]MessageHash, len(mb.messages)+len(mb.processingMessages))
+
+	i := 0
+	// Add messages from the "not processed" list
+	for msg := range mb.messages {
+		msgs[i] = msg
+		i++
+	}
+
+	// Add messages from the "processing" list
+	for msg := range mb.processingMessages {
+		msgs[i] = msg
+		i++
+	}
+
+	return msgs
+}
+
+// load retrieves all the messages from the versioned object and stores them as
+// unprocessed messages.
+func (mb *MessageBuffer) load() error {
+
+	// Load the versioned object
+	vo, err := mb.kv.Get(mb.key, currentMessageBufferVersion)
+	if err != nil {
+		return err
+	}
+
+	// Create slice of message hashes from data
+	var msgs []MessageHash
+	err = json.Unmarshal(vo.Data, &msgs)
+	if err != nil {
+		return err
+	}
+
+	// Convert slice to map and save all rounds as unprocessed
+	for _, m := range msgs {
+		mb.messages[m] = struct{}{}
+	}
+
+	return nil
+}
+
+// Add adds a message to the buffer in "not processing" state.
+func (mb *MessageBuffer) Add(m interface{}) {
+	h := mb.handler.HashMessage(m)
+
+	mb.mux.Lock()
+	defer mb.mux.Unlock()
+
+	// Ensure message does not already exist in buffer
+	_, exists1 := mb.messages[h]
+	_, exists2 := mb.processingMessages[h]
+	if exists1 || exists2 {
+		return
+	}
+
+	// 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 message to the buffer
+	mb.messages[h] = struct{}{}
+
+	// Save buffer
+	err = mb.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error whilse saving buffer: %v", err)
+	}
+}
+
+// Add adds a message to the buffer in "processing" state.
+func (mb *MessageBuffer) AddProcessing(m interface{}) {
+	h := mb.handler.HashMessage(m)
+
+	mb.mux.Lock()
+	defer mb.mux.Unlock()
+
+	// Ensure message does not already exist in buffer
+	_, exists1 := mb.messages[h]
+	_, exists2 := mb.processingMessages[h]
+	if exists1 || exists2 {
+		return
+	}
+
+	// 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 message to the buffer
+	mb.processingMessages[h] = struct{}{}
+
+	// Save buffer
+	err = mb.save()
+	if err != nil {
+		jww.FATAL.Panicf("Error whilse saving buffer: %v", err)
+	}
+}
+
+// Next gets the next message from the buffer whose state is "not processing".
+// The returned messages are moved to the processing state. If there are no
+// messages remaining, then false is returned.
+func (mb *MessageBuffer) Next() (interface{}, bool) {
+	mb.mux.Lock()
+	defer mb.mux.Unlock()
+
+	if len(mb.messages) == 0 {
+		return format.Message{}, false
+	}
+
+	// Pop the next MessageHash from the "not processing" list
+	h := next(mb.messages)
+	delete(mb.messages, h)
+
+	// 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.FATAL.Panicf("Could not load message: %v", err)
+	}
+
+	return m, true
+}
+
+// next returns the first MessageHash in the map returned by range.
+func next(msgMap map[MessageHash]struct{}) MessageHash {
+	for h := range msgMap {
+		return h
+	}
+	return MessageHash{}
+}
+
+// Remove sets a messaged as processed and removed it from the buffer.
+func (mb *MessageBuffer) Succeeded(m interface{}) {
+	h := mb.handler.HashMessage(m)
+
+	mb.mux.Lock()
+	defer mb.mux.Unlock()
+
+	// Done message from buffer
+	delete(mb.processingMessages, h)
+	delete(mb.messages, h)
+
+	// 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)
+	}
+
+	// Save modified buffer to key value store
+	err = mb.save()
+	if err != nil {
+		jww.FATAL.Fatalf("Failed to save: %v", err)
+	}
+}
+
+// Failed sets a message as failed to process. It changes the message back to
+// the "not processed" state.
+func (mb *MessageBuffer) Failed(m interface{}) {
+	h := mb.handler.HashMessage(m)
+
+	mb.mux.Lock()
+	defer mb.mux.Unlock()
+
+	// Done from "processing" state
+	delete(mb.processingMessages, h)
+
+	// Add to "not processed" state
+	mb.messages[h] = struct{}{}
+}
+
+// makeStoredMessageKey generates a new key for the message based on its has.
+func makeStoredMessageKey(key string, h MessageHash) string {
+	return key + messageSubKey + string(h[:])
+}
diff --git a/storage/utility/messageBuffer_test.go b/storage/utility/messageBuffer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f331078fd5429a81c317620caf28802932617ba6
--- /dev/null
+++ b/storage/utility/messageBuffer_test.go
@@ -0,0 +1,350 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/json"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"math/rand"
+	"os"
+	"reflect"
+	"testing"
+	"time"
+)
+
+type testHandler struct {
+	messages map[string][]byte
+}
+
+func (th *testHandler) SaveMessage(kv *versioned.KV, m interface{}, key string) error {
+	mBytes := m.([]byte)
+	th.messages[key] = mBytes
+	return nil
+}
+
+func (th *testHandler) LoadMessage(kv *versioned.KV, key string) (interface{}, error) {
+	m, ok := th.messages[key]
+	if !ok {
+		return nil, os.ErrNotExist
+	}
+	return m, nil
+}
+
+func (th *testHandler) DeleteMessage(kv *versioned.KV, key string) error {
+	_, ok := th.messages[key]
+	if !ok {
+		return os.ErrNotExist
+	}
+	delete(th.messages, key)
+	return nil
+}
+
+func (th *testHandler) HashMessage(m interface{}) MessageHash {
+	mBytes := m.([]byte)
+	// Sum returns a array that is the exact same size as the MessageHash and Go
+	// apparently automatically casts it
+	return md5.Sum(mBytes)
+}
+
+func newTestHandler() *testHandler {
+	return &testHandler{messages: make(map[string][]byte)}
+}
+
+// Tests happy path of NewMessageBuffer.
+func TestNewMessageBuffer(t *testing.T) {
+	// Set up expected value
+	th := newTestHandler()
+	expectedMB := &MessageBuffer{
+		messages:           make(map[MessageHash]struct{}),
+		processingMessages: make(map[MessageHash]struct{}),
+		handler:            th,
+		kv:                 versioned.NewKV(make(ekv.Memstore)),
+		key:                "testKey",
+	}
+
+	testMB, err := NewMessageBuffer(expectedMB.kv, th, expectedMB.key)
+	if err != nil {
+		t.Errorf("NewMessageBuffer() returned an error."+
+			"\n\texpected: %v\n\treceived: %v", nil, err)
+	}
+
+	if !reflect.DeepEqual(expectedMB, testMB) {
+		t.Errorf("NewMessageBuffer() returned an incorrect MessageBuffer."+
+			"\n\texpected: %v\n\treceived: %v", expectedMB, testMB)
+	}
+}
+
+// Tests happy path of TestLoadMessageBuffer.
+func TestLoadMessageBuffer(t *testing.T) {
+	th := newTestHandler()
+	// Set up expected value
+	expectedMB := &MessageBuffer{
+		messages:           make(map[MessageHash]struct{}),
+		processingMessages: make(map[MessageHash]struct{}),
+		handler:            th,
+		kv:                 versioned.NewKV(make(ekv.Memstore)),
+		key:                "testKey",
+	}
+	_ = addTestMessages(expectedMB, 20)
+	err := expectedMB.save()
+	if err != nil {
+		t.Fatalf("Error saving MessageBuffer: %v", err)
+	}
+
+	testMB, err := LoadMessageBuffer(expectedMB.kv, th, expectedMB.key)
+
+	// Move all the messages into one map to match the output
+	for mh := range expectedMB.processingMessages {
+		expectedMB.messages[mh] = struct{}{}
+	}
+	expectedMB.processingMessages = make(map[MessageHash]struct{})
+
+	if err != nil {
+		t.Errorf("LoadMessageBuffer() returned an error."+
+			"\n\texpected: %v\n\treceived: %v", nil, err)
+	}
+
+	if !reflect.DeepEqual(expectedMB, testMB) {
+		t.Errorf("NewMessageBuffer() returned an incorrect MessageBuffer."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedMB, testMB)
+	}
+}
+
+// Tests happy path of save() with a new empty MessageBuffer.
+func TestMessageBuffer_save_NewMB(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	key := "testKey"
+
+	mb, err := NewMessageBuffer(kv, newTestHandler(), key)
+	if err != nil {
+		t.Fatalf("Failed to create new MessageBuffer: %v", err)
+	}
+
+	err = mb.save()
+	if err != nil {
+		t.Errorf("save() returned an error."+
+			"\n\texpected: %v\n\treceived: %v", nil, err)
+	}
+	obj, err := kv.Get(key, 0)
+	if err != nil {
+		t.Errorf("save() did not correctly save buffer with key %+v to storage."+
+			"\n\terror: %v", key, err)
+	}
+
+	var messageArr []MessageHash
+	err = json.Unmarshal(obj.Data, &messageArr)
+	if !reflect.DeepEqual([]MessageHash{}, messageArr) {
+		t.Errorf("save() returned versioned object with incorrect data."+
+			"\n\texpected: %#v\n\treceived: %#v",
+			[]MessageHash{}, messageArr)
+	}
+}
+
+// Tests happy path of save().
+func TestMessageBuffer_save(t *testing.T) {
+	kv := versioned.NewKV(make(ekv.Memstore))
+	key := "testKey"
+	mb, err := NewMessageBuffer(kv, newTestHandler(), key)
+	if err != nil {
+		t.Fatalf("Failed to create new MessageBuffer: %v", err)
+	}
+
+	expectedMH := addTestMessages(mb, 20)
+
+	err = mb.save()
+	if err != nil {
+		t.Errorf("save() returned an error."+
+			"\n\texpected: %v\n\treceived: %v", nil, err)
+	}
+	obj, err := kv.Get(key, 0)
+	if err != nil {
+		t.Errorf("save() did not correctly save buffer with key %+v to storage."+
+			"\n\terror: %v", key, err)
+	}
+
+	var messageArr []MessageHash
+	err = json.Unmarshal(obj.Data, &messageArr)
+	if !cmpMessageHash(expectedMH, messageArr) {
+		t.Errorf("save() returned versioned object with incorrect data."+
+			"\n\texpected: %v\n\treceived: %v",
+			expectedMH, messageArr)
+	}
+}
+
+// Tests happy path of MessageBuffer.Add().
+func TestMessageBuffer_Add(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)
+	}
+	testMsgs, expectedMessages := makeTestMessages(20)
+	for _, m := range testMsgs {
+		testMB.Add(m)
+	}
+
+	if !reflect.DeepEqual(expectedMessages, testMB.messages) {
+		t.Errorf("Add() failed to add messages correctly into the buffer."+
+			"\n\texpected: %v\n\trecieved: %v",
+			expectedMessages, testMB.messages)
+	}
+
+	// Test adding duplicates
+	for _, m := range testMsgs {
+		testMB.Add(m)
+	}
+
+	if !reflect.DeepEqual(expectedMessages, testMB.messages) {
+		t.Errorf("Add() failed to add messages correctly into the buffer."+
+			"\n\texpected: %v\n\trecieved: %v",
+			expectedMessages, testMB.messages)
+	}
+}
+
+// Tests happy path of MessageBuffer.Next().
+func TestMessageBuffer_Next(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)
+	}
+	testMsgs, _ := makeTestMessages(20)
+	for _, m := range testMsgs {
+		testMB.Add(m)
+	}
+
+	for m, exists := testMB.Next(); exists; m, exists = testMB.Next() {
+		foundMsg := false
+		for i := range testMsgs {
+			mBytes := m.([]byte)
+			if bytes.Equal(testMsgs[i], mBytes) {
+				foundMsg = true
+				testMsgs[i] = testMsgs[len(testMsgs)-1]
+				testMsgs[len(testMsgs)-1] = []byte{}
+				testMsgs = testMsgs[:len(testMsgs)-1]
+				break
+			}
+		}
+		if !foundMsg {
+			t.Errorf("Next() returned the wrong message."+
+				"\n\trecieved: %+v", m)
+		}
+	}
+}
+
+// Tests happy path of MessageBuffer.Remove().
+func TestMessageBuffer_Succeeded(t *testing.T) {
+	th := newTestHandler()
+	// Create new MessageBuffer and fill with message
+	testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), th, "testKey")
+	if err != nil {
+		t.Fatalf("Failed to create new MessageBuffer: %v", err)
+	}
+	testMsgs, _ := makeTestMessages(1)
+	for _, m := range testMsgs {
+		testMB.Add(m)
+	}
+
+	// Get message
+	m, _ := testMB.Next()
+
+	testMB.Succeeded(m)
+
+	_, exists1 := testMB.messages[th.HashMessage(m)]
+	_, exists2 := testMB.processingMessages[th.HashMessage(m)]
+	if exists1 || exists2 {
+		t.Errorf("Remove() did not remove the message from the buffer."+
+			"\n\tbuffer: %+v", testMB)
+	}
+}
+
+// Tests happy path of MessageBuffer.Failed().
+func TestMessageBuffer_Failed(t *testing.T) {
+	th := newTestHandler()
+	// Create new MessageBuffer and fill with message
+	testMB, err := NewMessageBuffer(versioned.NewKV(make(ekv.Memstore)), th, "testKey")
+	if err != nil {
+		t.Fatalf("Failed to create new MessageBuffer: %v", err)
+	}
+	testMsgs, _ := makeTestMessages(1)
+	for _, m := range testMsgs {
+		testMB.Add(m)
+	}
+
+	// Get message
+	m, _ := testMB.Next()
+
+	testMB.Failed(m)
+
+	_, exists1 := testMB.messages[th.HashMessage(m)]
+	_, exists2 := testMB.processingMessages[th.HashMessage(m)]
+	if !exists1 || exists2 {
+		t.Errorf("Failed() did not move the message back into the \"not "+
+			"processed\" state.\n\tbuffer: %+v", testMB)
+	}
+}
+
+// addTestMessages adds random messages to the buffer.
+func addTestMessages(mb *MessageBuffer, n int) []MessageHash {
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	msgs := make([]MessageHash, n)
+	for i := 0; i < n; i++ {
+		keyData := make([]byte, 16)
+		prng.Read(keyData)
+		mh := MessageHash{}
+		copy(mh[:], keyData)
+
+		if i%10 == 0 {
+			mb.processingMessages[mh] = struct{}{}
+		} else {
+			mb.messages[mh] = struct{}{}
+		}
+		msgs[i] = mh
+
+	}
+	return msgs
+}
+
+// cmpMessageHash compares two slices of MessageHash to see if they have the
+// exact same elements in any order.
+func cmpMessageHash(arrA, arrB []MessageHash) bool {
+	if len(arrA) != len(arrB) {
+		return false
+	}
+	for _, a := range arrA {
+		foundInB := false
+		for _, b := range arrB {
+			if a == b {
+				foundInB = true
+				break
+			}
+		}
+		if !foundInB {
+			return false
+		}
+	}
+	return true
+}
+
+// makeTestMessages creates a list of messages with random data and the expected
+// map after they are added to the buffer.
+func makeTestMessages(n int) ([][]byte, map[MessageHash]struct{}) {
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	mh := map[MessageHash]struct{}{}
+	msgs := make([][]byte, n)
+	for i := range msgs {
+		msgs[i] = make([]byte, 256)
+		prng.Read(msgs[i])
+		mh[md5.Sum(msgs[i])] = struct{}{}
+	}
+
+	return msgs, mh
+}
diff --git a/storage/utility/meteredCmixMessageBuffer.go b/storage/utility/meteredCmixMessageBuffer.go
new file mode 100644
index 0000000000000000000000000000000000000000..e4b7ab4fbce6e5e97cf00c8fcadb0d130f13167e
--- /dev/null
+++ b/storage/utility/meteredCmixMessageBuffer.go
@@ -0,0 +1,156 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"crypto/md5"
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/primitives/format"
+	"time"
+)
+
+const currentMeteredCmixMessageVersion = 0
+
+type meteredCmixMessageHandler struct{}
+
+type meteredCmixMessage struct {
+	M         []byte
+	Count     uint
+	Timestamp time.Time
+}
+
+// SaveMessage saves the message as a versioned object at the specified key in
+// the key value store.
+func (*meteredCmixMessageHandler) SaveMessage(kv *versioned.KV, m interface{}, key string) error {
+	msg := m.(meteredCmixMessage)
+
+	marshaled, err := json.Marshal(&msg)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to marshal metered cmix message")
+	}
+
+	// Create versioned object
+	obj := versioned.Object{
+		Version:   currentMeteredCmixMessageVersion,
+		Timestamp: time.Now(),
+		Data:      marshaled,
+	}
+
+	// Save versioned object
+	return kv.Set(key, currentMessageBufferVersion, &obj)
+}
+
+// LoadMessage returns the message with the specified key from the key value
+// store. An empty message and error are returned if the message could not be
+// retrieved.
+func (*meteredCmixMessageHandler) LoadMessage(kv *versioned.KV, key string) (interface{}, error) {
+	// Load the versioned object
+	vo, err := kv.Get(key, currentMeteredCmixMessageVersion)
+	if err != nil {
+		return format.Message{}, err
+	}
+
+	msg := meteredCmixMessage{}
+	err = json.Unmarshal(vo.Data, &msg)
+	if err != nil {
+		return nil, errors.WithMessage(err, "Failed to unmarshal metered cmix message")
+	}
+
+	// Create message from data
+	return msg, nil
+}
+
+// DeleteMessage deletes the message with the specified key from the key value
+// store.
+func (*meteredCmixMessageHandler) DeleteMessage(kv *versioned.KV, key string) error {
+	return kv.Delete(key, currentMeteredCmixMessageVersion)
+}
+
+// HashMessage generates a hash of the message.
+func (*meteredCmixMessageHandler) HashMessage(m interface{}) MessageHash {
+	msg := m.(meteredCmixMessage)
+
+	return md5.Sum(msg.M)
+}
+
+// CmixMessageBuffer wraps the message buffer to store and load raw cmix
+// messages.
+type MeteredCmixMessageBuffer struct {
+	mb  *MessageBuffer
+	kv  *versioned.KV
+	key string
+}
+
+func NewMeteredCmixMessageBuffer(kv *versioned.KV, key string) (*MeteredCmixMessageBuffer, error) {
+	mb, err := NewMessageBuffer(kv, &meteredCmixMessageHandler{}, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &MeteredCmixMessageBuffer{mb: mb, kv: kv, key: key}, nil
+}
+
+func LoadMeteredCmixMessageBuffer(kv *versioned.KV, key string) (*MeteredCmixMessageBuffer, error) {
+	mb, err := LoadMessageBuffer(kv, &meteredCmixMessageHandler{}, key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &MeteredCmixMessageBuffer{mb: mb, kv: kv, key: key}, nil
+}
+
+func (mcmb *MeteredCmixMessageBuffer) Add(m format.Message) {
+	msg := meteredCmixMessage{
+		M:         m.Marshal(),
+		Count:     0,
+		Timestamp: time.Now(),
+	}
+	mcmb.mb.Add(msg)
+}
+
+func (mcmb *MeteredCmixMessageBuffer) AddProcessing(m format.Message) {
+	msg := meteredCmixMessage{
+		M:         m.Marshal(),
+		Count:     0,
+		Timestamp: time.Now(),
+	}
+	mcmb.mb.AddProcessing(msg)
+}
+
+func (mcmb *MeteredCmixMessageBuffer) Next() (format.Message, uint, time.Time, bool) {
+	m, ok := mcmb.mb.Next()
+	if !ok {
+		return format.Message{}, 0, time.Time{}, false
+	}
+
+	msg := m.(meteredCmixMessage)
+	rtnCnt := msg.Count
+
+	// increment the count and save
+	msg.Count++
+	mcmh := &meteredCmixMessageHandler{}
+	err := mcmh.SaveMessage(mcmb.kv, msg, makeStoredMessageKey(mcmb.key, mcmh.HashMessage(msg)))
+	if err != nil {
+		jww.FATAL.Panicf("Failed to save metered message after count "+
+			"update: %s", err)
+	}
+
+	msfFormat := format.Unmarshal(msg.M)
+	return msfFormat, rtnCnt, msg.Timestamp, true
+}
+
+func (mcmb *MeteredCmixMessageBuffer) Remove(m format.Message) {
+	mcmb.mb.Succeeded(meteredCmixMessage{M: m.Marshal()})
+}
+
+func (mcmb *MeteredCmixMessageBuffer) Failed(m format.Message) {
+	mcmb.mb.Failed(meteredCmixMessage{M: m.Marshal()})
+}
diff --git a/storage/utility/meteredCmixMessageBuffer_test.go b/storage/utility/meteredCmixMessageBuffer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bd2edcd259cb1bff0b04068d6c5fa8600fb16d82
--- /dev/null
+++ b/storage/utility/meteredCmixMessageBuffer_test.go
@@ -0,0 +1,222 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package utility
+
+import (
+	"bytes"
+	"encoding/json"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/elixxir/primitives/format"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+// Test happy path of meteredCmixMessage.SaveMessage().
+func Test_meteredCmixMessageHandler_SaveMessage(t *testing.T) {
+	// Set up test values
+	mcmh := &meteredCmixMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, _ := makeTestMeteredCmixMessage(10)
+
+	for _, msg := range testMsgs {
+		key := makeStoredMessageKey("testKey", mcmh.HashMessage(msg))
+
+		// Save message
+		err := mcmh.SaveMessage(kv, msg, key)
+		if err != nil {
+			t.Errorf("SaveMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		// Try to get message
+		obj, err := kv.Get(key, 0)
+		if err != nil {
+			t.Errorf("Get() returned an error: %v", err)
+		}
+
+		msgData, err := json.Marshal(&msg)
+		if err != nil {
+			t.Fatalf("Could not marshal message: %v", err)
+		}
+
+		// Test if message retrieved matches expected
+		if !bytes.Equal(msgData, obj.Data) {
+			t.Errorf("SaveMessage() returned versioned object with incorrect data."+
+				"\n\texpected: %v\n\treceived: %v",
+				msg, obj.Data)
+		}
+	}
+}
+
+// Test happy path of meteredCmixMessage.LoadMessage().
+func Test_meteredCmixMessageHandler_LoadMessage(t *testing.T) {
+	// Set up test values
+	mcmh := &meteredCmixMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, _ := makeTestMeteredCmixMessage(10)
+
+	for i, msg := range testMsgs {
+		key := makeStoredMessageKey("testKey", mcmh.HashMessage(msg))
+
+		// Save message
+		if err := mcmh.SaveMessage(kv, msg, key); err != nil {
+			t.Errorf("SaveMessage() returned an error: %v", err)
+		}
+
+		// Try to load message
+		testMsg, err := mcmh.LoadMessage(kv, key)
+		if err != nil {
+			t.Errorf("LoadMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		testMcm := testMsg.(meteredCmixMessage)
+
+		// Test if message loaded matches expected
+		if !bytes.Equal(msg.M, testMcm.M) || msg.Count != testMcm.Count || !msg.Timestamp.Equal(testMcm.Timestamp) {
+			t.Errorf("LoadMessage() returned an unexpected object (round %d)."+
+				"\n\texpected: %+v\n\treceived: %+v", i, msg, testMsg.(meteredCmixMessage))
+		}
+	}
+}
+
+// Test happy path of meteredCmixMessage.DeleteMessage().
+func Test_meteredCmixMessageHandler_DeleteMessage(t *testing.T) {
+	// Set up test values
+	mcmh := &meteredCmixMessageHandler{}
+	kv := versioned.NewKV(make(ekv.Memstore))
+	testMsgs, _ := makeTestMeteredCmixMessage(10)
+
+	for _, msg := range testMsgs {
+		key := makeStoredMessageKey("testKey", mcmh.HashMessage(msg))
+
+		// Save message
+		err := mcmh.SaveMessage(kv, msg, key)
+		if err != nil {
+			t.Errorf("SaveMessage() returned an error."+
+				"\n\texpected: %v\n\trecieved: %v", nil, err)
+		}
+
+		err = mcmh.DeleteMessage(kv, key)
+		if err != nil {
+			t.Errorf("DeleteMessage() produced an error: %v", err)
+		}
+
+		// Try to get message
+		_, err = kv.Get(key, 0)
+		if err == nil {
+			t.Error("Get() did not return an error.")
+		}
+	}
+}
+
+// Smoke test of meteredCmixMessageHandler.
+func Test_meteredCmixMessageHandler_Smoke(t *testing.T) {
+	// Set up test messages
+	testMsgs := makeTestFormatMessages(2)
+
+	// Create new buffer
+	mcmb, err := NewMeteredCmixMessageBuffer(versioned.NewKV(make(ekv.Memstore)), "testKey")
+	if err != nil {
+		t.Errorf("NewMeteredCmixMessageBuffer() returned an error."+
+			"\n\texpected: %v\n\trecieved: %v", nil, err)
+	}
+
+	// Add two messages
+
+	mcmb.Add(testMsgs[0])
+	mcmb.Add(testMsgs[1])
+
+	if len(mcmb.mb.messages) != 2 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			2, len(mcmb.mb.messages))
+	}
+
+	msg, _, _, exists := mcmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	mcmb.Remove(msg)
+
+	if len(mcmb.mb.messages) != 1 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			1, len(mcmb.mb.messages))
+	}
+
+	msg, _, _, exists = mcmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	if len(mcmb.mb.messages) != 0 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			0, len(mcmb.mb.messages))
+	}
+	mcmb.Failed(msg)
+
+	if len(mcmb.mb.messages) != 1 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			1, len(mcmb.mb.messages))
+	}
+
+	msg, _, _, exists = mcmb.Next()
+	if !exists {
+		t.Error("Next() did not find any messages in buffer.")
+	}
+	mcmb.Remove(msg)
+
+	msg, _, _, exists = mcmb.Next()
+	if exists {
+		t.Error("Next() found a message in the buffer when it should be empty.")
+	}
+	mcmb.Remove(msg)
+
+	if len(mcmb.mb.messages) != 0 {
+		t.Errorf("Unexpected length of buffer.\n\texpected: %d\n\trecieved: %d",
+			0, len(mcmb.mb.messages))
+	}
+
+}
+
+// makeTestMeteredCmixMessage creates a list of messages with random data and the
+// expected map after they are added to the buffer.
+func makeTestMeteredCmixMessage(n int) ([]meteredCmixMessage, map[MessageHash]struct{}) {
+	mcmh := &meteredCmixMessageHandler{}
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	mh := map[MessageHash]struct{}{}
+	msgs := make([]meteredCmixMessage, n)
+	for i := range msgs {
+		payload := make([]byte, 128)
+		prng.Read(payload)
+		msgs[i] = meteredCmixMessage{
+			M:         payload,
+			Count:     uint(prng.Uint64()),
+			Timestamp: time.Unix(0, 0),
+		}
+		mh[mcmh.HashMessage(msgs[i])] = struct{}{}
+	}
+
+	return msgs, mh
+}
+
+// makeTestFormatMessages creates a list of messages with random data.
+func makeTestFormatMessages(n int) []format.Message {
+	prng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	msgs := make([]format.Message, n)
+	for i := range msgs {
+		msgs[i] = format.NewMessage(128)
+		payload := make([]byte, 128)
+		prng.Read(payload)
+		msgs[i].SetPayloadA(payload)
+		prng.Read(payload)
+		msgs[i].SetPayloadB(payload)
+	}
+
+	return msgs
+}
diff --git a/storage/versioned/kv.go b/storage/versioned/kv.go
new file mode 100644
index 0000000000000000000000000000000000000000..083944e4db7c3ebefae3ea711811fc49fbc80c20
--- /dev/null
+++ b/storage/versioned/kv.go
@@ -0,0 +1,156 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package versioned
+
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/ekv"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+const PrefixSeparator = "/"
+
+// MakePartnerPrefix creates a string prefix
+// to denote who a conversation or relationship is with
+func MakePartnerPrefix(id *id.ID) string {
+	return fmt.Sprintf("Partner:%v", id.String())
+}
+
+// Upgrade functions must be of this type
+type Upgrade func(oldObject *Object) (*Object,
+	error)
+
+type root struct {
+	data ekv.KeyValue
+}
+
+// KV stores versioned data and Upgrade functions
+type KV struct {
+	r      *root
+	prefix string
+}
+
+// Create a versioned key/value store backed by something implementing KeyValue
+func NewKV(data ekv.KeyValue) *KV {
+	newKV := KV{}
+	root := root{}
+
+	root.data = data
+
+	newKV.r = &root
+
+	return &newKV
+}
+
+// Get gets and upgrades data stored in the key/value store
+// Make sure to inspect the version returned in the versioned object
+func (v *KV) Get(key string, version uint64) (*Object, error) {
+	key = v.makeKey(key, version)
+	jww.TRACE.Printf("Get %p with key %v", v.r.data, key)
+	// Get raw data
+	result := Object{}
+	err := v.r.data.Get(key, &result)
+	if err != nil {
+		return nil, err
+	}
+	return &result, nil
+}
+
+type UpgradeTable struct {
+	CurrentVersion uint64
+	Table          []Upgrade
+}
+
+// Get gets and upgrades data stored in the key/value store
+// Make sure to inspect the version returned in the versioned object
+func (v *KV) GetAndUpgrade(key string, ut UpgradeTable) (*Object, error) {
+	version := ut.CurrentVersion
+	baseKey := key
+	key = v.makeKey(baseKey, version)
+
+	if uint64(len(ut.Table)) != version {
+		jww.FATAL.Panicf("Cannot get upgrade for %s: table lengh (%d) "+
+			"does not match current version (%d)", key, len(ut.Table),
+			version)
+	}
+	var result *Object
+	// NOTE: Upgrades do not happen on the current version, so we check to
+	// see if version-1, version-2, and so on exist to find out if an
+	// earlier version of this object exists.
+	version++
+	for version != 0 {
+		version--
+		key = v.makeKey(baseKey, version)
+		jww.TRACE.Printf("Get %p with key %v", v.r.data, key)
+
+		// Get raw data
+		result = &Object{}
+		err := v.r.data.Get(key, result)
+		// Break when we find the *newest* version of the object
+		// in the data store.
+		if err == nil {
+			break
+		}
+	}
+
+	if result == nil || len(result.Data) == 0 {
+		return nil, errors.Errorf(
+			"Failed to get key and upgrade it for %s",
+			v.makeKey(baseKey, ut.CurrentVersion))
+	}
+
+	var err error
+	initialVersion := result.Version
+	for result.Version < uint64(len(ut.Table)) {
+		oldVersion := result.Version
+		result, err = ut.Table[oldVersion](result)
+		if err != nil || oldVersion == result.Version {
+			jww.FATAL.Panicf("failed to upgrade key %s from "+
+				"version %v, initial version %v", key,
+				oldVersion, initialVersion)
+		}
+	}
+
+	return result, nil
+}
+
+// delete removes a given key from the data store
+func (v *KV) Delete(key string, version uint64) error {
+	key = v.makeKey(key, version)
+	jww.TRACE.Printf("delete %p with key %v", v.r.data, key)
+	return v.r.data.Delete(key)
+}
+
+// Set upserts new data into the storage
+// When calling this, you are responsible for prefixing the key with the correct
+// type optionally unique id! Call MakeKeyWithPrefix() to do so.
+func (v *KV) Set(key string, version uint64, object *Object) error {
+	key = v.makeKey(key, version)
+	jww.TRACE.Printf("Set %p with key %v", v.r.data, key)
+	return v.r.data.Set(key, object)
+}
+
+//Returns a new KV with the new prefix
+func (v *KV) Prefix(prefix string) *KV {
+	kvPrefix := KV{
+		r:      v.r,
+		prefix: v.prefix + prefix + PrefixSeparator,
+	}
+	return &kvPrefix
+}
+
+//Returns the key with all prefixes appended
+func (v *KV) GetFullKey(key string, version uint64) string {
+	return v.makeKey(key, version)
+}
+
+func (v *KV) makeKey(key string, version uint64) string {
+	return fmt.Sprintf("%s%s_%d", v.prefix, key, version)
+}
diff --git a/storage/versioned/kv_test.go b/storage/versioned/kv_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..58ec70ad4f77972a326d089df74263e7aeae80fb
--- /dev/null
+++ b/storage/versioned/kv_test.go
@@ -0,0 +1,192 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package versioned
+
+import (
+	"bytes"
+	"errors"
+	"testing"
+	"time"
+
+	"gitlab.com/elixxir/ekv"
+)
+
+// KV Get should call the Upgrade function when it's available
+func TestVersionedKV_Get_Err(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	key := vkv.GetFullKey("test", 0)
+	result, err := vkv.Get(key, 0)
+	if err == nil {
+		t.Error("Getting a key that didn't exist should have" +
+			" returned an error")
+	}
+	if result != nil {
+		t.Error("Getting a key that didn't exist shouldn't " +
+			"have returned data")
+	}
+}
+
+// Test versioned KV happy path
+func TestVersionedKV_GetUpgrade(t *testing.T) {
+	// Set up a dummy KV with the required data
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	key := vkv.GetFullKey("test", 0)
+	original := Object{
+		Version:   0,
+		Timestamp: time.Now(),
+		Data:      []byte("not upgraded"),
+	}
+	originalSerialized := original.Marshal()
+	kv[key] = originalSerialized
+
+	upgrade := []Upgrade{func(oldObject *Object) (*Object, error) {
+		return &Object{
+			Version:   1,
+			Timestamp: time.Now(),
+			Data:      []byte("this object was upgraded from v0 to v1"),
+		}, nil
+	}}
+
+	result, err := vkv.GetAndUpgrade("test", UpgradeTable{CurrentVersion: 1,
+		Table: upgrade})
+	if err != nil {
+		t.Fatalf("Error getting something that should have been in: %v",
+			err)
+	}
+	if !bytes.Equal(result.Data,
+		[]byte("this object was upgraded from v0 to v1")) {
+		t.Errorf("Upgrade should have overwritten data."+
+			" result data: %q", result.Data)
+	}
+}
+
+// Test versioned KV key not found path
+func TestVersionedKV_GetUpgrade_KeyNotFound(t *testing.T) {
+	// Set up a dummy KV with the required data
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	key := "test"
+
+	upgrade := []Upgrade{func(oldObject *Object) (*Object, error) {
+		return &Object{
+			Version:   1,
+			Timestamp: time.Now(),
+			Data:      []byte("this object was upgraded from v0 to v1"),
+		}, nil
+	}}
+
+	_, err := vkv.GetAndUpgrade(key, UpgradeTable{CurrentVersion: 1,
+		Table: upgrade})
+	if err == nil {
+		t.Fatalf("Error getting something that shouldn't be there!")
+	}
+}
+
+// Test versioned KV upgrade func returns error path
+func TestVersionedKV_GetUpgrade_UpgradeReturnsError(t *testing.T) {
+	// Set up a dummy KV with the required data
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	key := vkv.GetFullKey("test", 0)
+	original := Object{
+		Version:   0,
+		Timestamp: time.Now(),
+		Data:      []byte("not upgraded"),
+	}
+	originalSerialized := original.Marshal()
+	kv[key] = originalSerialized
+
+	upgrade := []Upgrade{func(oldObject *Object) (*Object, error) {
+		return &Object{}, errors.New("test error")
+	}}
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("The code did not panic")
+		}
+	}()
+
+	_, _ = vkv.GetAndUpgrade("test", UpgradeTable{CurrentVersion: 1,
+		Table: upgrade})
+}
+
+// Test delete key happy path
+func TestVersionedKV_Delete(t *testing.T) {
+	// Set up a dummy KV with the required data
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	key := vkv.GetFullKey("test", 0)
+	original := Object{
+		Version:   0,
+		Timestamp: time.Now(),
+		Data:      []byte("not upgraded"),
+	}
+	originalSerialized := original.Marshal()
+	kv[key] = originalSerialized
+
+	err := vkv.Delete("test", 0)
+	if err != nil {
+		t.Fatalf("Error getting something that should have been in: %v",
+			err)
+	}
+
+	if _, ok := kv[key]; ok {
+		t.Fatal("Key still exists in kv map")
+	}
+}
+
+// Test Get without Upgrade path
+func TestVersionedKV_Get(t *testing.T) {
+	// Set up a dummy KV with the required data
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	originalVersion := uint64(0)
+	key := vkv.GetFullKey("test", originalVersion)
+	original := Object{
+		Version:   originalVersion,
+		Timestamp: time.Now(),
+		Data:      []byte("not upgraded"),
+	}
+	originalSerialized := original.Marshal()
+	kv[key] = originalSerialized
+
+	result, err := vkv.Get("test", originalVersion)
+	if err != nil {
+		t.Fatalf("Error getting something that should have been in: %v",
+			err)
+	}
+	if !bytes.Equal(result.Data, []byte("not upgraded")) {
+		t.Errorf("Upgrade should not have overwritten data."+
+			" result data: %q", result.Data)
+	}
+}
+
+// Test that Set puts data in the store
+func TestVersionedKV_Set(t *testing.T) {
+	kv := make(ekv.Memstore)
+	vkv := NewKV(kv)
+	originalVersion := uint64(1)
+	key := vkv.GetFullKey("test", originalVersion)
+	original := Object{
+		Version:   originalVersion,
+		Timestamp: time.Now(),
+		Data:      []byte("not upgraded"),
+	}
+	err := vkv.Set("test", originalVersion, &original)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Store should now have data in it at that key
+	_, ok := kv[key]
+	if !ok {
+		t.Error("data store didn't have anything in the key")
+	}
+}
diff --git a/storage/versioned/object.go b/storage/versioned/object.go
new file mode 100644
index 0000000000000000000000000000000000000000..8cdec88c27c59dc99284089996fbcd2e2c6394a0
--- /dev/null
+++ b/storage/versioned/object.go
@@ -0,0 +1,49 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package versioned
+
+import (
+	"encoding/json"
+	"fmt"
+	"time"
+)
+
+// Object is used by VersionedKeyValue to keep track of
+// versioning and time of storage
+type Object struct {
+	// Used to determine version Upgrade, if any
+	Version uint64
+
+	// Set when this object is written
+	Timestamp time.Time
+
+	// Serialized version of original object
+	Data []byte
+}
+
+// Unmarshal deserializes a Object from a byte slice. It's used to
+// make these storable in a KeyValue.
+// Object exports all fields and they have simple types, so
+// json.Unmarshal works fine.
+func (v *Object) Unmarshal(data []byte) error {
+	return json.Unmarshal(data, v)
+}
+
+// Marshal serializes a Object into a byte slice. It's used to
+// make these storable in a KeyValue.
+// Object exports all fields and they have simple types, so
+// json.Marshal works fine.
+func (v *Object) Marshal() []byte {
+	d, err := json.Marshal(v)
+	// Not being to marshal this simple object means something is really
+	// wrong
+	if err != nil {
+		panic(fmt.Sprintf("Could not marshal: %+v", v))
+	}
+	return d
+}
diff --git a/storage/versioned/object_test.go b/storage/versioned/object_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ba12575a327269b2094ff7c73d90fa2a7873f2c
--- /dev/null
+++ b/storage/versioned/object_test.go
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package versioned
+
+import (
+	"reflect"
+	"testing"
+	"time"
+)
+
+// Shows that all fields can be serialized/deserialized correctly using json
+func TestVersionedObject_MarshalUnmarshal(t *testing.T) {
+	original := Object{
+		Version:   8,
+		Timestamp: time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC),
+		Data:      []byte("original text"),
+	}
+
+	marshalled := original.Marshal()
+
+	unmarshalled := Object{}
+	err := unmarshalled.Unmarshal(marshalled)
+	if err != nil {
+		// Should never happen
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(original, unmarshalled) {
+		t.Error("Original and deserialized objects not equal")
+	}
+	t.Logf("%+v", unmarshalled)
+}
diff --git a/storage/versionedkv.go b/storage/versionedkv.go
deleted file mode 100644
index edd315658655e27a0c4a278ed9781615db5ce9d0..0000000000000000000000000000000000000000
--- a/storage/versionedkv.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package storage
-
-import (
-	"encoding/json"
-	"fmt"
-	"gitlab.com/elixxir/ekv"
-	"strconv"
-	"strings"
-)
-
-// MakeKeyPrefix provides a helper with a data type and a version
-// TODO: We might need a separator string here, or a fixed number of
-//       digits available to the version string
-//  Otherwise version 10 could be mistaken for version 1! Bad news
-//  For now, let's hope a semicolon won't be part of the rest of the key
-//  It's not in base64, so maybe it will be fine
-func MakeKeyPrefix(dataType string, version uint64) string {
-	return dataType + strconv.FormatUint(version, 10) + ";"
-}
-
-// VersionedObject is used by VersionedKeyValue to keep track of
-// versioning and time of storage
-type VersionedObject struct {
-	// Used to determine version upgrade, if any
-	Version uint64
-
-	// Marshal to/from time.Time using Time.MarshalText and
-	// Time.UnmarshalText
-	Timestamp []byte
-
-	// Serialized version of original object
-	Data []byte
-}
-
-// Unmarshal deserializes a VersionedObject from a byte slice. It's used to
-// make these storable in a KeyValue.
-// VersionedObject exports all fields and they have simple types, so
-// json.Unmarshal works fine.
-func (v *VersionedObject) Unmarshal(data []byte) error {
-	return json.Unmarshal(data, v)
-}
-
-// Marshal serializes a VersionedObject into a byte slice. It's used to
-// make these storable in a KeyValue.
-// VersionedObject exports all fields and they have simple types, so
-// json.Marshal works fine.
-func (v *VersionedObject) Marshal() []byte {
-	d, err := json.Marshal(v)
-	// Not being to marshal this simple object means something is really
-	// wrong
-	if err != nil {
-		panic(fmt.Sprintf("Could not marshal: %+v", v))
-	}
-	return d
-}
-
-// Upgrade functions must be of this type
-type upgrade func(key string, oldObject *VersionedObject) (*VersionedObject,
-	error)
-
-// VersionedKV stores versioned data and upgrade functions
-type VersionedKV struct {
-	upgradeTable map[string]upgrade
-	data         ekv.KeyValue
-}
-
-// Create a versioned key/value store backed by something implementing KeyValue
-func NewVersionedKV(data ekv.KeyValue) *VersionedKV {
-	newKV := new(VersionedKV)
-	// Add new upgrade functions to this upgrade table
-	newKV.upgradeTable = make(map[string]upgrade)
-	// All upgrade functions should upgrade to the latest version. You can
-	// call older upgrade functions if you need to. Upgrade functions don't
-	// change the key or store the upgraded version of the data in the
-	// key/value store. There's no mechanism built in for this -- users
-	// should always make the key prefix before calling Set, and if they
-	// want the upgraded data persisted they should call Set with the
-	// upgraded data.
-	newKV.upgradeTable[MakeKeyPrefix("test", 0)] = func(key string,
-		oldObject *VersionedObject) (*VersionedObject, error) {
-		return &VersionedObject{
-			Version: 1,
-			// Upgrade functions don't need to update the timestamp
-			Timestamp: oldObject.Timestamp,
-			Data: []byte("this object was upgraded from v0" +
-				" to v1"),
-		}, nil
-	}
-	newKV.data = data
-	return newKV
-}
-
-// Get gets and upgrades data stored in the key/value store
-// Make sure to inspect the version returned in the versioned object
-func (v *VersionedKV) Get(key string) (*VersionedObject, error) {
-	// Get raw data
-	result := VersionedObject{}
-	err := v.data.Get(key, &result)
-	if err != nil {
-		return nil, err
-	}
-	// If the key starts with a version tag that we can find in the table,
-	// we should call that function to upgrade it
-	for version, upgrade := range v.upgradeTable {
-		if strings.HasPrefix(key, version) {
-			// We should run this upgrade function
-			// The user of this function must update the key
-			// based on the version returned in this
-			// versioned object!
-			return upgrade(key, &result)
-		}
-	}
-	return &result, nil
-}
-
-// Set upserts new data into the storage
-// When calling this, you are responsible for prefixing the key with the correct
-// type and version! Call MakeKeyPrefix() to do so.
-func (v *VersionedKV) Set(key string, object *VersionedObject) error {
-	return v.data.Set(key, object)
-}
diff --git a/storage/versionedkv_test.go b/storage/versionedkv_test.go
deleted file mode 100644
index 4a7ccb8754bd6b70df058bc2a3c20707f9c2a5e4..0000000000000000000000000000000000000000
--- a/storage/versionedkv_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package storage
-
-import (
-	"bytes"
-	"gitlab.com/elixxir/ekv"
-	"reflect"
-	"testing"
-	"time"
-)
-
-// Shows that all fields can be serialized/deserialized correctly using json
-func TestVersionedObject_MarshalUnmarshal(t *testing.T) {
-	sometime, err := time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC).MarshalText()
-	if err != nil {
-		// Should never happen
-		t.Fatal(err)
-	}
-
-	original := VersionedObject{
-		Version:   8,
-		Timestamp: sometime,
-		Data:      []byte("original text"),
-	}
-
-	marshalled := original.Marshal()
-
-	unmarshalled := VersionedObject{}
-	err = unmarshalled.Unmarshal(marshalled)
-	if err != nil {
-		// Should never happen
-		t.Fatal(err)
-	}
-
-	if !reflect.DeepEqual(original, unmarshalled) {
-		t.Error("Original and serialized/deserialized objects not equal")
-	}
-	t.Logf("%+v", unmarshalled)
-}
-
-// VersionedKV Get should call the upgrade function when it's available
-func TestVersionedKV_Get_Err(t *testing.T) {
-	kv := make(ekv.Memstore)
-	vkv := NewVersionedKV(kv)
-	key := MakeKeyPrefix("test", 0) + "12345"
-	result, err := vkv.Get(key)
-	if err == nil {
-		t.Error("Getting a key that didn't exist should have returned an error")
-	}
-	if result != nil {
-		t.Error("Getting a key that didn't exist shouldn't have returned data")
-	}
-}
-
-// Test versioned KV upgrade path
-func TestVersionedKV_Get_Upgrade(t *testing.T) {
-	// Set up a dummy KV with the required data
-	kv := make(ekv.Memstore)
-	vkv := NewVersionedKV(kv)
-	key := MakeKeyPrefix("test", 0) + "12345"
-	now := time.Now()
-	nowText, err := now.MarshalText()
-	if err != nil {
-		//Should never happen
-		t.Fatal(err)
-	}
-	original := VersionedObject{
-		Version:   0,
-		Timestamp: nowText,
-		Data:      []byte("not upgraded"),
-	}
-	originalSerialized := original.Marshal()
-	if err != nil {
-		t.Fatal(err)
-	}
-	kv[key] = originalSerialized
-
-	result, err := vkv.Get(key)
-	if err != nil {
-		t.Fatalf("Error getting something that should have been in: %v", err)
-	}
-	if !bytes.Equal(result.Data, []byte("this object was upgraded from v0 to v1")) {
-		t.Errorf("upgrade should have overwritten data. result data: %q", result.Data)
-	}
-}
-
-// Test Get without upgrade path
-func TestVersionedKV_Get(t *testing.T) {
-	// Set up a dummy KV with the required data
-	kv := make(ekv.Memstore)
-	vkv := NewVersionedKV(kv)
-	originalVersion := uint64(1)
-	key := MakeKeyPrefix("test", originalVersion) + "12345"
-	now := time.Now()
-	nowText, err := now.MarshalText()
-	if err != nil {
-		//Should never happen
-		t.Fatal(err)
-	}
-	original := VersionedObject{
-		Version:   originalVersion,
-		Timestamp: nowText,
-		Data:      []byte("not upgraded"),
-	}
-	originalSerialized := original.Marshal()
-	if err != nil {
-		t.Fatal(err)
-	}
-	kv[key] = originalSerialized
-
-	result, err := vkv.Get(key)
-	if err != nil {
-		t.Fatalf("Error getting something that should have been in: %v", err)
-	}
-	if !bytes.Equal(result.Data, []byte("not upgraded")) {
-		t.Errorf("upgrade should not have overwritten data. result data: %q", result.Data)
-	}
-}
-
-// Test that Set puts data in the store
-func TestVersionedKV_Set(t *testing.T) {
-	kv := make(ekv.Memstore)
-	vkv := NewVersionedKV(kv)
-	originalVersion := uint64(1)
-	key := MakeKeyPrefix("test", originalVersion) + "12345"
-	now := time.Now()
-	nowText, err := now.MarshalText()
-	if err != nil {
-		//Should never happen
-		t.Fatal(err)
-	}
-	original := VersionedObject{
-		Version:   originalVersion,
-		Timestamp: nowText,
-		Data:      []byte("not upgraded"),
-	}
-	err = vkv.Set(key, &original)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Store should now have data in it at that key
-	_, ok := kv[key]
-	if !ok {
-		t.Error("data store didn't have anything in the key")
-	}
-}
diff --git a/switchboard/any.go b/switchboard/any.go
new file mode 100644
index 0000000000000000000000000000000000000000..8853200814ab135f340c2354a79e4cf2ee4e51b4
--- /dev/null
+++ b/switchboard/any.go
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// ID to respond to any message type
+const AnyType = message.NoType
+
+//ID to respond to any user
+func AnyUser() *id.ID {
+	return &id.ZeroUser
+}
diff --git a/switchboard/any_test.go b/switchboard/any_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..557faa519e97c0bf44a05b19b8c90b8aeef20bbb
--- /dev/null
+++ b/switchboard/any_test.go
@@ -0,0 +1,21 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+//tests that AnyUser returns the correct user
+func TestAnyUser(t *testing.T) {
+	au := AnyUser()
+	if !au.Cmp(&id.ZeroUser) {
+		t.Errorf("Wrong user returned from AnyUser")
+	}
+}
diff --git a/switchboard/byID.go b/switchboard/byID.go
new file mode 100644
index 0000000000000000000000000000000000000000..9048f527c122f20abf5ad7930148e66be5055653
--- /dev/null
+++ b/switchboard/byID.go
@@ -0,0 +1,70 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"github.com/golang-collections/collections/set"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+type byId struct {
+	list    map[id.ID]*set.Set
+	generic *set.Set
+}
+
+// builds a new byID structure
+// registers an empty ID and the designated zero ID as generic
+func newById() *byId {
+	bi := &byId{
+		list:    make(map[id.ID]*set.Set),
+		generic: set.New(),
+	}
+
+	//make the zero IDs, which are defined as any, all point to the generic
+	bi.list[*AnyUser()] = bi.generic
+	bi.list[id.ID{}] = bi.generic
+
+	return bi
+}
+
+// returns a set associated with the passed ID unioned with the generic return
+func (bi *byId) Get(uid *id.ID) *set.Set {
+	lookup, ok := bi.list[*uid]
+	if !ok {
+		return bi.generic
+	} else {
+		return lookup.Union(bi.generic)
+	}
+}
+
+// adds a listener to a set for the given ID. Creates a new set to add it to if
+// the set does not exist
+func (bi *byId) Add(uid *id.ID, l Listener) *set.Set {
+	s, ok := bi.list[*uid]
+	if !ok {
+		s = set.New(l)
+		bi.list[*uid] = s
+	} else {
+		s.Insert(l)
+	}
+
+	return s
+}
+
+// Removes the passed listener from the set for UserID and
+// deletes the set if it is empty if the ID is not a generic one
+func (bi *byId) Remove(uid *id.ID, l Listener) {
+	s, ok := bi.list[*uid]
+	if ok {
+		s.Remove(l)
+
+		if s.Len() == 0 && !uid.Cmp(AnyUser()) && !uid.Cmp(&id.ID{}) {
+			delete(bi.list, *uid)
+		}
+	}
+}
diff --git a/switchboard/byID_test.go b/switchboard/byID_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..52c294b7c570321c45ad07f285d1a6f35ec4af7e
--- /dev/null
+++ b/switchboard/byID_test.go
@@ -0,0 +1,315 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"github.com/golang-collections/collections/set"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+// tests the newByID functions forms a properly constructed byId
+func TestById_newById(t *testing.T) {
+	nbi := newById()
+
+	if nbi.list == nil {
+		t.Errorf("No list created")
+	}
+
+	if nbi.generic == nil {
+		t.Errorf("No generic created")
+	}
+
+	if nbi.generic != nbi.list[id.ZeroUser] {
+		t.Errorf("zero user not registered as generic")
+	}
+
+	if nbi.generic != nbi.list[id.ID{}] {
+		t.Errorf("zero id not registered as generic")
+	}
+}
+
+// tests that when nothing has been added an empty set is returned
+func TestById_Get_Empty(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	s := nbi.Get(uid)
+
+	if s.Len() != 0 {
+		t.Errorf("Should not have returned a set")
+	}
+}
+
+//tests that getting a set for a specific ID returns that set
+func TestById_Get_Selected(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	set1 := set.New(0)
+
+	nbi.list[*uid] = set1
+
+	s := nbi.Get(uid)
+
+	if s.Len() == 0 {
+		t.Errorf("Should have returned a set")
+	}
+
+	if !s.SubsetOf(set1) || !set1.SubsetOf(s) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+// tests that when getting a specific ID which is not there returns the generic
+// set if is present
+func TestById_Get_Generic(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	nbi.generic.Insert(0)
+
+	s := nbi.Get(uid)
+
+	if s.Len() == 0 {
+		t.Errorf("Should have returned a set")
+	}
+
+	if !s.SubsetOf(nbi.generic) || !nbi.generic.SubsetOf(s) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+// tests that when getting a specific ID is there and there are elements
+// in the generic that the union of the two is returned
+func TestById_Get_GenericSelected(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	set1 := set.New(0)
+
+	nbi.list[*uid] = set1
+
+	nbi.generic.Insert(1)
+
+	s := nbi.Get(uid)
+
+	if s.Len() == 0 {
+		t.Errorf("Should have returned a set")
+	}
+
+	setUnion := set1.Union(nbi.generic)
+
+	if !s.SubsetOf(setUnion) || !setUnion.SubsetOf(s) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+// Tests that when adding to a set which does not exist, the set is created
+func TestById_Add_New(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	l := &funcListener{}
+
+	nbi.Add(uid, l)
+
+	s := nbi.list[*uid]
+
+	if s.Len() != 1 {
+		t.Errorf("Should a set of the wrong size")
+	}
+
+	if !s.Has(l) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+// Tests that when adding to a set which does exist, the set is retained and
+// added to
+func TestById_Add_Old(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	l1 := &funcListener{}
+	l2 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbi.list[*uid] = set1
+
+	nbi.Add(uid, l2)
+
+	s := nbi.list[*uid]
+
+	if s.Len() != 2 {
+		t.Errorf("Should have returned a set")
+	}
+
+	if !s.Has(l1) {
+		t.Errorf("Set does not include the initial listener")
+	}
+
+	if !s.Has(l2) {
+		t.Errorf("Set does not include the new listener")
+	}
+}
+
+// Tests that when adding to a generic ID, the listener is added to the
+// generic set
+func TestById_Add_Generic(t *testing.T) {
+	nbi := newById()
+
+	l1 := &funcListener{}
+	l2 := &funcListener{}
+
+	nbi.Add(&id.ID{}, l1)
+	nbi.Add(AnyUser(), l2)
+
+	s := nbi.generic
+
+	if s.Len() != 2 {
+		t.Errorf("Should have returned a set of size 2")
+	}
+
+	if !s.Has(l1) {
+		t.Errorf("Set does not include the ZeroUser listener")
+	}
+
+	if !s.Has(l2) {
+		t.Errorf("Set does not include the empty user listener")
+	}
+}
+
+// Tests that removing a listener from a set with multiple listeners removes the
+// listener but maintains the set
+func TestById_Remove_ManyInSet(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	l1 := &funcListener{}
+	l2 := &funcListener{}
+
+	set1 := set.New(l1, l2)
+
+	nbi.list[*uid] = set1
+
+	nbi.Remove(uid, l1)
+
+	if _, ok := nbi.list[*uid]; !ok {
+		t.Errorf("Set removed when it should not have been")
+	}
+
+	if set1.Len() != 1 {
+		t.Errorf("Set is incorrect length after the remove call: %v",
+			set1.Len())
+	}
+
+	if set1.Has(l1) {
+		t.Errorf("Listener 1 still in set, it should not be")
+	}
+
+	if !set1.Has(l2) {
+		t.Errorf("Listener 2 not still in set, it should be")
+	}
+
+}
+
+// Tests that removing a listener from a set with a single listener removes the
+// listener and the set
+func TestById_Remove_SingleInSet(t *testing.T) {
+	nbi := newById()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	l1 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbi.list[*uid] = set1
+
+	nbi.Remove(uid, l1)
+
+	if _, ok := nbi.list[*uid]; ok {
+		t.Errorf("Set not removed when it should have been")
+	}
+
+	if set1.Len() != 0 {
+		t.Errorf("Set is incorrect length after the remove call: %v",
+			set1.Len())
+	}
+
+	if set1.Has(l1) {
+		t.Errorf("Listener 1 still in set, it should not be")
+	}
+}
+
+// Tests that removing a listener from a set with a single listener removes the
+// listener and not the set when the ID iz ZeroUser
+func TestById_Remove_SingleInSet_ZeroUser(t *testing.T) {
+	nbi := newById()
+
+	uid := &id.ZeroUser
+
+	l1 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbi.list[*uid] = set1
+
+	nbi.Remove(uid, l1)
+
+	if _, ok := nbi.list[*uid]; !ok {
+		t.Errorf("Set removed when it should not have been")
+	}
+
+	if set1.Len() != 0 {
+		t.Errorf("Set is incorrect length after the remove call: %v",
+			set1.Len())
+	}
+
+	if set1.Has(l1) {
+		t.Errorf("Listener 1 still in set, it should not be")
+	}
+}
+
+// Tests that removing a listener from a set with a single listener removes the
+// listener and not the set when the ID iz ZeroUser
+func TestById_Remove_SingleInSet_EmptyUser(t *testing.T) {
+	nbi := newById()
+
+	uid := &id.ID{}
+
+	l1 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbi.list[*uid] = set1
+
+	nbi.Remove(uid, l1)
+
+	if _, ok := nbi.list[*uid]; !ok {
+		t.Errorf("Set removed when it should not have been")
+	}
+
+	if set1.Len() != 0 {
+		t.Errorf("Set is incorrect length after the remove call: %v",
+			set1.Len())
+	}
+
+	if set1.Has(l1) {
+		t.Errorf("Listener 1 still in set, it should not be")
+	}
+}
diff --git a/switchboard/byType.go b/switchboard/byType.go
new file mode 100644
index 0000000000000000000000000000000000000000..ccf0c8c7375a6e7f4f9428e1a8b371c62f0fd267
--- /dev/null
+++ b/switchboard/byType.go
@@ -0,0 +1,71 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"github.com/golang-collections/collections/set"
+	"gitlab.com/elixxir/client/interfaces/message"
+)
+
+type byType struct {
+	list    map[message.Type]*set.Set
+	generic *set.Set
+}
+
+// builds a new byType structure
+// registers an AnyType as generic
+func newByType() *byType {
+	bt := &byType{
+		list:    make(map[message.Type]*set.Set),
+		generic: set.New(),
+	}
+
+	// make the zero messages, which are defined as AnyType,
+	// point to the generic
+	bt.list[AnyType] = bt.generic
+
+	return bt
+}
+
+// returns a set associated with the passed messageType unioned with the
+// generic return
+func (bt *byType) Get(messageType message.Type) *set.Set {
+	lookup, ok := bt.list[messageType]
+	if !ok {
+		return bt.generic
+	} else {
+		return lookup.Union(bt.generic)
+	}
+}
+
+// adds a listener to a set for the given messageType. Creates a new set to add
+// it to if the set does not exist
+func (bt *byType) Add(messageType message.Type, r Listener) *set.Set {
+	s, ok := bt.list[messageType]
+	if !ok {
+		s = set.New(r)
+		bt.list[messageType] = s
+	} else {
+		s.Insert(r)
+	}
+
+	return s
+}
+
+// Removes the passed listener from the set for messageType and
+// deletes the set if it is empty and the type is not AnyType
+func (bt *byType) Remove(mt message.Type, l Listener) {
+	s, ok := bt.list[mt]
+	if ok {
+		s.Remove(l)
+
+		if s.Len() == 0 && mt != AnyType {
+			delete(bt.list, mt)
+		}
+	}
+}
diff --git a/switchboard/byType_test.go b/switchboard/byType_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9abc45d24f571e6ddab79d0d606c0709d3072411
--- /dev/null
+++ b/switchboard/byType_test.go
@@ -0,0 +1,233 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"github.com/golang-collections/collections/set"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"testing"
+)
+
+func TestByType_newByType(t *testing.T) {
+	nbt := newByType()
+
+	if nbt.list == nil {
+		t.Errorf("No list created")
+	}
+
+	if nbt.generic == nil {
+		t.Errorf("No generic created")
+	}
+
+	if nbt.generic != nbt.list[0] {
+		t.Errorf("zero message type not registered as generic")
+	}
+
+}
+
+func TestByType_Get_Empty(t *testing.T) {
+	nbt := newByType()
+
+	s := nbt.Get(42)
+
+	if s.Len() != 0 {
+		t.Errorf("Should not have returned a set")
+	}
+}
+
+func TestByType_Get_Selected(t *testing.T) {
+	nbt := newByType()
+
+	m := message.Type(42)
+
+	set1 := set.New(0)
+
+	nbt.list[m] = set1
+
+	s := nbt.Get(m)
+
+	if s.Len() == 0 {
+		t.Errorf("Should have returned a set")
+	}
+
+	if !s.SubsetOf(set1) || !set1.SubsetOf(s) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+func TestByType_Get_Generic(t *testing.T) {
+	nbt := newByType()
+
+	m := message.Type(42)
+
+	nbt.generic.Insert(0)
+
+	s := nbt.Get(m)
+
+	if s.Len() == 0 {
+		t.Errorf("Should have returned a set")
+	}
+
+	if !s.SubsetOf(nbt.generic) || !nbt.generic.SubsetOf(s) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+func TestByType_Get_GenericSelected(t *testing.T) {
+	nbt := newByType()
+
+	m := message.Type(42)
+
+	nbt.generic.Insert(1)
+
+	set1 := set.New(0)
+
+	nbt.list[m] = set1
+
+	s := nbt.Get(m)
+
+	if s.Len() == 0 {
+		t.Errorf("Should have returned a set")
+	}
+
+	setUnion := set1.Union(nbt.generic)
+
+	if !s.SubsetOf(setUnion) || !setUnion.SubsetOf(s) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+// Tests that when adding to a set which does not exist, the set is created
+func TestByType_Add_New(t *testing.T) {
+	nbt := newByType()
+
+	m := message.Type(42)
+
+	l := &funcListener{}
+
+	nbt.Add(m, l)
+
+	s := nbt.list[m]
+
+	if s.Len() != 1 {
+		t.Errorf("Should a set of the wrong size")
+	}
+
+	if !s.Has(l) {
+		t.Errorf("Wrong set returned")
+	}
+}
+
+// Tests that when adding to a set which does exist, the set is retained and
+// added to
+func TestByType_Add_Old(t *testing.T) {
+	nbt := newByType()
+
+	m := message.Type(42)
+
+	l1 := &funcListener{}
+	l2 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbt.list[m] = set1
+
+	nbt.Add(m, l2)
+
+	s := nbt.list[m]
+
+	if s.Len() != 2 {
+		t.Errorf("Should have returned a set")
+	}
+
+	if !s.Has(l1) {
+		t.Errorf("Set does not include the initial listener")
+	}
+
+	if !s.Has(l2) {
+		t.Errorf("Set does not include the new listener")
+	}
+}
+
+// Tests that when adding to a generic ID, the listener is added to the
+// generic set
+func TestByType_Add_Generic(t *testing.T) {
+	nbt := newByType()
+
+	l1 := &funcListener{}
+
+	nbt.Add(AnyType, l1)
+
+	s := nbt.generic
+
+	if s.Len() != 1 {
+		t.Errorf("Should have returned a set of size 2")
+	}
+
+	if !s.Has(l1) {
+		t.Errorf("Set does not include the ZeroUser listener")
+	}
+}
+
+// Tests that removing a listener from a set with a single listener removes the
+// listener and the set
+func TestByType_Remove_SingleInSet(t *testing.T) {
+	nbt := newByType()
+
+	m := message.Type(42)
+
+	l1 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbt.list[m] = set1
+
+	nbt.Remove(m, l1)
+
+	if _, ok := nbt.list[m]; ok {
+		t.Errorf("Set not removed when it should have been")
+	}
+
+	if set1.Len() != 0 {
+		t.Errorf("Set is incorrect length after the remove call: %v",
+			set1.Len())
+	}
+
+	if set1.Has(l1) {
+		t.Errorf("Listener 1 still in set, it should not be")
+	}
+}
+
+// Tests that removing a listener from a set with a single listener removes the
+// listener and not the set when the ID iz ZeroUser
+func TestByType_Remove_SingleInSet_AnyType(t *testing.T) {
+	nbt := newByType()
+
+	m := AnyType
+
+	l1 := &funcListener{}
+
+	set1 := set.New(l1)
+
+	nbt.list[m] = set1
+
+	nbt.Remove(m, l1)
+
+	if _, ok := nbt.list[m]; !ok {
+		t.Errorf("Set removed when it should not have been")
+	}
+
+	if set1.Len() != 0 {
+		t.Errorf("Set is incorrect length after the remove call: %v",
+			set1.Len())
+	}
+
+	if set1.Has(l1) {
+		t.Errorf("Listener 1 still in set, it should not be")
+	}
+}
diff --git a/switchboard/listener.go b/switchboard/listener.go
new file mode 100644
index 0000000000000000000000000000000000000000..630ddc150a0c0ee4a733641a66437a2e1838c145
--- /dev/null
+++ b/switchboard/listener.go
@@ -0,0 +1,113 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+//interface for a listener adhere to
+type Listener interface {
+	// the Hear function is called to exercise the listener, passing in the
+	// data as an item
+	Hear(item message.Receive)
+	// Returns a name, used for debugging
+	Name() string
+}
+
+// This function type defines callbacks that get passed when the listener is
+// listened to. It will always be called in its own goroutine. It may be called
+// multiple times simultaneously
+type ListenerFunc func(item message.Receive)
+
+// id object returned when a listener is created and is used to delete it from
+// the system
+type ListenerID struct {
+	userID      *id.ID
+	messageType message.Type
+	listener    Listener
+}
+
+//getter for userID
+func (lid ListenerID) GetUserID() *id.ID {
+	return lid.userID
+}
+
+//getter for message type
+func (lid ListenerID) GetMessageType() message.Type {
+	return lid.messageType
+}
+
+//getter for name
+func (lid ListenerID) GetName() string {
+	return lid.listener.Name()
+}
+
+/*internal listener implementations*/
+
+//listener based off of a function
+type funcListener struct {
+	listener ListenerFunc
+	name     string
+}
+
+// creates a new FuncListener Adhereing to the listener interface out of the
+// passed function and name, returns a pointer to the result
+func newFuncListener(listener ListenerFunc, name string) *funcListener {
+	return &funcListener{
+		listener: listener,
+		name:     name,
+	}
+}
+
+// Adheres to the Hear function of the listener interface, calls the internal
+// function with the passed item
+func (fl *funcListener) Hear(item message.Receive) {
+	fl.listener(item)
+}
+
+// Adheres to the Name function of the listener interface, returns a name.
+// used for debugging
+func (fl *funcListener) Name() string {
+	return fl.name
+}
+
+//listener based off of a channel
+type chanListener struct {
+	listener chan message.Receive
+	name     string
+}
+
+// creates a new ChanListener Adhereing to the listener interface out of the
+// passed channel and name, returns a pointer to the result
+func newChanListener(listener chan message.Receive, name string) *chanListener {
+	return &chanListener{
+		listener: listener,
+		name:     name,
+	}
+}
+
+// Adheres to the Hear function of the listener interface, calls the passed the
+// heard item across the channel.  Drops the item if it cannot put it into the
+// channel immediately
+func (cl *chanListener) Hear(item message.Receive) {
+	select {
+	case cl.listener <- item:
+	default:
+		jww.WARN.Printf("Switchboard failed to speak on channel "+
+			"listener %s", cl.name)
+	}
+}
+
+// Adheres to the Name function of the listener interface, returns a name.
+// used for debugging
+func (cl *chanListener) Name() string {
+	return cl.name
+}
diff --git a/switchboard/listener_test.go b/switchboard/listener_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7541072b639558ee730fe485dcc6803a0fa911b4
--- /dev/null
+++ b/switchboard/listener_test.go
@@ -0,0 +1,165 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/xx_network/primitives/id"
+	"reflect"
+	"testing"
+	"time"
+)
+
+//verify func listener adheres to the listener interface
+var _ Listener = &funcListener{}
+
+//verify chan listener adheres to the listener interface
+var _ Listener = &chanListener{}
+
+//test listenerID returns the userID
+func TestListenerID_GetUserID(t *testing.T) {
+	lid := ListenerID{
+		userID:      id.NewIdFromUInt(42, id.User, t),
+		messageType: 42,
+		listener:    nil,
+	}
+
+	if !lid.GetUserID().Cmp(lid.userID) {
+		t.Errorf("Returned userID does not match")
+	}
+}
+
+//test listenerID returns the messageType
+func TestListenerID_GetMessageType(t *testing.T) {
+	lid := ListenerID{
+		userID:      id.NewIdFromUInt(42, id.User, t),
+		messageType: 42,
+		listener:    nil,
+	}
+
+	if lid.GetMessageType() != lid.messageType {
+		t.Errorf("Returned message type does not match")
+	}
+}
+
+//test listenerID returns the name
+func TestListenerID_GetName(t *testing.T) {
+	name := "test"
+
+	lid := ListenerID{
+		userID:      id.NewIdFromUInt(42, id.User, t),
+		messageType: 42,
+		listener:    newFuncListener(nil, name),
+	}
+
+	if lid.GetName() != name {
+		t.Errorf("Returned name type does not match")
+	}
+}
+
+//tests new function listener creates the funcListener properly
+func TestNewFuncListener(t *testing.T) {
+	f := func(item message.Receive) {}
+	name := "test"
+	listener := newFuncListener(f, name)
+
+	if listener.listener == nil {
+		t.Errorf("function is wrong")
+	}
+
+	if listener.name != name {
+		t.Errorf("name is wrong")
+	}
+}
+
+//tests FuncListener Hear works
+func TestFuncListener_Hear(t *testing.T) {
+	m := message.Receive{
+		Payload:     []byte{0, 1, 2, 3},
+		Sender:      id.NewIdFromUInt(42, id.User, t),
+		MessageType: 69,
+	}
+
+	heard := make(chan message.Receive, 1)
+
+	f := func(item message.Receive) {
+		heard <- item
+	}
+
+	listener := newFuncListener(f, "test")
+
+	listener.Hear(m)
+
+	select {
+	case item := <-heard:
+		if !reflect.DeepEqual(item, m) {
+			t.Errorf("Heard message did not match")
+		}
+	case <-time.After(5 * time.Millisecond):
+		t.Errorf("Did not hear")
+	}
+}
+
+// Test FuncListener returns the correct name
+func TestFuncListener_Name(t *testing.T) {
+	name := "test"
+	listener := newFuncListener(nil, name)
+
+	if listener.Name() != name {
+		t.Errorf("Name did not match")
+	}
+}
+
+//tests new chan listener creates the chanListener properly
+func TestNewChanListener(t *testing.T) {
+	c := make(chan message.Receive)
+	name := "test"
+	listener := newChanListener(c, name)
+
+	if listener.listener == nil {
+		t.Errorf("function is wrong")
+	}
+
+	if listener.name != name {
+		t.Errorf("name is wrong")
+	}
+}
+
+//tests ChanListener Hear works
+func TestChanListener_Hear(t *testing.T) {
+	m := message.Receive{
+		Payload:     []byte{0, 1, 2, 3},
+		Sender:      id.NewIdFromUInt(42, id.User, t),
+		MessageType: 69,
+	}
+
+	heard := make(chan message.Receive, 1)
+
+	listener := newChanListener(heard, "test")
+
+	listener.Hear(m)
+
+	select {
+	case item := <-heard:
+		if !reflect.DeepEqual(item, m) {
+			t.Errorf("Heard message did not match")
+		}
+	case <-time.After(5 * time.Millisecond):
+		t.Errorf("Did not hear")
+	}
+}
+
+// Test FuncListener returns the correct name
+func TestChanListener_Name(t *testing.T) {
+	name := "test"
+	listener := newChanListener(nil, name)
+
+	if listener.Name() != name {
+		t.Errorf("Name did not match")
+	}
+}
diff --git a/switchboard/switchboard.go b/switchboard/switchboard.go
new file mode 100644
index 0000000000000000000000000000000000000000..21695f10c93b9b248cf8b1a6edcd70b794b6d2c7
--- /dev/null
+++ b/switchboard/switchboard.go
@@ -0,0 +1,173 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"github.com/golang-collections/collections/set"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+)
+
+type Switchboard struct {
+	id          *byId
+	messageType *byType
+
+	mux sync.RWMutex
+}
+
+// New generates and returns a new switchboard object.
+func New() *Switchboard {
+	return &Switchboard{
+		id:          newById(),
+		messageType: newByType(),
+	}
+}
+
+// Registers a new listener. Returns the ID of the new listener.
+// Keep this around if you want to be able to delete the listener later.
+//
+// name is used for debug printing and not checked for uniqueness
+//
+// user: 0 for all, or any user ID to listen for messages from a particular
+// user. 0 can be id.ZeroUser or id.ZeroID
+// messageType: 0 for all, or any message type to listen for messages of that
+// type. 0 can be switchboard.AnyType
+// newListener: something implementing the Listener interface. Do not
+// pass nil to this.
+//
+// If a message matches multiple listeners, all of them will hear the message.
+func (sw *Switchboard) RegisterListener(user *id.ID, messageType message.Type,
+	newListener Listener) ListenerID {
+
+	// check the input data is valid
+	if user == nil {
+		jww.FATAL.Panicf("cannot register listener to nil user")
+	}
+
+	if newListener == nil {
+		jww.FATAL.Panicf("cannot register nil listener")
+	}
+
+	//register the listener by both ID and messageType
+	sw.mux.Lock()
+
+	sw.id.Add(user, newListener)
+	sw.messageType.Add(messageType, newListener)
+
+	sw.mux.Unlock()
+
+	//return a ListenerID so it can be unregistered in the future
+	return ListenerID{
+		userID:      user,
+		messageType: messageType,
+		listener:    newListener,
+	}
+}
+
+// Registers a new listener built around the passed function.
+// Returns the ID of the new listener.
+// Keep this around if you want to be able to delete the listener later.
+//
+// name is used for debug printing and not checked for uniqueness
+//
+// user: 0 for all, or any user ID to listen for messages from a particular
+// user. 0 can be id.ZeroUser or id.ZeroID
+// messageType: 0 for all, or any message type to listen for messages of that
+// type. 0 can be switchboard.AnyType
+// newListener: a function implementing the ListenerFunc function type.
+// Do not pass nil to this.
+//
+// If a message matches multiple listeners, all of them will hear the message.
+func (sw *Switchboard) RegisterFunc(name string, user *id.ID,
+	messageType message.Type, newListener ListenerFunc) ListenerID {
+	// check that the input data is valid
+	if newListener == nil {
+		jww.FATAL.Panicf("cannot register function listener '%s' "+
+			"with nil func", name)
+	}
+
+	// generate a funcListener object adhering to the listener interface
+	fl := newFuncListener(newListener, name)
+
+	//register the listener and return the result
+	return sw.RegisterListener(user, messageType, fl)
+}
+
+// Registers a new listener built around the passed channel.
+// Returns the ID of the new listener.
+// Keep this around if you want to be able to delete the listener later.
+//
+// name is used for debug printing and not checked for uniqueness
+//
+// user: 0 for all, or any user ID to listen for messages from a particular
+// user. 0 can be id.ZeroUser or id.ZeroID
+// messageType: 0 for all, or any message type to listen for messages of that
+// type. 0 can be switchboard.AnyType
+// newListener: an item channel.
+// Do not pass nil to this.
+//
+// If a message matches multiple listeners, all of them will hear the message.
+func (sw *Switchboard) RegisterChannel(name string, user *id.ID,
+	messageType message.Type, newListener chan message.Receive) ListenerID {
+	// check that the input data is valid
+	if newListener == nil {
+		jww.FATAL.Panicf("cannot register channel listener '%s' with"+
+			" nil channel", name)
+	}
+
+	// generate a chanListener object adhering to the listener interface
+	cl := newChanListener(newListener, name)
+
+	//register the listener and return the result
+	return sw.RegisterListener(user, messageType, cl)
+}
+
+// Speak broadcasts a message to the appropriate listeners.
+// each is spoken to in their own goroutine
+func (sw *Switchboard) Speak(item message.Receive) {
+	sw.mux.RLock()
+	defer sw.mux.RUnlock()
+
+	// Matching listeners: include those that match all criteria perfectly, as
+	// well as those that do not care about certain criteria
+	matches := sw.matchListeners(item)
+
+	//Execute hear on all matched listeners in a new goroutine
+	matches.Do(func(i interface{}) {
+		r := i.(Listener)
+		go r.Hear(item)
+	})
+
+	// print to log if nothing was heard
+	if matches.Len() == 0 {
+		jww.ERROR.Printf(
+			"Message of type %v from user %q didn't match any listeners in"+
+				" the map", item.MessageType, item.Sender)
+	}
+}
+
+// Unregister removes the listener with the specified ID so it will no longer
+// get called
+func (sw *Switchboard) Unregister(listenerID ListenerID) {
+	sw.mux.Lock()
+
+	sw.id.Remove(listenerID.userID, listenerID.listener)
+	sw.messageType.Remove(listenerID.messageType, listenerID.listener)
+
+	sw.mux.Unlock()
+}
+
+// finds all listeners who match the items sender or ID, or have those fields
+// as generic
+func (sw *Switchboard) matchListeners(item message.Receive) *set.Set {
+	idSet := sw.id.Get(item.Sender)
+	typeSet := sw.messageType.Get(item.MessageType)
+	return idSet.Intersection(typeSet)
+}
diff --git a/switchboard/switchboard_test.go b/switchboard/switchboard_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a726c73cf55a5771db525ac8e86a75168abe596
--- /dev/null
+++ b/switchboard/switchboard_test.go
@@ -0,0 +1,350 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package switchboard
+
+import (
+	"gitlab.com/elixxir/client/interfaces/message"
+	"gitlab.com/xx_network/primitives/id"
+	"strings"
+	"testing"
+	"time"
+)
+
+// tests that New create a correctly structured switchboard
+func TestNew(t *testing.T) {
+	sw := New()
+
+	if sw.id == nil {
+		t.Errorf("did not create an id map")
+	}
+
+	if sw.messageType == nil {
+		t.Errorf("did not create a messageType map")
+	}
+}
+
+//Tests that register listener handles errors properly
+func TestSwitchboard_RegisterListener_Error_NilUserID(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil && !strings.Contains(r.(string),
+			"cannot register listener to nil user") {
+			t.Errorf("A nil userID caused the wrong error: %s", r)
+		}
+	}()
+
+	sw := New()
+	sw.RegisterListener(nil, 0, &funcListener{})
+
+	t.Errorf("A nil userID should have caused an panic")
+}
+
+//Tests that register listener handles errors properly
+func TestSwitchboard_RegisterListener_Error_NilListener(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil && !strings.Contains(r.(string),
+			"cannot register nil listener") {
+			t.Errorf("A nil listener caused the wrong error: %s", r)
+		}
+	}()
+
+	sw := New()
+	sw.RegisterListener(id.NewIdFromUInt(42, id.User, t), 0, nil)
+
+	t.Errorf("A nil listener should have caused an error")
+}
+
+//Tests that RegisterListener properly registers the listeners
+func TestSwitchboard_RegisterListener(t *testing.T) {
+	sw := New()
+
+	l := &funcListener{}
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	mt := message.Type(69)
+
+	lid := sw.RegisterListener(uid, mt, l)
+
+	if lid.messageType != mt {
+		t.Errorf("ListenerID message type is wrong")
+	}
+
+	if !lid.userID.Cmp(uid) {
+		t.Errorf("ListenerID userID is wrong")
+	}
+
+	if lid.listener != l {
+		t.Errorf("ListenerID listener is wrong")
+	}
+
+	//check that the listener is registered in the appropriate location
+	setID := sw.id.Get(uid)
+
+	if !setID.Has(l) {
+		t.Errorf("Listener is not registered by ID")
+	}
+
+	setType := sw.messageType.Get(mt)
+
+	if !setType.Has(l) {
+		t.Errorf("Listener is not registered by Message Type")
+	}
+
+}
+
+//Tests that register funcListener handles errors properly
+func TestSwitchboard_RegisterFunc_Error_NilUserID(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil && !strings.Contains(r.(string),
+			"cannot register listener to nil user") {
+			t.Errorf("A nil userID caused the wrong error: %s", r)
+		}
+	}()
+
+	sw := New()
+	sw.RegisterFunc("test", nil, 0, func(receive message.Receive) {})
+
+	t.Errorf("A nil user ID should have caused an error")
+}
+
+//Tests that register funcListener handles errors properly
+func TestSwitchboard_RegisterFunc_Error_NilFunc(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil && !strings.Contains(r.(string),
+			"cannot register function listener 'test' with nil func") {
+			t.Errorf("A nil func caused the wrong error: %s", r)
+		}
+	}()
+
+	sw := New()
+	sw.RegisterFunc("test", id.NewIdFromUInt(42, id.User, t), 0, nil)
+
+	t.Errorf("A nil listener func should have caused an error")
+}
+
+//Tests that RegisterFunc properly registers the listeners
+func TestSwitchboard_RegisterFunc(t *testing.T) {
+	sw := New()
+
+	heard := false
+
+	l := func(receive message.Receive) { heard = true }
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	mt := message.Type(69)
+
+	lid := sw.RegisterFunc("test", uid, mt, l)
+
+	if lid.messageType != mt {
+		t.Errorf("ListenerID message type is wrong")
+	}
+
+	if !lid.userID.Cmp(uid) {
+		t.Errorf("ListenerID userID is wrong")
+	}
+
+	//check that the listener is registered in the appropriate location
+	setID := sw.id.Get(uid)
+
+	if !setID.Has(lid.listener) {
+		t.Errorf("Listener is not registered by ID")
+	}
+
+	setType := sw.messageType.Get(mt)
+
+	if !setType.Has(lid.listener) {
+		t.Errorf("Listener is not registered by Message Type")
+	}
+
+	lid.listener.Hear(message.Receive{})
+	if !heard {
+		t.Errorf("Func listener not registered correctly")
+	}
+}
+
+//Tests that register chanListener handles errors properly
+func TestSwitchboard_RegisterChan_Error_NilUser(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil && !strings.Contains(r.(string),
+			"cannot register listener to nil user") {
+			t.Errorf("A nil user ID caused the wrong error: %s", r)
+		}
+	}()
+	sw := New()
+	sw.RegisterChannel("test", nil, 0,
+		make(chan message.Receive))
+
+	t.Errorf("A nil userID should have caused an error")
+}
+
+//Tests that register chanListener handles errors properly
+func TestSwitchboard_RegisterChan_Error_NilChan(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil && !strings.Contains(r.(string),
+			"cannot register channel listener 'test' with nil channel") {
+			t.Errorf("A nil channel caused the wrong error: %s", r)
+		}
+	}()
+	sw := New()
+	sw.RegisterChannel("test", &id.ID{}, 0, nil)
+
+	t.Errorf("A nil channel func should have caused an error")
+}
+
+//Tests that RegisterChan properly registers the listeners
+func TestSwitchboard_RegisterChan(t *testing.T) {
+	sw := New()
+
+	ch := make(chan message.Receive, 1)
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+
+	mt := message.Type(69)
+
+	lid := sw.RegisterChannel("test", uid, mt, ch)
+
+	//check the returns
+	if lid.messageType != mt {
+		t.Errorf("ListenerID message type is wrong")
+	}
+
+	if !lid.userID.Cmp(uid) {
+		t.Errorf("ListenerID userID is wrong")
+	}
+
+	//check that the listener is registered in the appropriate location
+	setID := sw.id.Get(uid)
+
+	if !setID.Has(lid.listener) {
+		t.Errorf("Listener is not registered by ID")
+	}
+
+	setType := sw.messageType.Get(mt)
+
+	if !setType.Has(lid.listener) {
+		t.Errorf("Listener is not registered by Message Type")
+	}
+
+	lid.listener.Hear(message.Receive{})
+	select {
+	case <-ch:
+	case <-time.After(5 * time.Millisecond):
+		t.Errorf("Chan listener not registered correctly")
+	}
+}
+
+//tests all combinations of hits and misses for speak
+func TestSwitchboard_Speak(t *testing.T) {
+
+	uids := []*id.ID{{}, AnyUser(), id.NewIdFromUInt(42, id.User, t), id.NewIdFromUInt(69, id.User, t)}
+	mts := []message.Type{AnyType, 42, 69}
+
+	for _, uidReg := range uids {
+		for _, mtReg := range mts {
+
+			//create the registrations
+			sw := New()
+			ch1 := make(chan message.Receive, 1)
+			ch2 := make(chan message.Receive, 1)
+
+			sw.RegisterChannel("test", uidReg, mtReg, ch1)
+			sw.RegisterChannel("test", uidReg, mtReg, ch2)
+
+			//send every possible message
+			for _, uid := range uids {
+				for _, mt := range mts {
+					if uid.Cmp(&id.ID{}) || mt == AnyType {
+						continue
+					}
+
+					m := message.Receive{
+						Payload:     []byte{0, 1, 2, 3},
+						Sender:      uid,
+						MessageType: mt,
+					}
+
+					sw.Speak(m)
+
+					shouldHear := (m.Sender.Cmp(uidReg) ||
+						uidReg.Cmp(&id.ID{}) || uidReg.Cmp(AnyUser())) &&
+						(m.MessageType == mtReg || mtReg == AnyType)
+
+					var heard1 bool
+
+					select {
+					case <-ch1:
+						heard1 = true
+					case <-time.After(5 * time.Millisecond):
+						heard1 = false
+					}
+
+					if shouldHear != heard1 {
+						t.Errorf("Correct operation not recorded "+
+							"for listener 1: Expected: %v, Occured: %v",
+							shouldHear, heard1)
+					}
+
+					var heard2 bool
+
+					select {
+					case <-ch2:
+						heard2 = true
+					case <-time.After(5 * time.Millisecond):
+						heard2 = false
+					}
+
+					if shouldHear != heard2 {
+						t.Errorf("Correct operation not recorded "+
+							"for listener 2: Expected: %v, Occured: %v",
+							shouldHear, heard2)
+					}
+				}
+			}
+		}
+	}
+}
+
+//tests that Unregister removes the listener and only the listener
+func TestSwitchboard_Unregister(t *testing.T) {
+	sw := New()
+
+	uid := id.NewIdFromUInt(42, id.User, t)
+	mt := message.Type(69)
+
+	l := func(receive message.Receive) {}
+
+	lid1 := sw.RegisterFunc("a", uid, mt, l)
+
+	lid2 := sw.RegisterFunc("a", uid, mt, l)
+
+	sw.Unregister(lid1)
+
+	//get sets to check
+	setID := sw.id.Get(uid)
+	setType := sw.messageType.Get(mt)
+
+	//check that the removed listener is not registered
+	if setID.Has(lid1.listener) {
+		t.Errorf("Removed Listener is registered by ID, should not be")
+	}
+
+	if setType.Has(lid1.listener) {
+		t.Errorf("Removed Listener not registered by Message Type, " +
+			"should not be")
+	}
+
+	//check that the not removed listener is still registered
+	if !setID.Has(lid2.listener) {
+		t.Errorf("Remaining Listener is not registered by ID")
+	}
+
+	if !setType.Has(lid2.listener) {
+		t.Errorf("Remaining Listener is not registered by Message Type")
+	}
+}
diff --git a/ud/addFact.go b/ud/addFact.go
new file mode 100644
index 0000000000000000000000000000000000000000..a32668f7179d1282f7f96438f13f76905e8b2b8f
--- /dev/null
+++ b/ud/addFact.go
@@ -0,0 +1,74 @@
+package ud
+
+import (
+	"crypto/rand"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/crypto/factID"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+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.
+// 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
+// called along with the code to finalize the fact.
+func (m *Manager) SendRegisterFact(fact fact.Fact) (string, error) {
+	jww.INFO.Printf("ud.SendRegisterFact(%s)", fact.Stringify())
+	return m.addFact(fact, m.myID, m.comms)
+}
+
+func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (string, error) {
+
+	if !m.IsRegistered() {
+		return "", errors.New("Failed to add fact: " +
+			"client is not registered")
+	}
+
+	// Create a primitives Fact so we can hash it
+	f, err := fact.NewFact(inFact.T, inFact.Fact)
+	if err != nil {
+		return "", err
+	}
+
+	// Create a hash of our fact
+	fhash := factID.Fingerprint(f)
+
+	// Sign our inFact for putting into the request
+	fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil)
+	if err != nil {
+		return "", err
+	}
+
+	// Create our Fact Removal Request message data
+	remFactMsg := pb.FactRegisterRequest{
+		UID: uid.Marshal(),
+		Fact: &pb.Fact{
+			Fact:     inFact.Fact,
+			FactType: uint32(inFact.T),
+		},
+		FactSig: fsig,
+	}
+
+	// Send the message
+	response, err := aFC.SendRegisterFact(m.host, &remFactMsg)
+
+	confirmationID := ""
+	if response != nil {
+		confirmationID = response.ConfirmationID
+	}
+
+	// Return the error
+	return confirmationID, err
+}
diff --git a/ud/addFact_test.go b/ud/addFact_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e1ce5ad31464a042d70a1de251d8b8668a3e8fa
--- /dev/null
+++ b/ud/addFact_test.go
@@ -0,0 +1,62 @@
+package ud
+
+import (
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/csprng"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+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) {
+	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()
+	cpk, err := rsa.GenerateKey(rng, 2048)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Create our Manager object
+	m := Manager{
+		host:       h,
+		privKey:    cpk,
+		registered: &isReg,
+	}
+
+	// Create our test fact
+	USCountryCode := "US"
+	USNumber := "6502530000"
+	f := fact.Fact{
+		Fact: USNumber + USCountryCode,
+		T:    2,
+	}
+
+	// Setup a dummy comms that implements SendRegisterFact
+	// This way we don't need to run UDB just to check that this
+	// function works.
+	tafc := testAFC{}
+	uid := &id.ID{}
+	// Run addFact and see if it returns without an error!
+	_, err = m.addFact(f, uid, &tafc)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/ud/confirmFact.go b/ud/confirmFact.go
new file mode 100644
index 0000000000000000000000000000000000000000..a625e7e6337d8facc4cff7096d078ee53b264a7d
--- /dev/null
+++ b/ud/confirmFact.go
@@ -0,0 +1,37 @@
+package ud
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/comms/messages"
+)
+
+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
+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 {
+		return errors.WithMessage(err, "Failed to confirm fact")
+	}
+	return nil
+}
+
+func (m *Manager) confirmFact(confirmationID, code string, comm confirmFactComm) error {
+	if !m.IsRegistered() {
+		return errors.New("Failed to confirm fact: " +
+			"client is not registered")
+	}
+
+	msg := &pb.FactConfirmRequest{
+		ConfirmationID: confirmationID,
+		Code:           code,
+	}
+	_, err := comm.SendConfirmFact(m.host, msg)
+	return err
+}
diff --git a/ud/confirmFact_test.go b/ud/confirmFact_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2e060c09857b6e0f90d9fac6b5a81f5757d4d79b
--- /dev/null
+++ b/ud/confirmFact_test.go
@@ -0,0 +1,54 @@
+package ud
+
+import (
+	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"
+)
+
+type testComm struct {
+	request *pb.FactConfirmRequest
+}
+
+func (t *testComm) SendConfirmFact(_ *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error) {
+	t.request = message
+	return &messages.Ack{}, nil
+}
+
+// Happy path.
+func TestManager_confirmFact(t *testing.T) {
+	// Create new host
+	host, err := connect.NewHost(&id.UDB, "0.0.0.0", nil, connect.GetDefaultHostParams())
+	if err != nil {
+		t.Fatalf("Could not create a new host: %+v", err)
+	}
+
+	isReg := uint32(1)
+
+	// Set up manager
+	m := &Manager{
+		host:       host,
+		registered: &isReg,
+	}
+
+	c := &testComm{}
+
+	expectedRequest := &pb.FactConfirmRequest{
+		ConfirmationID: "test",
+		Code:           "1234",
+	}
+
+	err = m.confirmFact(expectedRequest.ConfirmationID, expectedRequest.Code, c)
+	if err != nil {
+		t.Errorf("confirmFact() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expectedRequest, c.request) {
+		t.Errorf("end point did not recieve the expected request."+
+			"\n\texpected: %+v\n\treceived: %+v", expectedRequest, c.request)
+	}
+
+}
diff --git a/ud/generate.sh b/ud/generate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f8e0ec99843d0d39bf12b63f145d81a8df1bc99d
--- /dev/null
+++ b/ud/generate.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+protoc --go_out=. udMessages.proto
diff --git a/ud/lookup.go b/ud/lookup.go
new file mode 100644
index 0000000000000000000000000000000000000000..d7ce0fa69f2addc3bb09419f98ec60b3a6cc2985
--- /dev/null
+++ b/ud/lookup.go
@@ -0,0 +1,77 @@
+package ud
+
+import (
+	"fmt"
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// LookupTag specifies which callback to trigger when UD receives a lookup
+// request.
+const LookupTag = "xxNetwork_UdLookup"
+
+// TODO: reconsider where this comes from
+const maxLookupMessages = 20
+
+type lookupCallback func(contact.Contact, error)
+
+// Lookup returns the public key of the passed ID as known by the user discovery
+// system or returns by the timeout.
+func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error {
+	jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout)
+	if !m.IsRegistered() {
+		return errors.New("Failed to lookup: client is not registered.")
+	}
+
+	// Build the request and marshal it
+	request := &LookupSend{UserID: uid.Marshal()}
+	requestMarshaled, err := proto.Marshal(request)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to form outgoing lookup request.")
+	}
+
+	f := func(payload []byte, err error) {
+		m.lookupResponseProcess(uid, callback, payload, err)
+	}
+
+	err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, LookupTag,
+		maxLookupMessages, f, timeout)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to transmit lookup request.")
+	}
+
+	return nil
+}
+
+func (m *Manager) lookupResponseProcess(uid *id.ID, callback lookupCallback,
+	payload []byte, err error) {
+	if err != nil {
+		go callback(contact.Contact{}, errors.WithMessage(err, "Failed to lookup."))
+		return
+	}
+
+	// Unmarshal the message
+	lookupResponse := &LookupResponse{}
+	if err := proto.Unmarshal(payload, lookupResponse); err != nil {
+		jww.WARN.Printf("Dropped a lookup response from user discovery due to "+
+			"failed unmarshal: %s", err)
+	}
+	if lookupResponse.Error != "" {
+		err = errors.Errorf("User Discovery returned an error on lookup: %s",
+			lookupResponse.Error)
+		go callback(contact.Contact{}, err)
+		return
+	}
+
+	fmt.Printf("pubKey: %+v\n", lookupResponse.PubKey)
+	c := contact.Contact{
+		ID:       uid,
+		DhPubKey: m.grp.NewIntFromBytes(lookupResponse.PubKey),
+	}
+
+	go callback(c, nil)
+}
diff --git a/ud/lookup_test.go b/ud/lookup_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..413b85f9d0443411977557937dfd74cee8833d63
--- /dev/null
+++ b/ud/lookup_test.go
@@ -0,0 +1,203 @@
+package ud
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_Lookup(t *testing.T) {
+	// Set up manager
+	isReg := uint32(1)
+	m := &Manager{
+		grp:        cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		udContact:  contact.Contact{ID: &id.UDB},
+		single:     &mockSingleLookup{},
+		registered: &isReg,
+	}
+
+	// Generate callback function
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+	uid := id.NewIdFromUInt(0x500000000000000, id.User, t)
+
+	// Run the lookup
+	err := m.Lookup(uid, callback, 10*time.Millisecond)
+	if err != nil {
+		t.Errorf("Lookup() returned an error: %+v", err)
+	}
+
+	// Verify the callback is called
+	select {
+	case cb := <-callbackChan:
+		if cb.err != nil {
+			t.Errorf("Callback returned an error: %+v", cb.err)
+		}
+
+		expectedContact := contact.Contact{
+			ID:       uid,
+			DhPubKey: m.grp.NewIntFromBytes([]byte{5}),
+		}
+		if !reflect.DeepEqual(expectedContact, cb.c) {
+			t.Errorf("Failed to get expected Contact."+
+				"\n\texpected: %v\n\treceived: %v", expectedContact, cb.c)
+		}
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Callback not called.")
+	}
+}
+
+// Happy path.
+func TestManager_lookupResponseProcess(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+	pubKey := []byte{5}
+	expectedContact := contact.Contact{
+		ID:       uid,
+		DhPubKey: m.grp.NewIntFromBytes(pubKey),
+	}
+
+	// Generate expected Send message
+	payload, err := proto.Marshal(&LookupResponse{PubKey: pubKey})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+
+	m.lookupResponseProcess(uid, callback, payload, nil)
+
+	select {
+	case results := <-callbackChan:
+		if results.err != nil {
+			t.Errorf("Callback returned an error: %+v", results.err)
+		}
+		if !reflect.DeepEqual(expectedContact, results.c) {
+			t.Errorf("Callback returned unexpected Contact."+
+				"\nexpected: %+v\nreceived: %+v", expectedContact, results.c)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// Happy path: error is returned on callback when passed into function.
+func TestManager_lookupResponseProcess_CallbackError(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	testErr := errors.New("lookup failure")
+
+	m.lookupResponseProcess(nil, callback, []byte{}, testErr)
+
+	select {
+	case results := <-callbackChan:
+		if results.err == nil || !strings.Contains(results.err.Error(), testErr.Error()) {
+			t.Errorf("Callback failed to return error."+
+				"\nexpected: %+v\nreceived: %+v", testErr, results.err)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// Error path: LookupResponse message contains an error.
+func TestManager_lookupResponseProcess_MessageError(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	callbackChan := make(chan struct {
+		c   contact.Contact
+		err error
+	})
+	callback := func(c contact.Contact, err error) {
+		callbackChan <- struct {
+			c   contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	// Generate expected Send message
+	testErr := "LookupResponse error occurred"
+	payload, err := proto.Marshal(&LookupResponse{Error: testErr})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+
+	m.lookupResponseProcess(uid, callback, payload, nil)
+
+	select {
+	case results := <-callbackChan:
+		if results.err == nil || !strings.Contains(results.err.Error(), testErr) {
+			t.Errorf("Callback failed to return error."+
+				"\nexpected: %s\nreceived: %+v", testErr, results.err)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// mockSingleLookup is used to test the lookup function, which uses the single-
+// use manager. It adheres to the SingleInterface interface.
+type mockSingleLookup struct {
+}
+
+func (s *mockSingleLookup) TransmitSingleUse(_ contact.Contact, payload []byte,
+	_ string, _ uint8, callback single.ReplyComm, _ time.Duration) error {
+
+	lookupMsg := &LookupSend{}
+	if err := proto.Unmarshal(payload, lookupMsg); err != nil {
+		return errors.Errorf("Failed to unmarshal LookupSend: %+v", err)
+	}
+
+	lookupResponse := &LookupResponse{PubKey: lookupMsg.UserID[:1]}
+	msg, err := proto.Marshal(lookupResponse)
+	if err != nil {
+		return errors.Errorf("Failed to marshal LookupResponse: %+v", err)
+	}
+
+	callback(msg, nil)
+	return nil
+}
+
+func (s *mockSingleLookup) StartProcesses() stoppable.Stoppable {
+	return stoppable.NewSingle("")
+}
diff --git a/ud/manager.go b/ud/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..d38c5aa5b7f2490e75fc502dd89c131c2faad94a
--- /dev/null
+++ b/ud/manager.go
@@ -0,0 +1,110 @@
+package ud
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/api"
+	"gitlab.com/elixxir/client/interfaces"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/comms/client"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+type SingleInterface interface {
+	TransmitSingleUse(contact.Contact, []byte, string, uint8, single.ReplyComm,
+		time.Duration) error
+	StartProcesses() stoppable.Stoppable
+}
+
+type Manager struct {
+	// External
+	client  *api.Client
+	comms   *client.Comms
+	rng     *fastRNG.StreamGenerator
+	sw      interfaces.Switchboard
+	storage *storage.Session
+	net     interfaces.NetworkManager
+
+	// Loaded from external access
+	udContact contact.Contact
+	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.
+func NewManager(client *api.Client, single *single.Manager) (*Manager, error) {
+	jww.INFO.Println("ud.NewManager()")
+	if !client.GetHealth().IsHealthy() {
+		return nil, errors.New("cannot start UD Manager when network was " +
+			"never healthy.")
+	}
+
+	m := &Manager{
+		client:    client,
+		comms:     client.GetComms(),
+		rng:       client.GetRng(),
+		sw:        client.GetSwitchboard(),
+		storage:   client.GetStorage(),
+		net:       client.GetNetworkInterface(),
+		udContact: contact.Contact{},
+		single:    single,
+	}
+
+	var err error
+
+	// 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()
+	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()
+
+	// Get the commonly used data from storage
+	m.privKey = m.storage.GetUser().ReceptionRSA
+
+	// Load if the client is registered
+	m.loadRegistered()
+
+	// Store the pointer to the group locally for easy access
+	m.grp = m.storage.E2e().GetGroup()
+
+	return m, nil
+}
diff --git a/ud/register.go b/ud/register.go
new file mode 100644
index 0000000000000000000000000000000000000000..764b8a2b96ee03e75b3eba91e44a2f31f4590f3b
--- /dev/null
+++ b/ud/register.go
@@ -0,0 +1,90 @@
+package ud
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/crypto/factID"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/comms/messages"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+)
+
+type registerUserComms interface {
+	SendRegisterUser(*connect.Host, *pb.UDBUserRegistration) (*messages.Ack, error)
+}
+
+// Register registers a user with user discovery. Will return an error if the
+// network signatures are malformed or if the username is taken. Usernames cannot
+// be changed after registration at this time. Will fail if the user is already
+// registered.
+// Identity does not go over cmix, it occurs over normal communications
+func (m *Manager) Register(username string) error {
+	jww.INFO.Printf("ud.Register(%s)", username)
+	return m.register(username, m.comms)
+}
+
+// register registers a user with user discovery with a specified comm for
+// easier testing.
+func (m *Manager) register(username string, comm registerUserComms) error {
+	if m.IsRegistered() {
+		return errors.New("cannot register client with User Discovery: " +
+			"client is already registered")
+	}
+
+	var err error
+	user := m.storage.User()
+	cryptoUser := m.storage.User().GetCryptographicIdentity()
+	rng := m.rng.GetStream()
+
+	// Construct the user registration message
+	msg := &pb.UDBUserRegistration{
+		PermissioningSignature: user.GetReceptionRegistrationValidationSignature(),
+		RSAPublicPem:           string(rsa.CreatePublicKeyPem(cryptoUser.GetReceptionRSA().GetPublic())),
+		IdentityRegistration: &pb.Identity{
+			Username: username,
+			DhPubKey: m.storage.E2e().GetDHPublicKey().Bytes(),
+			Salt:     cryptoUser.GetReceptionSalt(),
+		},
+		UID: cryptoUser.GetReceptionID().Marshal(),
+	}
+
+	// Sign the identity data and add to user registration message
+	identityDigest := msg.IdentityRegistration.Digest()
+	msg.IdentitySignature, err = rsa.Sign(rng, cryptoUser.GetReceptionRSA(),
+		hash.CMixHash, identityDigest, nil)
+	if err != nil {
+		return errors.Errorf("Failed to sign user's IdentityRegistration: %+v", err)
+	}
+
+	// Create new username fact
+	usernameFact, err := fact.NewFact(fact.Username, username)
+	if err != nil {
+		return errors.Errorf("Failed to create new username fact: %+v", err)
+	}
+
+	// Hash and sign fact
+	hashedFact := factID.Fingerprint(usernameFact)
+	signedFact, err := rsa.Sign(rng, cryptoUser.GetReceptionRSA(), hash.CMixHash, hashedFact, nil)
+
+	// Add username fact register request to the user registration message
+	msg.Frs = &pb.FactRegisterRequest{
+		UID: cryptoUser.GetReceptionID().Marshal(),
+		Fact: &pb.Fact{
+			Fact:     username,
+			FactType: 0,
+		},
+		FactSig: signedFact,
+	}
+
+	// Register user with user discovery
+	_, err = comm.SendRegisterUser(m.host, msg)
+
+	if err == nil {
+		err = m.setRegistered()
+	}
+
+	return err
+}
diff --git a/ud/register_test.go b/ud/register_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7cfd66f1da801bf54b3c853b2232eb3d2f0d6645
--- /dev/null
+++ b/ud/register_test.go
@@ -0,0 +1,114 @@
+package ud
+
+import (
+	"bytes"
+	"gitlab.com/elixxir/client/storage"
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/crypto/factID"
+	"gitlab.com/elixxir/crypto/fastRNG"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/elixxir/primitives/fact"
+	"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"
+	"testing"
+)
+
+type testRegisterComm struct {
+	msg *pb.UDBUserRegistration
+}
+
+func (t *testRegisterComm) SendRegisterUser(_ *connect.Host, msg *pb.UDBUserRegistration) (*messages.Ack, error) {
+	t.msg = msg
+	return &messages.Ack{}, nil
+}
+
+// Happy path.
+func TestManager_register(t *testing.T) {
+	// Create new host
+	host, err := connect.NewHost(&id.UDB, "0.0.0.0", nil, connect.GetDefaultHostParams())
+	if err != nil {
+		t.Fatalf("Could not create a new host: %+v", err)
+	}
+
+	isReg := uint32(0)
+
+	// Set up manager
+	m := &Manager{
+		host:       host,
+		rng:        fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+		storage:    storage.InitTestingSession(t),
+		registered: &isReg,
+	}
+
+	c := &testRegisterComm{}
+
+	err = m.register("testUser", c)
+	if err != nil {
+		t.Errorf("register() returned an error: %+v", err)
+	}
+
+	// Check if the UDBUserRegistration contents are correct
+	m.isCorrect("testUser", c.msg, t)
+
+	// Verify the signed identity data
+	pubKey := m.storage.User().GetCryptographicIdentity().GetTransmissionRSA().GetPublic()
+	err = rsa.Verify(pubKey, hash.CMixHash, c.msg.IdentityRegistration.Digest(),
+		c.msg.IdentitySignature, nil)
+	if err != nil {
+		t.Errorf("Failed to verify signed identity data: %+v", err)
+	}
+
+	// Verify the signed fact
+	usernameFact, _ := fact.NewFact(fact.Username, "testUser")
+	err = rsa.Verify(pubKey, hash.CMixHash, factID.Fingerprint(usernameFact),
+		c.msg.Frs.FactSig, nil)
+	if err != nil {
+		t.Errorf("Failed to verify signed fact data: %+v", err)
+	}
+}
+
+// isCorrect checks if the UDBUserRegistration has all the expected fields minus
+// any signatures.
+func (m *Manager) isCorrect(username string, msg *pb.UDBUserRegistration, t *testing.T) {
+	user := m.storage.User()
+	cryptoUser := m.storage.User().GetCryptographicIdentity()
+
+	if !bytes.Equal(user.GetTransmissionRegistrationValidationSignature(), msg.PermissioningSignature) {
+		t.Errorf("PermissioningSignature incorrect.\n\texpected: %v\n\treceived: %v",
+			user.GetTransmissionRegistrationValidationSignature(), msg.PermissioningSignature)
+	}
+
+	if string(rsa.CreatePublicKeyPem(cryptoUser.GetTransmissionRSA().GetPublic())) != msg.RSAPublicPem {
+		t.Errorf("RSAPublicPem incorrect.\n\texpected: %v\n\treceived: %v",
+			string(rsa.CreatePublicKeyPem(cryptoUser.GetTransmissionRSA().GetPublic())), msg.RSAPublicPem)
+	}
+
+	if username != msg.IdentityRegistration.Username {
+		t.Errorf("IdentityRegistration Username incorrect.\n\texpected: %#v\n\treceived: %#v",
+			username, msg.IdentityRegistration.Username)
+	}
+
+	if !bytes.Equal(m.storage.E2e().GetDHPublicKey().Bytes(), msg.IdentityRegistration.DhPubKey) {
+		t.Errorf("IdentityRegistration DhPubKey incorrect.\n\texpected: %#v\n\treceived: %#v",
+			m.storage.E2e().GetDHPublicKey().Bytes(), msg.IdentityRegistration.DhPubKey)
+	}
+
+	if !bytes.Equal(cryptoUser.GetTransmissionSalt(), msg.IdentityRegistration.Salt) {
+		t.Errorf("IdentityRegistration Salt incorrect.\n\texpected: %#v\n\treceived: %#v",
+			cryptoUser.GetTransmissionSalt(), msg.IdentityRegistration.Salt)
+	}
+
+	if !bytes.Equal(cryptoUser.GetTransmissionID().Marshal(), msg.Frs.UID) {
+		t.Errorf("Frs UID incorrect.\n\texpected: %v\n\treceived: %v",
+			cryptoUser.GetTransmissionID().Marshal(), msg.Frs.UID)
+	}
+
+	if !reflect.DeepEqual(&pb.Fact{Fact: username}, msg.Frs.Fact) {
+		t.Errorf("Frs Fact incorrect.\n\texpected: %v\n\treceived: %v",
+			&pb.Fact{Fact: username}, msg.Frs.Fact)
+	}
+}
diff --git a/ud/registered.go b/ud/registered.go
new file mode 100644
index 0000000000000000000000000000000000000000..8191f2667246b8936a098eac688c49d2c2ae2be5
--- /dev/null
+++ b/ud/registered.go
@@ -0,0 +1,56 @@
+package ud
+
+import (
+	"encoding/binary"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/storage/versioned"
+	"sync/atomic"
+	"time"
+)
+
+const isRegisteredKey = "isRegisteredKey"
+const isRegisteredVersion = 0
+
+// loadRegistered loads from storage if the client is registered with user
+// discovery.
+func (m *Manager) loadRegistered() {
+	var isReg = uint32(0)
+	obj, err := m.storage.Get(isRegisteredKey)
+	if err != nil {
+		jww.INFO.Printf("Failed to load is registered, "+
+			"assuming un-registered: %s", err)
+	} else {
+		isReg = binary.BigEndian.Uint32(obj.Data)
+	}
+
+	m.registered = &isReg
+}
+
+// IsRegistered returns if the client is registered with user discovery
+func (m *Manager) IsRegistered() bool {
+	return atomic.LoadUint32(m.registered) == 1
+}
+
+// IsRegistered returns if the client is registered with user discovery
+func (m *Manager) setRegistered() error {
+	if !atomic.CompareAndSwapUint32(m.registered, 0, 1) {
+		return errors.New("cannot register with User Discovery when " +
+			"already registered")
+	}
+
+	data := make([]byte, 4)
+	binary.BigEndian.PutUint32(data, 1)
+
+	obj := &versioned.Object{
+		Version:   isRegisteredVersion,
+		Timestamp: time.Now(),
+		Data:      data,
+	}
+
+	if err := m.storage.Set(isRegisteredKey, obj); err != nil {
+		jww.FATAL.Panicf("Failed to store that the client is "+
+			"registered: %+v", err)
+	}
+	return nil
+}
diff --git a/ud/remove.go b/ud/remove.go
new file mode 100644
index 0000000000000000000000000000000000000000..44c4a03dc8de6b432aa828549f8535f9fa620b29
--- /dev/null
+++ b/ud/remove.go
@@ -0,0 +1,47 @@
+package ud
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/comms/messages"
+)
+
+type removeFactComms interface {
+	SendDeleteMessage(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error)
+}
+
+// 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())
+	return m.removeFact(fact, nil)
+}
+
+func (m *Manager) removeFact(fact fact.Fact, rFC removeFactComms) error {
+	if !m.IsRegistered() {
+		return errors.New("Failed to remove fact: " +
+			"client is not registered")
+	}
+
+	// Construct the message to send
+	// Convert our Fact to a mixmessages Fact for sending
+	mmFact := mixmessages.Fact{
+		Fact:     fact.Fact,
+		FactType: uint32(fact.T),
+	}
+
+	// Create our Fact Removal Request message data
+	remFactMsg := mixmessages.FactRemovalRequest{
+		UID:         m.myID.Marshal(),
+		RemovalData: &mmFact,
+	}
+
+	// Send the message
+	_, err := rFC.SendDeleteMessage(m.host, &remFactMsg)
+
+	// Return the error
+	return err
+}
diff --git a/ud/remove_test.go b/ud/remove_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bfe30a77de244b56c1ebd8dd96bb49abadce8ff1
--- /dev/null
+++ b/ud/remove_test.go
@@ -0,0 +1,53 @@
+package ud
+
+import (
+	pb "gitlab.com/elixxir/comms/mixmessages"
+	"gitlab.com/elixxir/primitives/fact"
+	"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"
+	"testing"
+)
+
+type testRFC struct{}
+
+func (rFC *testRFC) SendDeleteMessage(host *connect.Host, message *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 {
+		t.Fatal(err)
+	}
+
+	isReg := uint32(1)
+
+	m := Manager{
+		comms:      nil,
+		host:       h,
+		privKey:    cpk,
+		registered: &isReg,
+		myID:       &id.ID{},
+	}
+
+	f := fact.Fact{
+		Fact: "testing",
+		T:    2,
+	}
+
+	trfc := testRFC{}
+
+	err = m.removeFact(f, &trfc)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/ud/search.go b/ud/search.go
new file mode 100644
index 0000000000000000000000000000000000000000..5367fe10c5d65b22814a799f6071584ffeca8e24
--- /dev/null
+++ b/ud/search.go
@@ -0,0 +1,134 @@
+package ud
+
+import (
+	"github.com/golang/protobuf/proto"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/crypto/factID"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// SearchTag specifies which callback to trigger when UD receives a search
+// request.
+const SearchTag = "xxNetwork_UdLookup"
+
+// TODO: reconsider where this comes from
+const maxSearchMessages = 20
+
+type searchCallback func([]contact.Contact, error)
+
+// Search searches for the passed Facts. The searchCallback will return a list
+// of contacts, each having the facts it hit against. This is NOT intended to be
+// used to search for multiple users at once; that can have a privacy reduction.
+// Instead, it is intended to be used to search for a user where multiple pieces
+// of information is known.
+func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout time.Duration) error {
+	jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), timeout)
+	if !m.IsRegistered() {
+		return errors.New("Failed to search: client is not registered.")
+	}
+
+	factHashes, factMap := hashFactList(list)
+
+	// Build the request and marshal it
+	request := &SearchSend{Fact: factHashes}
+	requestMarshaled, err := proto.Marshal(request)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to form outgoing search request.")
+	}
+
+	f := func(payload []byte, err error) {
+		m.searchResponseHandler(factMap, callback, payload, err)
+	}
+
+	err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, SearchTag,
+		maxSearchMessages, f, timeout)
+	if err != nil {
+		return errors.WithMessage(err, "Failed to transmit search request.")
+	}
+
+	return nil
+}
+
+func (m *Manager) searchResponseHandler(factMap map[string]fact.Fact,
+	callback searchCallback, payload []byte, err error) {
+	if err != nil {
+		go callback(nil, errors.WithMessage(err, "Failed to search."))
+		return
+	}
+
+	// Unmarshal the message
+	searchResponse := &SearchResponse{}
+	if err := proto.Unmarshal(payload, searchResponse); err != nil {
+		jww.WARN.Printf("Dropped a search response from user discovery due to "+
+			"failed unmarshal: %s", err)
+	}
+	if searchResponse.Error != "" {
+		err = errors.Errorf("User Discovery returned an error on search: %s",
+			searchResponse.Error)
+		go callback(nil, err)
+		return
+	}
+
+	c, err := m.parseContacts(searchResponse.Contacts, factMap)
+	if err != nil {
+		go callback(nil, errors.WithMessage(err, "Failed to parse contacts from "+
+			"remote server."))
+		return
+	}
+
+	go callback(c, nil)
+}
+
+// hashFactList hashes each fact in the FactList into a HashFact and returns a
+// slice of the HashFacts. Also returns a map of Facts keyed on fact hashes to
+// be used for the callback return.
+func hashFactList(list fact.FactList) ([]*HashFact, map[string]fact.Fact) {
+	hashes := make([]*HashFact, len(list))
+	hashMap := make(map[string]fact.Fact, len(list))
+
+	for i, f := range list {
+		hashes[i] = &HashFact{
+			Hash: factID.Fingerprint(f),
+			Type: int32(f.T),
+		}
+		hashMap[string(factID.Fingerprint(f))] = f
+	}
+
+	return hashes, hashMap
+}
+
+// parseContacts parses the list of Contacts in the SearchResponse and returns a
+// list of contact.Contact with their ID and public key.
+func (m *Manager) parseContacts(response []*Contact,
+	hashMap map[string]fact.Fact) ([]contact.Contact, error) {
+	contacts := make([]contact.Contact, len(response))
+
+	// Convert each contact message into a new contact.Contact
+	for i, c := range response {
+		// Unmarshal user ID bytes
+		uid, err := id.Unmarshal(c.UserID)
+		if err != nil {
+			return nil, errors.Errorf("failed to parse Contact user ID: %+v", err)
+		}
+
+		// Create new Contact
+		contacts[i] = contact.Contact{
+			ID:       uid,
+			DhPubKey: m.grp.NewIntFromBytes(c.PubKey),
+			Facts:    []fact.Fact{},
+		}
+
+		// Assign each Fact with a matching hash to the Contact
+		for _, hashFact := range c.TrigFacts {
+			if f, exists := hashMap[string(hashFact.Hash)]; exists {
+				contacts[i].Facts = append(contacts[i].Facts, f)
+			}
+		}
+	}
+
+	return contacts, nil
+}
diff --git a/ud/search_test.go b/ud/search_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bbbb65c0d517f8abaa09df06628a5acecd663e27
--- /dev/null
+++ b/ud/search_test.go
@@ -0,0 +1,492 @@
+package ud
+
+import (
+	"fmt"
+	"github.com/golang/protobuf/proto"
+	errors "github.com/pkg/errors"
+	"gitlab.com/elixxir/client/interfaces/contact"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/stoppable"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/factID"
+	"gitlab.com/elixxir/primitives/fact"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+)
+
+// Happy path.
+func TestManager_Search(t *testing.T) {
+	// Set up manager
+	isReg := uint32(1)
+	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
+	m := &Manager{
+		grp:        grp,
+		udContact:  contact.Contact{ID: &id.UDB, DhPubKey: grp.NewInt(42)},
+		single:     &mockSingleSearch{},
+		registered: &isReg,
+	}
+
+	// Generate callback function
+	callbackChan := make(chan struct {
+		c   []contact.Contact
+		err error
+	})
+	callback := func(c []contact.Contact, err error) {
+		callbackChan <- struct {
+			c   []contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	// Generate fact list
+	var factList fact.FactList
+	for i := 0; i < 10; i++ {
+		factList = append(factList, fact.Fact{
+			Fact: fmt.Sprintf("fact %d", i),
+			T:    fact.FactType(rand.Intn(4)),
+		})
+	}
+	factHashes, _ := hashFactList(factList)
+
+	var contacts []*Contact
+	for i, hash := range factHashes {
+		contacts = append(contacts, &Contact{
+			UserID:    id.NewIdFromString("user", id.User, t).Marshal(),
+			PubKey:    []byte{byte(i + 1)},
+			TrigFacts: []*HashFact{hash},
+		})
+	}
+
+	err := m.Search(factList, callback, 10*time.Millisecond)
+	if err != nil {
+		t.Errorf("Search() returned an error: %+v", err)
+	}
+
+	// Verify the callback is called
+	select {
+	case cb := <-callbackChan:
+		if cb.err != nil {
+			t.Errorf("Callback returned an error: %+v", cb.err)
+		}
+
+		expectedContacts := []contact.Contact{m.udContact}
+		if !contact.Equal(expectedContacts[0], cb.c[0]) {
+			t.Errorf("Failed to get expected Contacts."+
+				"\n\texpected: %+v\n\treceived: %+v", expectedContacts, cb.c)
+		}
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Callback not called.")
+	}
+}
+
+//
+// // Error path: the callback returns an error.
+// func TestManager_Search_CallbackError(t *testing.T) {
+// 	isReg := uint32(1)
+// 	// Set up manager
+// 	m := &Manager{
+// 		rng:        fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+// 		grp:        cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+// 		storage:    storage.InitTestingSession(t),
+// 		udContact:  contact.Contact{ID: &id.UDB},
+// 		net:        newTestNetworkManager(t),
+// 		registered: &isReg,
+// 	}
+//
+// 	// Generate callback function
+// 	callbackChan := make(chan struct {
+// 		c   []contact.Contact
+// 		err error
+// 	})
+// 	callback := func(c []contact.Contact, err error) {
+// 		callbackChan <- struct {
+// 			c   []contact.Contact
+// 			err error
+// 		}{c: c, err: err}
+// 	}
+//
+// 	// Generate fact list
+// 	factList := fact.FactList{
+// 		{Fact: "fact1", T: fact.Username},
+// 		{Fact: "fact2", T: fact.Email},
+// 		{Fact: "fact3", T: fact.Phone},
+// 	}
+//
+// 	// Trigger lookup response chan
+// 	// go func() {
+// 	// 	time.Sleep(1 * time.Millisecond)
+// 	// 	m.inProgressSearch[0] <- &SearchResponse{
+// 	// 		Contacts: nil,
+// 	// 		Error:    "Error",
+// 	// 	}
+// 	// }()
+//
+// 	// Run the search
+// 	err := m.Search(factList, callback, 10*time.Millisecond)
+// 	if err != nil {
+// 		t.Errorf("Search() returned an error: %+v", err)
+// 	}
+//
+// 	// Verify the callback is called
+// 	select {
+// 	case cb := <-callbackChan:
+// 		if cb.err == nil {
+// 			t.Error("Callback did not return an expected error.")
+// 		}
+//
+// 		if cb.c != nil {
+// 			t.Errorf("Failed to get expected Contacts."+
+// 				"\n\texpected: %v\n\treceived: %v", nil, cb.c)
+// 		}
+// 	case <-time.After(100 * time.Millisecond):
+// 		t.Error("Callback not called.")
+// 	}
+//
+// 	// if _, exists := m.inProgressSearch[m.commID-1]; exists {
+// 	// 	t.Error("Failed to delete SearchResponse from inProgressSearch.")
+// 	// }
+// }
+//
+// // Error path: the round event chan times out.
+// func TestManager_Search_EventChanTimeout(t *testing.T) {
+// 	isReg := uint32(1)
+// 	// Set up manager
+// 	m := &Manager{
+// 		rng:        fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
+// 		grp:        cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+// 		storage:    storage.InitTestingSession(t),
+// 		udContact:  contact.Contact{ID: &id.UDB},
+// 		net:        newTestNetworkManager(t),
+// 		registered: &isReg,
+// 	}
+//
+// 	// Generate callback function
+// 	callbackChan := make(chan struct {
+// 		c   []contact.Contact
+// 		err error
+// 	})
+// 	callback := func(c []contact.Contact, err error) {
+// 		callbackChan <- struct {
+// 			c   []contact.Contact
+// 			err error
+// 		}{c: c, err: err}
+// 	}
+//
+// 	// Generate fact list
+// 	factList := fact.FactList{
+// 		{Fact: "fact1", T: fact.Username},
+// 		{Fact: "fact2", T: fact.Email},
+// 		{Fact: "fact3", T: fact.Phone},
+// 	}
+//
+// 	// Run the search
+// 	err := m.Search(factList, callback, 10*time.Millisecond)
+// 	if err != nil {
+// 		t.Errorf("Search() returned an error: %+v", err)
+// 	}
+//
+// 	// Verify the callback is called
+// 	select {
+// 	case cb := <-callbackChan:
+// 		if cb.err == nil {
+// 			t.Error("Callback did not return an expected error.")
+// 		}
+//
+// 		if cb.c != nil {
+// 			t.Errorf("Failed to get expected Contacts."+
+// 				"\n\texpected: %v\n\treceived: %v", nil, cb.c)
+// 		}
+// 	case <-time.After(100 * time.Millisecond):
+// 		t.Error("Callback not called.")
+// 	}
+//
+// 	// if _, exists := m.inProgressSearch[m.commID-1]; exists {
+// 	// 	t.Error("Failed to delete SearchResponse from inProgressSearch.")
+// 	// }
+// }
+
+// Happy path.
+func TestManager_searchResponseHandler(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	callbackChan := make(chan struct {
+		c   []contact.Contact
+		err error
+	})
+	callback := func(c []contact.Contact, err error) {
+		callbackChan <- struct {
+			c   []contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	// Generate fact list
+	var factList fact.FactList
+	for i := 0; i < 10; i++ {
+		factList = append(factList, fact.Fact{
+			Fact: fmt.Sprintf("fact %d", i),
+			T:    fact.FactType(rand.Intn(4)),
+		})
+	}
+	factHashes, factMap := hashFactList(factList)
+
+	var contacts []*Contact
+	var expectedContacts []contact.Contact
+	for i, hash := range factHashes {
+		contacts = append(contacts, &Contact{
+			UserID:    id.NewIdFromString("user", id.User, t).Marshal(),
+			PubKey:    []byte{byte(i + 1)},
+			TrigFacts: []*HashFact{hash},
+		})
+		expectedContacts = append(expectedContacts, contact.Contact{
+			ID:       id.NewIdFromString("user", id.User, t),
+			DhPubKey: m.grp.NewIntFromBytes([]byte{byte(i + 1)}),
+			Facts:    fact.FactList{factMap[string(hash.Hash)]},
+		})
+	}
+
+	// Generate expected Send message
+	payload, err := proto.Marshal(&SearchResponse{Contacts: contacts})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+
+	m.searchResponseHandler(factMap, callback, payload, nil)
+
+	select {
+	case results := <-callbackChan:
+		if results.err != nil {
+			t.Errorf("Callback returned an error: %+v", results.err)
+		}
+		if !reflect.DeepEqual(expectedContacts, results.c) {
+			t.Errorf("Callback returned incorrect Contacts."+
+				"\nexpected: %+v\nreceived: %+v", expectedContacts, results.c)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// Happy path: error is returned on callback when passed into function.
+func TestManager_searchResponseHandler_CallbackError(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	callbackChan := make(chan struct {
+		c   []contact.Contact
+		err error
+	})
+	callback := func(c []contact.Contact, err error) {
+		callbackChan <- struct {
+			c   []contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	testErr := errors.New("search failure")
+
+	m.searchResponseHandler(map[string]fact.Fact{}, callback, []byte{}, testErr)
+
+	select {
+	case results := <-callbackChan:
+		if results.err == nil || !strings.Contains(results.err.Error(), testErr.Error()) {
+			t.Errorf("Callback failed to return error."+
+				"\nexpected: %+v\nreceived: %+v", testErr, results.err)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// Error path: SearchResponse message contains an error.
+func TestManager_searchResponseHandler_MessageError(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	callbackChan := make(chan struct {
+		c   []contact.Contact
+		err error
+	})
+	callback := func(c []contact.Contact, err error) {
+		callbackChan <- struct {
+			c   []contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	// Generate expected Send message
+	testErr := "SearchResponse error occurred"
+	payload, err := proto.Marshal(&SearchResponse{Error: testErr})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+
+	m.searchResponseHandler(map[string]fact.Fact{}, callback, payload, nil)
+
+	select {
+	case results := <-callbackChan:
+		if results.err == nil || !strings.Contains(results.err.Error(), testErr) {
+			t.Errorf("Callback failed to return error."+
+				"\nexpected: %s\nreceived: %+v", testErr, results.err)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// Error path: contact is malformed and cannot be parsed.
+func TestManager_searchResponseHandler_ParseContactError(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	callbackChan := make(chan struct {
+		c   []contact.Contact
+		err error
+	})
+	callback := func(c []contact.Contact, err error) {
+		callbackChan <- struct {
+			c   []contact.Contact
+			err error
+		}{c: c, err: err}
+	}
+
+	var contacts []*Contact
+	for i := 0; i < 10; i++ {
+		contacts = append(contacts, &Contact{
+			UserID: []byte{byte(i + 1)},
+		})
+	}
+
+	// Generate expected Send message
+	payload, err := proto.Marshal(&SearchResponse{Contacts: contacts})
+	if err != nil {
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+	}
+
+	m.searchResponseHandler(nil, callback, payload, nil)
+
+	select {
+	case results := <-callbackChan:
+		if results.err == nil || !strings.Contains(results.err.Error(), "failed to parse Contact user ID") {
+			t.Errorf("Callback failed to return error: %+v", results.err)
+		}
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
+	}
+}
+
+// Happy path.
+func Test_hashFactList(t *testing.T) {
+	var factList fact.FactList
+	var expectedHashFacts []*HashFact
+	expectedHashMap := make(map[string]fact.Fact)
+	for i := 0; i < 10; i++ {
+		f := fact.Fact{
+			Fact: fmt.Sprintf("fact %d", i),
+			T:    fact.FactType(rand.Intn(4)),
+		}
+		factList = append(factList, f)
+		expectedHashFacts = append(expectedHashFacts, &HashFact{
+			Hash: factID.Fingerprint(f),
+			Type: int32(f.T),
+		})
+		expectedHashMap[string(factID.Fingerprint(f))] = f
+	}
+
+	hashFacts, hashMap := hashFactList(factList)
+
+	if !reflect.DeepEqual(expectedHashFacts, hashFacts) {
+		t.Errorf("hashFactList() failed to return the expected hash facts."+
+			"\nexpected: %+v\nreceived: %+v", expectedHashFacts, hashFacts)
+	}
+
+	if !reflect.DeepEqual(expectedHashMap, hashMap) {
+		t.Errorf("hashFactList() failed to return the expected hash map."+
+			"\nexpected: %+v\nreceived: %+v", expectedHashMap, hashMap)
+	}
+}
+
+// Happy path.
+func TestManager_parseContacts(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+
+	// Generate fact list
+	var factList fact.FactList
+	for i := 0; i < 10; i++ {
+		factList = append(factList, fact.Fact{
+			Fact: fmt.Sprintf("fact %d", i),
+			T:    fact.FactType(rand.Intn(4)),
+		})
+	}
+	factHashes, factMap := hashFactList(factList)
+
+	var contacts []*Contact
+	var expectedContacts []contact.Contact
+	for i, hash := range factHashes {
+		contacts = append(contacts, &Contact{
+			UserID:    id.NewIdFromString("user", id.User, t).Marshal(),
+			PubKey:    []byte{byte(i + 1)},
+			TrigFacts: []*HashFact{hash},
+		})
+		expectedContacts = append(expectedContacts, contact.Contact{
+			ID:       id.NewIdFromString("user", id.User, t),
+			DhPubKey: m.grp.NewIntFromBytes([]byte{byte(i + 1)}),
+			Facts:    fact.FactList{factMap[string(hash.Hash)]},
+		})
+	}
+
+	testContacts, err := m.parseContacts(contacts, factMap)
+	if err != nil {
+		t.Errorf("parseContacts() returned an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expectedContacts, testContacts) {
+		t.Errorf("parseContacts() did not return the expected contacts."+
+			"\nexpected: %+v\nreceived: %+v", expectedContacts, testContacts)
+	}
+}
+
+// Error path: provided contact IDs are malformed and cannot be unmarshaled.
+func TestManager_parseContacts_IdUnmarshalError(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
+	contacts := []*Contact{{UserID: []byte("invalid ID")}}
+
+	_, err := m.parseContacts(contacts, nil)
+	if err == nil || !strings.Contains(err.Error(), "failed to parse Contact user ID") {
+		t.Errorf("parseContacts() did not return an error when IDs are invalid: %+v", err)
+	}
+}
+
+// mockSingleSearch is used to test the search function, which uses the single-
+// use manager. It adheres to the SingleInterface interface.
+type mockSingleSearch struct {
+}
+
+func (s *mockSingleSearch) TransmitSingleUse(partner contact.Contact, payload []byte,
+	_ string, _ uint8, callback single.ReplyComm, _ time.Duration) error {
+
+	searchMsg := &SearchSend{}
+	if err := proto.Unmarshal(payload, searchMsg); err != nil {
+		return errors.Errorf("Failed to unmarshal SearchSend: %+v", err)
+	}
+
+	searchResponse := &SearchResponse{
+		Contacts: []*Contact{{
+			UserID: partner.ID.Marshal(),
+			PubKey: partner.DhPubKey.Bytes(),
+		}},
+	}
+	msg, err := proto.Marshal(searchResponse)
+	if err != nil {
+		return errors.Errorf("Failed to marshal SearchResponse: %+v", err)
+	}
+
+	callback(msg, nil)
+	return nil
+}
+
+func (s *mockSingleSearch) StartProcesses() stoppable.Stoppable {
+	return stoppable.NewSingle("")
+}
diff --git a/ud/udMessages.pb.go b/ud/udMessages.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..1a245ab0c669f283d9ac5ee522d6e3f763501c4e
--- /dev/null
+++ b/ud/udMessages.pb.go
@@ -0,0 +1,336 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: udMessages.proto
+
+package ud
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// Contains the Hash and its Type
+type HashFact struct {
+	Hash                 []byte   `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
+	Type                 int32    `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *HashFact) Reset()         { *m = HashFact{} }
+func (m *HashFact) String() string { return proto.CompactTextString(m) }
+func (*HashFact) ProtoMessage()    {}
+func (*HashFact) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9e0cfdc16fb09bb6, []int{0}
+}
+
+func (m *HashFact) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_HashFact.Unmarshal(m, b)
+}
+func (m *HashFact) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_HashFact.Marshal(b, m, deterministic)
+}
+func (m *HashFact) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_HashFact.Merge(m, src)
+}
+func (m *HashFact) XXX_Size() int {
+	return xxx_messageInfo_HashFact.Size(m)
+}
+func (m *HashFact) XXX_DiscardUnknown() {
+	xxx_messageInfo_HashFact.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HashFact proto.InternalMessageInfo
+
+func (m *HashFact) GetHash() []byte {
+	if m != nil {
+		return m.Hash
+	}
+	return nil
+}
+
+func (m *HashFact) GetType() int32 {
+	if m != nil {
+		return m.Type
+	}
+	return 0
+}
+
+// Describes a user lookup result. The ID, public key, and the
+// facts inputted that brought up this user.
+type Contact struct {
+	UserID               []byte      `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"`
+	PubKey               []byte      `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
+	TrigFacts            []*HashFact `protobuf:"bytes,3,rep,name=trigFacts,proto3" json:"trigFacts,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *Contact) Reset()         { *m = Contact{} }
+func (m *Contact) String() string { return proto.CompactTextString(m) }
+func (*Contact) ProtoMessage()    {}
+func (*Contact) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9e0cfdc16fb09bb6, []int{1}
+}
+
+func (m *Contact) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Contact.Unmarshal(m, b)
+}
+func (m *Contact) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Contact.Marshal(b, m, deterministic)
+}
+func (m *Contact) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Contact.Merge(m, src)
+}
+func (m *Contact) XXX_Size() int {
+	return xxx_messageInfo_Contact.Size(m)
+}
+func (m *Contact) XXX_DiscardUnknown() {
+	xxx_messageInfo_Contact.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Contact proto.InternalMessageInfo
+
+func (m *Contact) GetUserID() []byte {
+	if m != nil {
+		return m.UserID
+	}
+	return nil
+}
+
+func (m *Contact) GetPubKey() []byte {
+	if m != nil {
+		return m.PubKey
+	}
+	return nil
+}
+
+func (m *Contact) GetTrigFacts() []*HashFact {
+	if m != nil {
+		return m.TrigFacts
+	}
+	return nil
+}
+
+// Message sent to UDB to search for users
+type SearchSend struct {
+	// PublicKey used in the registration
+	Fact                 []*HashFact `protobuf:"bytes,1,rep,name=fact,proto3" json:"fact,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *SearchSend) Reset()         { *m = SearchSend{} }
+func (m *SearchSend) String() string { return proto.CompactTextString(m) }
+func (*SearchSend) ProtoMessage()    {}
+func (*SearchSend) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9e0cfdc16fb09bb6, []int{2}
+}
+
+func (m *SearchSend) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SearchSend.Unmarshal(m, b)
+}
+func (m *SearchSend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SearchSend.Marshal(b, m, deterministic)
+}
+func (m *SearchSend) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SearchSend.Merge(m, src)
+}
+func (m *SearchSend) XXX_Size() int {
+	return xxx_messageInfo_SearchSend.Size(m)
+}
+func (m *SearchSend) XXX_DiscardUnknown() {
+	xxx_messageInfo_SearchSend.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SearchSend proto.InternalMessageInfo
+
+func (m *SearchSend) GetFact() []*HashFact {
+	if m != nil {
+		return m.Fact
+	}
+	return nil
+}
+
+// Message sent from UDB to client in response to a search
+type SearchResponse struct {
+	// ID of the session created
+	Contacts             []*Contact `protobuf:"bytes,1,rep,name=contacts,proto3" json:"contacts,omitempty"`
+	Error                string     `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
+}
+
+func (m *SearchResponse) Reset()         { *m = SearchResponse{} }
+func (m *SearchResponse) String() string { return proto.CompactTextString(m) }
+func (*SearchResponse) ProtoMessage()    {}
+func (*SearchResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9e0cfdc16fb09bb6, []int{3}
+}
+
+func (m *SearchResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_SearchResponse.Unmarshal(m, b)
+}
+func (m *SearchResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_SearchResponse.Marshal(b, m, deterministic)
+}
+func (m *SearchResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SearchResponse.Merge(m, src)
+}
+func (m *SearchResponse) XXX_Size() int {
+	return xxx_messageInfo_SearchResponse.Size(m)
+}
+func (m *SearchResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_SearchResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SearchResponse proto.InternalMessageInfo
+
+func (m *SearchResponse) GetContacts() []*Contact {
+	if m != nil {
+		return m.Contacts
+	}
+	return nil
+}
+
+func (m *SearchResponse) GetError() string {
+	if m != nil {
+		return m.Error
+	}
+	return ""
+}
+
+// Message sent to UDB for looking up a user
+type LookupSend struct {
+	UserID               []byte   `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *LookupSend) Reset()         { *m = LookupSend{} }
+func (m *LookupSend) String() string { return proto.CompactTextString(m) }
+func (*LookupSend) ProtoMessage()    {}
+func (*LookupSend) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9e0cfdc16fb09bb6, []int{4}
+}
+
+func (m *LookupSend) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_LookupSend.Unmarshal(m, b)
+}
+func (m *LookupSend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_LookupSend.Marshal(b, m, deterministic)
+}
+func (m *LookupSend) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LookupSend.Merge(m, src)
+}
+func (m *LookupSend) XXX_Size() int {
+	return xxx_messageInfo_LookupSend.Size(m)
+}
+func (m *LookupSend) XXX_DiscardUnknown() {
+	xxx_messageInfo_LookupSend.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LookupSend proto.InternalMessageInfo
+
+func (m *LookupSend) GetUserID() []byte {
+	if m != nil {
+		return m.UserID
+	}
+	return nil
+}
+
+// Message sent from UDB for looking up a user
+type LookupResponse struct {
+	PubKey               []byte   `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
+	Error                string   `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *LookupResponse) Reset()         { *m = LookupResponse{} }
+func (m *LookupResponse) String() string { return proto.CompactTextString(m) }
+func (*LookupResponse) ProtoMessage()    {}
+func (*LookupResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9e0cfdc16fb09bb6, []int{5}
+}
+
+func (m *LookupResponse) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_LookupResponse.Unmarshal(m, b)
+}
+func (m *LookupResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_LookupResponse.Marshal(b, m, deterministic)
+}
+func (m *LookupResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LookupResponse.Merge(m, src)
+}
+func (m *LookupResponse) XXX_Size() int {
+	return xxx_messageInfo_LookupResponse.Size(m)
+}
+func (m *LookupResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_LookupResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LookupResponse proto.InternalMessageInfo
+
+func (m *LookupResponse) GetPubKey() []byte {
+	if m != nil {
+		return m.PubKey
+	}
+	return nil
+}
+
+func (m *LookupResponse) GetError() string {
+	if m != nil {
+		return m.Error
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*HashFact)(nil), "parse.HashFact")
+	proto.RegisterType((*Contact)(nil), "parse.Contact")
+	proto.RegisterType((*SearchSend)(nil), "parse.SearchSend")
+	proto.RegisterType((*SearchResponse)(nil), "parse.SearchResponse")
+	proto.RegisterType((*LookupSend)(nil), "parse.LookupSend")
+	proto.RegisterType((*LookupResponse)(nil), "parse.LookupResponse")
+}
+
+func init() { proto.RegisterFile("udMessages.proto", fileDescriptor_9e0cfdc16fb09bb6) }
+
+var fileDescriptor_9e0cfdc16fb09bb6 = []byte{
+	// 266 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xc1, 0x4b, 0xc3, 0x30,
+	0x14, 0xc6, 0xc9, 0xda, 0xce, 0xed, 0x39, 0xaa, 0x04, 0x91, 0x1e, 0x4b, 0xf4, 0x50, 0x04, 0x0b,
+	0xce, 0xbb, 0x07, 0x15, 0x51, 0xd4, 0x4b, 0x76, 0xf3, 0x96, 0xb5, 0xcf, 0x55, 0x84, 0x26, 0xe4,
+	0x25, 0x87, 0xfd, 0xf7, 0xd2, 0x34, 0x6e, 0x08, 0xf3, 0x96, 0xef, 0xbd, 0xf7, 0xe3, 0xfb, 0xde,
+	0x0b, 0x9c, 0xfa, 0xf6, 0x1d, 0x89, 0xd4, 0x06, 0xa9, 0x36, 0x56, 0x3b, 0xcd, 0x33, 0xa3, 0x2c,
+	0xa1, 0x58, 0xc2, 0xec, 0x59, 0x51, 0xf7, 0xa4, 0x1a, 0xc7, 0x39, 0xa4, 0x9d, 0xa2, 0xae, 0x60,
+	0x25, 0xab, 0x16, 0x32, 0xbc, 0x87, 0x9a, 0xdb, 0x1a, 0x2c, 0x26, 0x25, 0xab, 0x32, 0x19, 0xde,
+	0xa2, 0x83, 0xa3, 0x07, 0xdd, 0xbb, 0x01, 0x39, 0x87, 0xa9, 0x27, 0xb4, 0x2f, 0x8f, 0x11, 0x8a,
+	0x6a, 0xa8, 0x1b, 0xbf, 0x7e, 0xc5, 0x6d, 0x00, 0x17, 0x32, 0x2a, 0x7e, 0x0d, 0x73, 0x67, 0xbf,
+	0x36, 0x83, 0x1d, 0x15, 0x49, 0x99, 0x54, 0xc7, 0xcb, 0x93, 0x3a, 0x24, 0xa9, 0x7f, 0x63, 0xc8,
+	0xfd, 0x84, 0xb8, 0x01, 0x58, 0xa1, 0xb2, 0x4d, 0xb7, 0xc2, 0xbe, 0xe5, 0x17, 0x90, 0x7e, 0xaa,
+	0xc6, 0x15, 0xec, 0x30, 0x17, 0x9a, 0x42, 0x42, 0x3e, 0x22, 0x12, 0xc9, 0xe8, 0x9e, 0x90, 0x5f,
+	0xc1, 0xac, 0x19, 0xe3, 0x52, 0x44, 0xf3, 0x88, 0xc6, 0x2d, 0xe4, 0xae, 0xcf, 0xcf, 0x20, 0x43,
+	0x6b, 0xb5, 0x2d, 0x92, 0x92, 0x55, 0x73, 0x39, 0x0a, 0x71, 0x09, 0xf0, 0xa6, 0xf5, 0xb7, 0x37,
+	0x21, 0xc6, 0x3f, 0x3b, 0x8b, 0x3b, 0xc8, 0xc7, 0xa9, 0x9d, 0xf3, 0xfe, 0x0a, 0xec, 0xcf, 0x15,
+	0x0e, 0xba, 0xdc, 0xa7, 0x1f, 0x13, 0xdf, 0xae, 0xa7, 0xe1, 0x7b, 0x6e, 0x7f, 0x02, 0x00, 0x00,
+	0xff, 0xff, 0xb4, 0xff, 0x7b, 0xf5, 0xb2, 0x01, 0x00, 0x00,
+}
diff --git a/ud/udMessages.proto b/ud/udMessages.proto
new file mode 100644
index 0000000000000000000000000000000000000000..0fdc127500d548390c5a8385b445b0ab31130f90
--- /dev/null
+++ b/ud/udMessages.proto
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Call ./generate.sh to generate the protocol buffer code
+
+syntax = "proto3";
+
+package parse;
+option go_package = "ud";
+
+// Contains the Hash and its Type
+message HashFact {
+  bytes hash = 1;
+  int32 type = 2;
+}
+
+// Describes a user lookup result. The ID, public key, and the
+// facts inputted that brought up this user.
+message Contact {
+  bytes userID = 1;
+  bytes pubKey = 2;
+  repeated HashFact trigFacts = 3;
+}
+
+// Message sent to UDB to search for users
+message SearchSend {
+  // PublicKey used in the registration
+  repeated HashFact fact = 1;
+}
+
+// Message sent from UDB to client in response to a search
+message SearchResponse {
+  // ID of the session created
+  repeated Contact contacts = 1;
+  string error = 3;
+}
+
+// Message sent to UDB for looking up a user
+message LookupSend {
+  bytes userID = 1;
+}
+
+// Message sent from UDB for looking up a user
+message LookupResponse {
+  bytes pubKey = 1;
+  string error = 3;
+}
\ No newline at end of file
diff --git a/user/regCode.go b/user/regCode.go
deleted file mode 100644
index 65094ad616ef680f8ea7a193d505c45e00cc1dc8..0000000000000000000000000000000000000000
--- a/user/regCode.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package user
-
-import (
-	"encoding/base32"
-	"gitlab.com/elixxir/primitives/id"
-	"golang.org/x/crypto/blake2b"
-)
-
-const RegCodeLen = 5
-
-func RegistrationCode(id *id.ID) string {
-	return base32.StdEncoding.EncodeToString(userHash(id))
-}
-
-func userHash(id *id.ID) []byte {
-	h, _ := blake2b.New256(nil)
-	h.Write(id.Marshal())
-	huid := h.Sum(nil)
-	huid = huid[len(huid)-RegCodeLen:]
-	return huid
-}
diff --git a/user/regState.go b/user/regState.go
deleted file mode 100644
index 62c88b2b59682c7bc6ea208efb232bd13c096530..0000000000000000000000000000000000000000
--- a/user/regState.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package user
-
-const (
-	NotStarted            uint32 = iota // Set on session creation
-	KeyGenComplete               = 1000 // Set upon generation of session information
-	PermissioningComplete        = 2000 // Set upon completion of RegisterWithPermissioning
-	UDBComplete                  = 3000 // Set upon completion of RegisterWithUdb
-)
diff --git a/user/session.go b/user/session.go
deleted file mode 100644
index 04665d6579c272144b08d222d7d7e911c882881b..0000000000000000000000000000000000000000
--- a/user/session.go
+++ /dev/null
@@ -1,787 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package user
-
-import (
-	"bytes"
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/rand"
-	"crypto/sha256"
-	"encoding/gob"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"gitlab.com/xx_network/comms/connect"
-	"io"
-	"sync"
-	"sync/atomic"
-	"time"
-)
-
-// Errors
-var ErrQuery = errors.New("element not in map")
-
-// Interface for User Session operations
-type Session interface {
-	GetCurrentUser() (currentUser *User)
-	GetNodeKeys(topology *connect.Circuit) []NodeKeys
-	PushNodeKey(id *id.ID, key NodeKeys)
-	GetRSAPrivateKey() *rsa.PrivateKey
-	GetRSAPublicKey() *rsa.PublicKey
-	GetCMIXDHPrivateKey() *cyclic.Int
-	GetCMIXDHPublicKey() *cyclic.Int
-	GetE2EDHPrivateKey() *cyclic.Int
-	GetE2EDHPublicKey() *cyclic.Int
-	GetCmixGroup() *cyclic.Group
-	GetE2EGroup() *cyclic.Group
-	GetLastMessageID() string
-	SetLastMessageID(id string)
-	StoreSession() error
-	Immolate() error
-	UpsertMap(key string, element interface{}) error
-	QueryMap(key string) (interface{}, error)
-	DeleteMap(key string) error
-	GetKeyStore() *keyStore.KeyStore
-	GetRekeyManager() *keyStore.RekeyManager
-	GetSwitchboard() *switchboard.Switchboard
-	GetQuitChan() chan struct{}
-	LockStorage()
-	UnlockStorage()
-	GetSessionData() ([]byte, error)
-	GetRegistrationValidationSignature() []byte
-	GetNodes() map[id.ID]int
-	AppendGarbledMessage(messages ...*format.Message)
-	PopGarbledMessages() []*format.Message
-	GetSalt() []byte
-	SetRegState(rs uint32) error
-	GetRegState() uint32
-	ChangeUsername(string) error
-	StorageIsEmpty() bool
-	GetContactByValue(string) (*id.ID, []byte)
-	StoreContactByValue(string, *id.ID, []byte)
-	DeleteContact(*id.ID) (string, error)
-	GetSessionLocation() uint8
-	LoadEncryptedSession(store globals.Storage) ([]byte, error)
-	RegisterPermissioningSignature(sig []byte) error
-}
-
-type NodeKeys struct {
-	TransmissionKey *cyclic.Int
-	ReceptionKey    *cyclic.Int
-}
-
-// Creates a new Session interface for registration
-func NewSession(store globals.Storage,
-	u *User,
-	publicKeyRSA *rsa.PublicKey,
-	privateKeyRSA *rsa.PrivateKey,
-	cmixPublicKeyDH *cyclic.Int,
-	cmixPrivateKeyDH *cyclic.Int,
-	e2ePublicKeyDH *cyclic.Int,
-	e2ePrivateKeyDH *cyclic.Int,
-	salt []byte,
-	cmixGrp, e2eGrp *cyclic.Group,
-	password string) Session {
-	regState := uint32(KeyGenComplete)
-	// With an underlying Session data structure
-	return Session(&SessionObj{
-		NodeKeys:            make(map[id.ID]NodeKeys),
-		CurrentUser:         u,
-		RSAPublicKey:        publicKeyRSA,
-		RSAPrivateKey:       privateKeyRSA,
-		CMIXDHPublicKey:     cmixPublicKeyDH,
-		CMIXDHPrivateKey:    cmixPrivateKeyDH,
-		E2EDHPublicKey:      e2ePublicKeyDH,
-		E2EDHPrivateKey:     e2ePrivateKeyDH,
-		CmixGrp:             cmixGrp,
-		E2EGrp:              e2eGrp,
-		InterfaceMap:        make(map[string]interface{}),
-		KeyMaps:             keyStore.NewStore(),
-		RekeyManager:        keyStore.NewRekeyManager(),
-		store:               store,
-		listeners:           switchboard.NewSwitchboard(),
-		quitReceptionRunner: make(chan struct{}),
-		password:            password,
-		Salt:                salt,
-		RegState:            &regState,
-		storageLocation:     globals.LocationA,
-		ContactsByValue:     make(map[string]SearchedUserRecord),
-	})
-}
-
-//LoadSession loads the encrypted session from the storage location and processes it
-// Returns a session object on success
-func LoadSession(store globals.Storage, password string) (Session, error) {
-	if store == nil {
-		err := errors.New("LoadSession: Local Storage not available")
-		return nil, err
-	}
-
-	wrappedSession, loadLocation, err := processSession(store, password)
-	if err != nil {
-		return nil, err
-	}
-
-	for wrappedSession.Version != SessionVersion {
-		switch wrappedSession.Version {
-		case 1:
-			globals.Log.INFO.Println("Converting session file from V1 to V2")
-			wrappedSession, err = ConvertSessionV1toV2(wrappedSession)
-		default:
-		}
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	//extract teh session from the wrapper
-	var sessionBytes bytes.Buffer
-
-	sessionBytes.Write(wrappedSession.Session)
-	dec := gob.NewDecoder(&sessionBytes)
-
-	session := SessionObj{}
-
-	err = dec.Decode(&session)
-	if err != nil {
-		return nil, errors.Wrap(err, "Unable to decode session")
-	}
-
-	session.storageLocation = loadLocation
-
-	// Reconstruct Key maps
-	session.KeyMaps.ReconstructKeys(session.E2EGrp,
-		session.CurrentUser.User)
-	// Create switchboard
-	session.listeners = switchboard.NewSwitchboard()
-	// Create quit channel for reception runner
-	session.quitReceptionRunner = make(chan struct{})
-
-	// Set storage pointer
-	session.store = store
-	session.password = password
-
-	if session.NodeKeys == nil {
-		session.NodeKeys = make(map[id.ID]NodeKeys)
-	}
-
-	return &session, nil
-}
-
-//processSession: gets the loadLocation and decrypted wrappedSession
-func processSession(store globals.Storage, password string) (*SessionStorageWrapper, uint8, error) {
-	var wrappedSession *SessionStorageWrapper
-	loadLocation := globals.NoSave
-	//load sessions
-	wrappedSessionA, errA := processSessionWrapper(store.LoadA(), password)
-	wrappedSessionB, errB := processSessionWrapper(store.LoadB(), password)
-
-	//figure out which session to use of the two locations
-	if errA != nil && errB != nil {
-		return nil, globals.NoSave, errors.Errorf("Loading both sessions errored: \n "+
-			"SESSION A ERR: %s \n SESSION B ERR: %s", errA, errB)
-	} else if errA == nil && errB != nil {
-		loadLocation = globals.LocationA
-		wrappedSession = wrappedSessionA
-	} else if errA != nil && errB == nil {
-		loadLocation = globals.LocationB
-		wrappedSession = wrappedSessionB
-	} else {
-		if wrappedSessionA.Timestamp.After(wrappedSessionB.Timestamp) {
-			loadLocation = globals.LocationA
-			wrappedSession = wrappedSessionA
-		} else {
-			loadLocation = globals.LocationB
-			wrappedSession = wrappedSessionB
-		}
-	}
-	return wrappedSession, loadLocation, nil
-
-}
-
-//processSessionWrapper acts as a helper function for processSession
-func processSessionWrapper(sessionGob []byte, password string) (*SessionStorageWrapper, error) {
-
-	if sessionGob == nil || len(sessionGob) < 12 {
-		return nil, errors.New("No session file passed")
-	}
-
-	decryptedSessionGob, err := decrypt(sessionGob, password)
-
-	if err != nil {
-		return nil, errors.Wrap(err, "Could not decode the "+
-			"session wrapper")
-	}
-
-	var sessionBytes bytes.Buffer
-
-	sessionBytes.Write(decryptedSessionGob)
-	dec := gob.NewDecoder(&sessionBytes)
-
-	wrappedSession := SessionStorageWrapper{}
-
-	err = dec.Decode(&wrappedSession)
-	if err != nil {
-		return nil, errors.Wrap(err, "Unable to decode session wrapper")
-	}
-
-	return &wrappedSession, nil
-}
-
-// Struct holding relevant session data
-// When adding to this structure, ALWAYS ALWAYS
-// consider if you want the data to be in the session file
-type SessionObj struct {
-	// Currently authenticated user
-	CurrentUser *User
-
-	NodeKeys         map[id.ID]NodeKeys
-	RSAPrivateKey    *rsa.PrivateKey
-	RSAPublicKey     *rsa.PublicKey
-	CMIXDHPrivateKey *cyclic.Int
-	CMIXDHPublicKey  *cyclic.Int
-	E2EDHPrivateKey  *cyclic.Int
-	E2EDHPublicKey   *cyclic.Int
-	CmixGrp          *cyclic.Group
-	E2EGrp           *cyclic.Group
-	Salt             []byte
-
-	// Last received message ID. Check messages after this on the gateway.
-	LastMessageID string
-
-	//Interface map for random data storage
-	InterfaceMap map[string]interface{}
-
-	// E2E KeyStore
-	KeyMaps *keyStore.KeyStore
-
-	// Rekey Manager
-	RekeyManager *keyStore.RekeyManager
-
-	// Non exported fields (not GOB encoded/decoded)
-	// Local pointer to storage of this session
-	store globals.Storage
-
-	// Switchboard
-	listeners *switchboard.Switchboard
-
-	// Quit channel for message reception runner
-	quitReceptionRunner chan struct{}
-
-	lock sync.Mutex
-
-	// The password used to encrypt this session when saved
-	password string
-
-	//The validation signature provided by permissioning
-	RegValidationSignature []byte
-
-	// Buffer of messages that cannot be decrypted
-	garbledMessages []*format.Message
-
-	RegState *uint32
-
-	storageLocation uint8
-
-	ContactsByValue map[string]SearchedUserRecord
-}
-
-//WriteToSession: Writes to the location where session is being stored the arbitrary replacement string
-// The replacement string is meant to be the output of a loadEncryptedSession
-func WriteToSession(replacement []byte, store globals.Storage) error {
-	//Write to both
-	err := store.SaveA(replacement)
-	if err != nil {
-		return errors.Errorf("Failed to save to session A: %v", err)
-	}
-	err = store.SaveB(replacement)
-	if err != nil {
-		return errors.Errorf("Failed to save to session B: %v", err)
-	}
-
-	return nil
-}
-
-//LoadEncryptedSession: gets the encrypted session file from storage
-// Returns it as a base64 encoded string
-func (s *SessionObj) LoadEncryptedSession(store globals.Storage) ([]byte, error) {
-	sessionData, _, err := processSession(store, s.password)
-	if err != nil {
-		return make([]byte, 0), err
-	}
-	encryptedSession := encrypt(sessionData.Session, s.password)
-	return encryptedSession, nil
-}
-
-type SearchedUserRecord struct {
-	Id id.ID
-	Pk []byte
-}
-
-func (s *SessionObj) GetLastMessageID() string {
-	s.LockStorage()
-	defer s.UnlockStorage()
-
-	return s.LastMessageID
-}
-
-func (s *SessionObj) StorageIsEmpty() bool {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.store.IsEmpty()
-}
-
-func (s *SessionObj) SetLastMessageID(id string) {
-	s.LockStorage()
-	s.LastMessageID = id
-	s.UnlockStorage()
-}
-
-func (s *SessionObj) GetNodes() map[id.ID]int {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	nodes := make(map[id.ID]int, 0)
-	for node := range s.NodeKeys {
-		nodes[node] = 1
-	}
-	return nodes
-}
-
-func (s *SessionObj) GetSalt() []byte {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	salt := make([]byte, len(s.Salt))
-	copy(salt, s.Salt)
-	return salt
-}
-
-func (s *SessionObj) GetNodeKeys(topology *connect.Circuit) []NodeKeys {
-	s.LockStorage()
-	defer s.UnlockStorage()
-
-	keys := make([]NodeKeys, topology.Len())
-
-	for i := 0; i < topology.Len(); i++ {
-		keys[i] = s.NodeKeys[*topology.GetNodeAtIndex(i)]
-	}
-
-	return keys
-}
-
-func (s *SessionObj) PushNodeKey(id *id.ID, key NodeKeys) {
-	s.LockStorage()
-	defer s.UnlockStorage()
-
-	s.NodeKeys[*id] = key
-
-	return
-}
-
-//RegisterPermissioningSignature sets sessions registration signature and
-// sets the regState to reflect that registering with permissioning is complete
-// Returns an error if unable to set the regState
-func (s *SessionObj) RegisterPermissioningSignature(sig []byte) error {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	err := s.SetRegState(PermissioningComplete)
-	if err != nil {
-		return errors.Wrap(err, "Could not store permissioning signature")
-	}
-
-	s.RegValidationSignature = sig
-
-	//storing to ensure we never loose the signature
-	err = s.storeSession()
-
-	return err
-}
-
-func (s *SessionObj) GetRSAPrivateKey() *rsa.PrivateKey {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.RSAPrivateKey
-}
-
-func (s *SessionObj) GetRSAPublicKey() *rsa.PublicKey {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.RSAPublicKey
-}
-
-func (s *SessionObj) GetCMIXDHPrivateKey() *cyclic.Int {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.CMIXDHPrivateKey
-}
-
-func (s *SessionObj) GetCMIXDHPublicKey() *cyclic.Int {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.CMIXDHPublicKey
-}
-
-func (s *SessionObj) GetE2EDHPrivateKey() *cyclic.Int {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.E2EDHPrivateKey
-}
-
-func (s *SessionObj) GetE2EDHPublicKey() *cyclic.Int {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.E2EDHPublicKey
-}
-
-func (s *SessionObj) GetCmixGroup() *cyclic.Group {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.CmixGrp
-}
-
-func (s *SessionObj) GetRegistrationValidationSignature() []byte {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.RegValidationSignature
-}
-
-func (s *SessionObj) GetE2EGroup() *cyclic.Group {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.E2EGrp
-}
-
-// Return a copy of the current user
-func (s *SessionObj) GetCurrentUser() (currentUser *User) {
-	// This is where it deadlocks
-	s.LockStorage()
-	defer s.UnlockStorage()
-	if s.CurrentUser != nil {
-		// Explicit deep copy
-		currentUser = &User{
-			User:     s.CurrentUser.User,
-			Username: s.CurrentUser.Username,
-			Precan:   s.CurrentUser.Precan,
-		}
-	}
-	return currentUser
-}
-
-func (s *SessionObj) GetRegState() uint32 {
-	return atomic.LoadUint32(s.RegState)
-}
-
-func (s *SessionObj) SetRegState(rs uint32) error {
-	prevRs := rs - 1000
-	b := atomic.CompareAndSwapUint32(s.RegState, prevRs, rs)
-	if !b {
-		return errors.New("Could not increment registration state")
-	}
-	return nil
-}
-
-func (s *SessionObj) ChangeUsername(username string) error {
-	b := s.GetRegState()
-	if b != PermissioningComplete {
-		return errors.New("Can only change username during " +
-			"PermissioningComplete registration state")
-	}
-	s.CurrentUser.Username = username
-	return nil
-}
-
-type SessionStorageWrapper struct {
-	Version   uint32
-	Timestamp time.Time
-	Session   []byte
-}
-
-func (s *SessionObj) storeSession() error {
-
-	if s.store == nil {
-		err := errors.New("StoreSession: Local Storage not available")
-		return err
-	}
-
-	sessionData, err := s.getSessionData()
-
-	encryptedSession := encrypt(sessionData, s.password)
-	if s.storageLocation == globals.LocationA {
-		err = s.store.SaveB(encryptedSession)
-		if err != nil {
-			err = errors.New(fmt.Sprintf("StoreSession: Could not save the encoded user"+
-				" session in location B: %s", err.Error()))
-		} else {
-			s.storageLocation = globals.LocationB
-		}
-	} else if s.storageLocation == globals.LocationB {
-		err = s.store.SaveA(encryptedSession)
-		if err != nil {
-			err = errors.New(fmt.Sprintf("StoreSession: Could not save the encoded user"+
-				" session in location A: %s", err.Error()))
-		} else {
-			s.storageLocation = globals.LocationA
-		}
-	} else {
-		err = errors.New("Could not store because no location is " +
-			"selected")
-	}
-
-	return err
-
-}
-
-func (s *SessionObj) StoreSession() error {
-	s.LockStorage()
-	err := s.storeSession()
-	s.UnlockStorage()
-	return err
-}
-
-// Immolate scrubs all cryptographic data from ram and logs out
-// the ram overwriting can be improved
-func (s *SessionObj) Immolate() error {
-	s.LockStorage()
-	if s == nil {
-		err := errors.New("immolate: Cannot immolate that which has no life")
-		return err
-	}
-
-	globals.Log.WARN.Println("Immolate not implemented, did nothing")
-
-	s.UnlockStorage()
-
-	return nil
-}
-
-//Upserts an element into the interface map and saves the session object
-func (s *SessionObj) UpsertMap(key string, element interface{}) error {
-	s.LockStorage()
-	s.InterfaceMap[key] = element
-	err := s.storeSession()
-	s.UnlockStorage()
-	return err
-}
-
-//Pulls an element from the interface in the map
-func (s *SessionObj) QueryMap(key string) (interface{}, error) {
-	var err error
-	s.LockStorage()
-	element, ok := s.InterfaceMap[key]
-	if !ok {
-		err = ErrQuery
-		element = nil
-	}
-	s.UnlockStorage()
-	return element, err
-}
-
-func (s *SessionObj) DeleteMap(key string) error {
-	s.LockStorage()
-	delete(s.InterfaceMap, key)
-	err := s.storeSession()
-	s.UnlockStorage()
-	return err
-}
-
-func (s *SessionObj) GetSessionData() ([]byte, error) {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	return s.getSessionData()
-}
-
-func (s *SessionObj) GetKeyStore() *keyStore.KeyStore {
-	return s.KeyMaps
-}
-
-func (s *SessionObj) GetRekeyManager() *keyStore.RekeyManager {
-	return s.RekeyManager
-}
-
-func (s *SessionObj) GetSwitchboard() *switchboard.Switchboard {
-	return s.listeners
-}
-
-func (s *SessionObj) GetQuitChan() chan struct{} {
-	return s.quitReceptionRunner
-}
-
-func (s *SessionObj) getSessionData() ([]byte, error) {
-	var sessionBuffer bytes.Buffer
-
-	enc := gob.NewEncoder(&sessionBuffer)
-
-	err := enc.Encode(s)
-
-	if err != nil {
-		err = errors.New(fmt.Sprintf("StoreSession: Could not encode user"+
-			" session: %s", err.Error()))
-		return nil, err
-	}
-
-	sw := SessionStorageWrapper{
-		Version:   SessionVersion,
-		Session:   sessionBuffer.Bytes(),
-		Timestamp: time.Now(),
-	}
-
-	var wrapperBuffer bytes.Buffer
-
-	enc = gob.NewEncoder(&wrapperBuffer)
-
-	err = enc.Encode(&sw)
-
-	if err != nil {
-		err = errors.New(fmt.Sprintf("StoreSession: Could not encode user"+
-			" session wrapper: %s", err.Error()))
-		return nil, err
-	}
-
-	return wrapperBuffer.Bytes(), nil
-}
-
-// Locking a mutex that belongs to the session object makes the locking
-// independent of the implementation of the storage, which is probably good.
-func (s *SessionObj) LockStorage() {
-	s.lock.Lock()
-}
-
-func (s *SessionObj) UnlockStorage() {
-	s.lock.Unlock()
-}
-
-func clearCyclicInt(c *cyclic.Int) {
-	c.Reset()
-	//c.Set(cyclic.NewMaxInt())
-	//c.SetInt64(0)
-}
-
-// FIXME Shouldn't we just be putting pseudorandom bytes in to obscure the mem?
-func burntString(length int) string {
-	b := make([]byte, length)
-
-	rand.Read(b)
-
-	return string(b)
-}
-
-// Internal crypto helper functions below
-
-func hashPassword(password string) []byte {
-	hasher := sha256.New()
-	hasher.Write([]byte(password))
-	return hasher.Sum(nil)
-}
-
-func initAESGCM(password string) cipher.AEAD {
-	aesCipher, _ := aes.NewCipher(hashPassword(password))
-	// NOTE: We use gcm as it's authenticated and simplest to set up
-	aesGCM, err := cipher.NewGCM(aesCipher)
-	if err != nil {
-		globals.Log.FATAL.Panicf("Could not init AES GCM mode: %s",
-			err.Error())
-	}
-	return aesGCM
-}
-
-func encrypt(data []byte, password string) []byte {
-	aesGCM := initAESGCM(password)
-	nonce := make([]byte, aesGCM.NonceSize())
-	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
-		globals.Log.FATAL.Panicf("Could not generate nonce: %s",
-			err.Error())
-	}
-	ciphertext := aesGCM.Seal(nonce, nonce, data, nil)
-	return ciphertext
-}
-
-func decrypt(data []byte, password string) ([]byte, error) {
-	aesGCM := initAESGCM(password)
-	nonceLen := aesGCM.NonceSize()
-	nonce, ciphertext := data[:nonceLen], data[nonceLen:]
-	plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "Cannot decrypt with password!")
-	}
-	return plaintext, nil
-}
-
-// AppendGarbledMessage appends a message or messages to the garbled message
-// buffer.
-// FIXME: improve performance of adding items to the buffer
-func (s *SessionObj) AppendGarbledMessage(messages ...*format.Message) {
-	s.garbledMessages = append(s.garbledMessages, messages...)
-}
-
-// PopGarbledMessages returns the content of the garbled message buffer and
-// deletes its contents.
-func (s *SessionObj) PopGarbledMessages() []*format.Message {
-	tempBuffer := s.garbledMessages
-	s.garbledMessages = []*format.Message{}
-	return tempBuffer
-}
-
-func (s *SessionObj) GetContactByValue(v string) (*id.ID, []byte) {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	u, ok := s.ContactsByValue[v]
-	if !ok {
-		return nil, nil
-	}
-	return &(u.Id), u.Pk
-}
-
-func (s *SessionObj) StoreContactByValue(v string, uid *id.ID, pk []byte) {
-	s.LockStorage()
-	defer s.UnlockStorage()
-	u, ok := s.ContactsByValue[v]
-	if ok {
-		globals.Log.WARN.Printf("Attempted to store over extant "+
-			"user value: %s; before: %v, new: %v", v, u.Id, *uid)
-	} else {
-		s.ContactsByValue[v] = SearchedUserRecord{
-			Id: *uid,
-			Pk: pk,
-		}
-	}
-}
-
-func (s *SessionObj) DeleteContact(uid *id.ID) (string, error) {
-	s.LockStorage()
-	defer s.UnlockStorage()
-
-	for v, u := range s.ContactsByValue {
-		if u.Id.Cmp(uid) {
-			delete(s.ContactsByValue, v)
-			_, ok := s.ContactsByValue[v]
-			if ok {
-				return "", errors.Errorf("Failed to delete user: %+v", u)
-			} else {
-				return v, nil
-			}
-		}
-	}
-
-	return "", errors.Errorf("No user found in usermap with userid: %s",
-		uid)
-
-}
-
-func (s *SessionObj) GetSessionLocation() uint8 {
-	if s.storageLocation == globals.LocationA {
-		return globals.LocationA
-	} else if s.storageLocation == globals.LocationB {
-		return globals.LocationB
-	}
-	return globals.NoSave
-}
diff --git a/user/sessionVersion.go b/user/sessionVersion.go
deleted file mode 100644
index 79d18a4790e83d0d65337bc09bc0f077976aae38..0000000000000000000000000000000000000000
--- a/user/sessionVersion.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package user
-
-const SessionVersion = 2
diff --git a/user/session_test.go b/user/session_test.go
deleted file mode 100644
index ddf7201574ec3eb66bd06c5bb87c8932d4ccd5e5..0000000000000000000000000000000000000000
--- a/user/session_test.go
+++ /dev/null
@@ -1,686 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package user
-
-import (
-	"bytes"
-	"crypto/sha256"
-	"encoding/gob"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/xx_network/comms/connect"
-	"math/rand"
-	"reflect"
-	"testing"
-)
-
-// TestUserRegistry tests the constructors/getters/setters
-// surrounding the User struct and the Registry interface
-func TestUserSession(t *testing.T) {
-
-	test := 11
-	pass := 0
-
-	u := new(User)
-	// This is 65 so you can see the letter A in the gob if you need to make
-	// sure that the gob contains the user ID
-	UID := uint64(65)
-
-	u.User = id.NewIdFromUInt(UID, id.User, t)
-	u.Username = "Mario"
-
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-
-	nodeID := id.NewIdFromUInt(1, id.Node, t)
-
-	topology := connect.NewCircuit([]*id.ID{nodeID})
-
-	// Storage
-	storage := &globals.RamStorage{}
-
-	rng := rand.New(rand.NewSource(42))
-	privateKey, _ := rsa.GenerateKey(rng, 768)
-	publicKey := rsa.PublicKey{PublicKey: privateKey.PublicKey}
-
-	cmixGrp, e2eGrp := getGroups()
-
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewInt(1))
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewInt(1))
-
-	privateKeyDHE2E := e2eGrp.RandomCoprime(e2eGrp.NewInt(1))
-	publicKeyDHE2E := e2eGrp.ExpG(privateKeyDHE2E, e2eGrp.NewInt(1))
-
-	ses := NewSession(storage,
-		u, &publicKey, privateKey, publicKeyDH, privateKeyDH,
-		publicKeyDHE2E, privateKeyDHE2E, make([]byte, 1), cmixGrp, e2eGrp,
-		"password")
-
-	regSignature := make([]byte, 768)
-	rng.Read(regSignature)
-
-	err := ses.RegisterPermissioningSignature(regSignature)
-	if err != nil {
-		t.Errorf("failure in setting register up for permissioning: %s",
-			err.Error())
-	}
-
-	ses.PushNodeKey(nodeID, NodeKeys{
-		TransmissionKey: grp.NewInt(2),
-		ReceptionKey:    grp.NewInt(2),
-	})
-
-	ses.SetLastMessageID("totally unique ID")
-
-	err = ses.StoreSession()
-
-	if err != nil {
-		t.Errorf("Session not stored correctly: %s", err.Error())
-	}
-
-	ses.Immolate()
-
-	//TODO: write test which validates the immolation
-
-	ses, err = LoadSession(storage, "password")
-
-	if err != nil {
-		t.Errorf("Unable to login with valid user: %v",
-			err.Error())
-	} else {
-		pass++
-	}
-
-	if ses.GetLastMessageID() != "totally unique ID" {
-		t.Errorf("Last message ID should have been stored " +
-			"and loaded")
-	} else {
-		pass++
-	}
-
-	ses.SetLastMessageID("test")
-
-	if ses.GetLastMessageID() != "test" {
-		t.Errorf("Last message ID not set correctly with" +
-			" SetLastMessageID!")
-	} else {
-		pass++
-	}
-
-	if ses.GetNodeKeys(topology) == nil {
-		t.Errorf("Keys not set correctly!")
-	} else {
-
-		test += len(ses.GetNodeKeys(topology))
-
-		for i := 0; i < len(ses.GetNodeKeys(topology)); i++ {
-			orig := privateKey.PrivateKey
-			sesPriv := ses.GetRSAPrivateKey().PrivateKey
-			if !reflect.DeepEqual(*ses.GetRSAPublicKey(), publicKey) {
-				t.Errorf("Error: Public key not set correctly!")
-			} else if sesPriv.E != orig.E {
-				t.Errorf("Error: Private key not set correctly E!  \nExpected: %+v\nreceived: %+v",
-					orig.E, sesPriv.E)
-			} else if sesPriv.D.Cmp(orig.D) != 0 {
-				t.Errorf("Error: Private key not set correctly D!  \nExpected: %+v\nreceived: %+v",
-					orig.D, sesPriv.D)
-			} else if sesPriv.N.Cmp(orig.N) != 0 {
-				t.Errorf("Error: Private key not set correctly N!  \nExpected: %+v\nreceived: %+v",
-					orig.N, sesPriv.N)
-			} else if !reflect.DeepEqual(sesPriv.Primes, orig.Primes) {
-				t.Errorf("Error: Private key not set correctly PRIMES!  \nExpected: %+v\nreceived: %+v",
-					orig, sesPriv)
-			} else if ses.GetNodeKeys(topology)[i].ReceptionKey.Cmp(grp.
-				NewInt(2)) != 0 {
-				t.Errorf("Reception key not set correct!")
-			} else if ses.GetNodeKeys(topology)[i].TransmissionKey.Cmp(
-				grp.NewInt(2)) != 0 {
-				t.Errorf("Transmission key not set correctly!")
-			}
-
-			pass++
-		}
-	}
-
-	//TODO: FIX THIS?
-	if ses.GetRSAPrivateKey() == nil {
-		t.Errorf("Error: Private Keys not set correctly!")
-	} else {
-		pass++
-	}
-
-	err = ses.UpsertMap("test", 5)
-
-	if err != nil {
-		t.Errorf("Could not store in session map interface: %s",
-			err.Error())
-	}
-
-	element, err := ses.QueryMap("test")
-
-	if err != nil {
-		t.Errorf("Could not read element in session map "+
-			"interface: %s", err.Error())
-	}
-
-	if element.(int) != 5 {
-		t.Errorf("Could not read element in session map "+
-			"interface: Expected: 5, Recieved: %v", element)
-	}
-
-	ses.DeleteMap("test")
-
-	_, err = ses.QueryMap("test")
-
-	if err == nil {
-		t.Errorf("Could not delete element in session map " +
-			"interface")
-	}
-
-	//Logout
-	ses.Immolate()
-
-	// Error tests
-
-	// Test nil LocalStorage
-
-	_, err = LoadSession(nil, "password")
-
-	if err == nil {
-		t.Errorf("Error did not catch a nil LocalStorage")
-	}
-
-	// Test invalid / corrupted LocalStorage
-	h := sha256.New()
-	h.Write([]byte(string(20000)))
-	randBytes := h.Sum(nil)
-	storage.SaveA(randBytes)
-	storage.SaveB(randBytes)
-
-	defer func() {
-		recover()
-	}()
-
-	_, err = LoadSession(storage, "password")
-	if err == nil {
-		t.Errorf("LoadSession should error on bad decrypt!")
-	}
-}
-
-func TestSessionObj_DeleteContact(t *testing.T) {
-	u := new(User)
-	// This is 65 so you can see the letter A in the gob if you need to make
-	// sure that the gob contains the user ID
-	UID := uint64(65)
-
-	u.User = id.NewIdFromUInt(UID, id.User, t)
-	u.Username = "Mario"
-
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-
-	nodeID := id.NewIdFromUInt(1, id.Node, t)
-
-	// Storage
-	storage := &globals.RamStorage{}
-
-	rng := rand.New(rand.NewSource(42))
-	privateKey, _ := rsa.GenerateKey(rng, 768)
-	publicKey := rsa.PublicKey{PublicKey: privateKey.PublicKey}
-
-	cmixGrp, e2eGrp := getGroups()
-
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewInt(1))
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewInt(1))
-
-	privateKeyDHE2E := e2eGrp.RandomCoprime(e2eGrp.NewInt(1))
-	publicKeyDHE2E := e2eGrp.ExpG(privateKeyDHE2E, e2eGrp.NewInt(1))
-
-	ses := NewSession(storage,
-		u, &publicKey, privateKey, publicKeyDH, privateKeyDH,
-		publicKeyDHE2E, privateKeyDHE2E, make([]byte, 1), cmixGrp, e2eGrp,
-		"password")
-
-	regSignature := make([]byte, 768)
-	rng.Read(regSignature)
-
-	err := ses.RegisterPermissioningSignature(regSignature)
-	if err != nil {
-		t.Errorf("failure in setting register up for permissioning: %s",
-			err.Error())
-	}
-
-	ses.PushNodeKey(nodeID, NodeKeys{
-		TransmissionKey: grp.NewInt(2),
-		ReceptionKey:    grp.NewInt(2),
-	})
-
-	testContact := id.NewIdFromString("test", id.User, t)
-	ses.StoreContactByValue("test", testContact, []byte("test"))
-
-	_, err = ses.DeleteContact(testContact)
-	if err != nil {
-		t.Errorf("Failed to delete contact: %+v", err)
-	}
-}
-
-func TestGetPubKey(t *testing.T) {
-	u := new(User)
-	UID := id.NewIdFromUInt(1, id.User, t)
-
-	u.User = UID
-	u.Username = "Mario"
-
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-
-	rng := rand.New(rand.NewSource(42))
-	privateKey, _ := rsa.GenerateKey(rng, 768)
-	publicKey := rsa.PublicKey{PublicKey: privateKey.PublicKey}
-
-	cmixGrp, e2eGrp := getGroups()
-
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewInt(1))
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewInt(1))
-
-	privateKeyDHE2E := e2eGrp.RandomCoprime(e2eGrp.NewInt(1))
-	publicKeyDHE2E := e2eGrp.ExpG(privateKeyDHE2E, e2eGrp.NewInt(1))
-
-	ses := NewSession(&globals.RamStorage{},
-		u, &publicKey, privateKey, publicKeyDH, privateKeyDH,
-		publicKeyDHE2E, privateKeyDHE2E, make([]byte, 1), cmixGrp, e2eGrp,
-		"password")
-
-	regSignature := make([]byte, 768)
-	rng.Read(regSignature)
-
-	err := ses.RegisterPermissioningSignature(regSignature)
-	if err != nil {
-		t.Errorf("failure in setting register up for permissioning: %s",
-			err.Error())
-	}
-
-	ses.PushNodeKey(id.NewIdFromUInt(1, id.Node, t), NodeKeys{
-		TransmissionKey: grp.NewInt(2),
-		ReceptionKey:    grp.NewInt(2),
-	})
-
-	pubKey := *ses.GetRSAPublicKey()
-	if !reflect.DeepEqual(pubKey, publicKey) {
-		t.Errorf("Public key not returned correctly!")
-	}
-}
-
-//Tests the isEmpty function before and after StoreSession
-func TestSessionObj_StorageIsEmpty(t *testing.T) {
-	// Generate all the values needed for a session
-	u := new(User)
-	// This is 65 so you can see the letter A in the gob if you need to make
-	// sure that the gob contains the user ID
-	UID := uint64(65)
-
-	u.User = id.NewIdFromUInt(UID, id.User, t)
-	u.Username = "Mario"
-
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-
-	nodeID := id.NewIdFromUInt(1, id.Node, t)
-	// Storage
-	storage := &globals.RamStorage{}
-
-	//Keys
-	rng := rand.New(rand.NewSource(42))
-	privateKey, _ := rsa.GenerateKey(rng, 768)
-	publicKey := rsa.PublicKey{PublicKey: privateKey.PublicKey}
-	cmixGrp, e2eGrp := getGroups()
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewInt(1))
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewInt(1))
-	privateKeyDHE2E := e2eGrp.RandomCoprime(e2eGrp.NewInt(1))
-	publicKeyDHE2E := e2eGrp.ExpG(privateKeyDHE2E, e2eGrp.NewInt(1))
-
-	ses := NewSession(storage,
-		u, &publicKey, privateKey, publicKeyDH, privateKeyDH,
-		publicKeyDHE2E, privateKeyDHE2E, make([]byte, 1), cmixGrp, e2eGrp,
-		"password")
-
-	regSignature := make([]byte, 768)
-	rng.Read(regSignature)
-
-	ses.PushNodeKey(nodeID, NodeKeys{
-		TransmissionKey: grp.NewInt(2),
-		ReceptionKey:    grp.NewInt(2),
-	})
-
-	ses.SetLastMessageID("totally unique ID")
-
-	//Test that the session is empty before the StoreSession call
-	if !ses.StorageIsEmpty() {
-		t.Errorf("session should be empty before the StoreSession call")
-	}
-	err := ses.StoreSession()
-	if err != nil {
-		t.Errorf("Failed to store session: %v", err)
-	}
-
-	//Test that the session is not empty after the StoreSession call
-	if ses.StorageIsEmpty() {
-		t.Errorf("session should not be empty after a StoreSession call")
-	}
-
-}
-
-// GetContactByValue happy path
-func TestSessionObj_GetContactByValue(t *testing.T) {
-	// Generate all the values needed for a session
-	u := new(User)
-	// This is 65 so you can see the letter A in the gob if you need to make
-	// sure that the gob contains the user ID
-	UID := uint64(65)
-
-	u.User = id.NewIdFromUInt(UID, id.User, t)
-	u.Username = "Mario"
-
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-
-	nodeID := id.NewIdFromUInt(1, id.Node, t)
-
-	// Storage
-	storage := &globals.RamStorage{}
-
-	//Keys
-	rng := rand.New(rand.NewSource(42))
-	privateKey, _ := rsa.GenerateKey(rng, 768)
-	publicKey := rsa.PublicKey{PublicKey: privateKey.PublicKey}
-	cmixGrp, e2eGrp := getGroups()
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewInt(1))
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewInt(1))
-	privateKeyDHE2E := e2eGrp.RandomCoprime(e2eGrp.NewInt(1))
-	publicKeyDHE2E := e2eGrp.ExpG(privateKeyDHE2E, e2eGrp.NewInt(1))
-
-	ses := NewSession(storage,
-		u, &publicKey, privateKey, publicKeyDH, privateKeyDH,
-		publicKeyDHE2E, privateKeyDHE2E, make([]byte, 1), cmixGrp, e2eGrp,
-		"password")
-
-	regSignature := make([]byte, 768)
-	rng.Read(regSignature)
-
-	err := ses.RegisterPermissioningSignature(regSignature)
-	if err != nil {
-		t.Errorf("failure in setting register up for permissioning: %s",
-			err.Error())
-	}
-
-	ses.PushNodeKey(nodeID, NodeKeys{
-		TransmissionKey: grp.NewInt(2),
-		ReceptionKey:    grp.NewInt(2),
-	})
-
-	userId := id.NewIdFromBytes([]byte("test"), t)
-
-	ses.StoreContactByValue("value", userId, []byte("test"))
-
-	observedUser, observedPk := ses.GetContactByValue("value")
-
-	if bytes.Compare([]byte("test"), observedPk) != 0 {
-		t.Errorf("Failed to retieve public key using GetContactByValue; "+
-			"Expected: %+v\n\tRecieved: %+v", privateKey.PublicKey.N.Bytes(), observedPk)
-	}
-
-	if !observedUser.Cmp(userId) {
-		t.Errorf("Failed to retrieve user using GetContactByValue;"+
-			"Expected: %+v\n\tRecieved: %+v", u.User, observedUser)
-	}
-}
-
-func TestGetPrivKey(t *testing.T) {
-	u := new(User)
-	UID := id.NewIdFromUInt(1, id.User, t)
-
-	u.User = UID
-	u.Username = "Mario"
-
-	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
-
-	rng := rand.New(rand.NewSource(42))
-	privateKey, _ := rsa.GenerateKey(rng, 768)
-	publicKey := rsa.PublicKey{PublicKey: privateKey.PublicKey}
-
-	cmixGrp, e2eGrp := getGroups()
-
-	privateKeyDH := cmixGrp.RandomCoprime(cmixGrp.NewInt(1))
-	publicKeyDH := cmixGrp.ExpG(privateKeyDH, cmixGrp.NewInt(1))
-
-	privateKeyDHE2E := e2eGrp.RandomCoprime(e2eGrp.NewInt(1))
-	publicKeyDHE2E := e2eGrp.ExpG(privateKeyDHE2E, e2eGrp.NewInt(1))
-
-	ses := NewSession(&globals.RamStorage{},
-		u, &publicKey, privateKey, publicKeyDH, privateKeyDH,
-		publicKeyDHE2E, privateKeyDHE2E, make([]byte, 1), cmixGrp, e2eGrp,
-		"password")
-
-	regSignature := make([]byte, 768)
-	rng.Read(regSignature)
-
-	err := ses.RegisterPermissioningSignature(regSignature)
-	if err != nil {
-		t.Errorf("failure in setting register up for permissioning: %s",
-			err.Error())
-	}
-
-	ses.PushNodeKey(id.NewIdFromUInt(1, id.Node, t), NodeKeys{
-		TransmissionKey: grp.NewInt(2),
-		ReceptionKey:    grp.NewInt(2),
-	})
-
-	privKey := ses.GetRSAPrivateKey()
-	if !reflect.DeepEqual(*privKey, *privateKey) {
-		t.Errorf("Private key is not returned correctly!")
-	}
-}
-
-func TestBruntString(t *testing.T) {
-	// Generate a new user and record the pointer to the nick
-	u := new(User)
-	u.Username = "Mario"
-	preBurnPointer := &u.Username
-
-	// Burn the string and record the pointer to the nick
-	u.Username = burntString(len(u.Username))
-	postBurnPointer := &u.Username
-
-	// Check the nick is not the same as before
-	if u.Username == "Mario" {
-		t.Errorf("String was not burnt")
-	}
-
-	// Check the pointer is the same (otherwise it wasn't overwritten)
-	if preBurnPointer != postBurnPointer {
-		t.Errorf("Pointer values are not the same")
-	}
-}
-
-func getGroups() (*cyclic.Group, *cyclic.Group) {
-
-	cmixGrp := cyclic.NewGroup(
-		large.NewIntFromString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+
-			"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+
-			"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+
-			"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+
-			"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"+
-			"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"+
-			"83655D23DCA3AD961C62F356208552BB9ED529077096966D"+
-			"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"+
-			"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"+
-			"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"+
-			"15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16),
-		large.NewIntFromString("2", 16))
-
-	e2eGrp := cyclic.NewGroup(
-		large.NewIntFromString("E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B"+
-			"7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AE"+
-			"DF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F"+
-			"8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041"+
-			"023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF45"+
-			"3B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE9209"+
-			"6EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29"+
-			"A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E"+
-			"37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F2"+
-			"78DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696"+
-			"015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E"+
-			"6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873"+
-			"847AEF49F66E43873", 16),
-		large.NewIntFromString("2", 16))
-
-	return cmixGrp, e2eGrp
-
-}
-
-// Tests that AppendGarbledMessage properly appends an array of messages by
-// testing that the final buffer matches the values appended.
-func TestSessionObj_AppendGarbledMessage(t *testing.T) {
-	session := NewSession(nil, nil, nil, nil,
-		nil, nil, nil,
-		nil, nil, nil, nil, "")
-	msgs := GenerateTestMessages(10)
-
-	session.AppendGarbledMessage(msgs...)
-
-	if !reflect.DeepEqual(msgs, session.(*SessionObj).garbledMessages) {
-		t.Errorf("AppendGarbledMessage() did not append the correct values"+
-			"\n\texpected: %v\n\trecieved: %v",
-			msgs, session.(*SessionObj).garbledMessages)
-	}
-}
-
-// Tests that PopGarbledMessages returns the correct data and that the buffer
-// is cleared.
-func TestSessionObj_PopGarbledMessages(t *testing.T) {
-	session := NewSession(nil, nil, nil, nil,
-		nil, nil, nil,
-		nil, nil, nil, nil, "")
-	msgs := GenerateTestMessages(10)
-
-	session.(*SessionObj).garbledMessages = msgs
-
-	poppedMsgs := session.PopGarbledMessages()
-
-	if !reflect.DeepEqual(msgs, poppedMsgs) {
-		t.Errorf("PopGarbledMessages() did not pop the correct values"+
-			"\n\texpected: %v\n\trecieved: %v",
-			msgs, poppedMsgs)
-	}
-
-	if !reflect.DeepEqual([]*format.Message{}, session.(*SessionObj).garbledMessages) {
-		t.Errorf("PopGarbledMessages() did not remove the values from the buffer"+
-			"\n\texpected: %#v\n\trecieved: %#v",
-			[]*format.Message{}, session.(*SessionObj).garbledMessages)
-	}
-
-}
-
-/*// Tests ConvertSessionV1toV2() by creating an empty session object and setting
-// the RegState to the version 1, running it through the function, and testing
-// that RegState has values that match version 2.
-func TestSessionObj_ConvertSessionV1toV2(t *testing.T) {
-	ses := SessionObj{}
-	number := uint32(0)
-	ses.RegState = &number
-
-	ConvertSessionV1toV2(&ses)
-
-	if *ses.RegState != 0 {
-		t.Errorf("ConvertSessionV1toV2() did not properly convert the "+
-			"session object's RegState\n\texpected: %v\n\treceived: %v",
-			0, *ses.RegState)
-	}
-
-	number = uint32(1)
-	ses.RegState = &number
-
-	ConvertSessionV1toV2(&ses)
-
-	if *ses.RegState != 2000 {
-		t.Errorf("ConvertSessionV1toV2() did not properly convert the "+
-			"session object's RegState\n\texpected: %v\n\treceived: %v",
-			2000, *ses.RegState)
-	}
-
-	number = uint32(2)
-	ses.RegState = &number
-
-	ConvertSessionV1toV2(&ses)
-
-	if *ses.RegState != 3000 {
-		t.Errorf("ConvertSessionV1toV2() did not properly convert the "+
-			"session object's RegState\n\texpected: %v\n\treceived: %v",
-			3000, *ses.RegState)
-	}
-}*/
-
-func GenerateTestMessages(size int) []*format.Message {
-	msgs := make([]*format.Message, size)
-
-	for i := 0; i < size; i++ {
-		msgs[i] = format.NewMessage()
-		payloadBytes := make([]byte, format.PayloadLen)
-		payloadBytes[0] = byte(i)
-		msgs[i].SetPayloadA(payloadBytes)
-		msgs[i].SetPayloadB(payloadBytes)
-	}
-
-	return msgs
-}
-
-// Happy path
-func TestConvertSessionV1toV2(t *testing.T) {
-	u := new(User)
-	UID := id.NewIdFromUInt(1, id.Node, t)
-
-	u.User = UID
-	u.Username = "Bernie"
-
-	session := NewSession(nil, u, nil, nil,
-		nil, nil, nil,
-		nil, nil, nil, nil, "")
-	var sessionBuffer bytes.Buffer
-
-	enc := gob.NewEncoder(&sessionBuffer)
-
-	err := enc.Encode(session)
-	if err != nil {
-		t.Errorf("Failed to getSessionData: %+v", err)
-	}
-
-	storageWrapper := &SessionStorageWrapper{Version: 1, Session: sessionBuffer.Bytes()}
-	newSession, err := ConvertSessionV1toV2(storageWrapper)
-	if err != nil {
-		t.Errorf("Failed conversion: %+v", err)
-	}
-
-	if newSession.Version != SessionVersion {
-		t.Errorf("ConvertSessionV1toV2 should modify version number")
-	}
-
-}
-
-// Error path: Pass in an improper session
-func TestConvertSessionV1toV2_Error(t *testing.T) {
-	// Pass in an improper session
-	var sessionBuffer bytes.Buffer
-
-	_ = gob.NewEncoder(&sessionBuffer)
-
-	storageWrapper := &SessionStorageWrapper{Version: 1, Session: sessionBuffer.Bytes()}
-
-	_, err := ConvertSessionV1toV2(storageWrapper)
-	if err == nil {
-		t.Errorf("Failed conversion: %+v", err)
-	}
-
-}
diff --git a/user/sessionv1.go b/user/sessionv1.go
deleted file mode 100644
index 3f470aa558447c8edf0aad156db45e6a23d52d21..0000000000000000000000000000000000000000
--- a/user/sessionv1.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package user
-
-import (
-	"bytes"
-	"encoding/gob"
-	"fmt"
-	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/keyStore"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/switchboard"
-	"sync"
-)
-
-// Struct holding relevant session data
-type SessionObjV1 struct {
-	// Currently authenticated user
-	CurrentUser *UserV1
-
-	Keys             map[id.ID]NodeKeys
-	RSAPrivateKey    *rsa.PrivateKey
-	RSAPublicKey     *rsa.PublicKey
-	CMIXDHPrivateKey *cyclic.Int
-	CMIXDHPublicKey  *cyclic.Int
-	E2EDHPrivateKey  *cyclic.Int
-	E2EDHPublicKey   *cyclic.Int
-	CmixGrp          *cyclic.Group
-	E2EGrp           *cyclic.Group
-	Salt             []byte
-
-	// Last received message ID. Check messages after this on the gateway.
-	LastMessageID string
-
-	//Interface map for random data storage
-	InterfaceMap map[string]interface{}
-
-	// E2E KeyStore
-	KeyMaps *keyStore.KeyStore
-
-	// Rekey Manager
-	RekeyManager *keyStore.RekeyManager
-
-	// Non exported fields (not GOB encoded/decoded)
-	// Local pointer to storage of this session
-	store globals.Storage
-
-	// Switchboard
-	listeners *switchboard.Switchboard
-
-	// Quit channel for message reception runner
-	quitReceptionRunner chan struct{}
-
-	lock sync.Mutex
-
-	// The password used to encrypt this session when saved
-	password string
-
-	//The validation signature provided by permissioning
-	regValidationSignature []byte
-
-	// Buffer of messages that cannot be decrypted
-	garbledMessages []*format.Message
-
-	RegState *uint32
-
-	storageLocation uint8
-
-	ContactsByValue map[string]SearchedUserRecord
-}
-
-// Struct representing a User in the system
-type UserV1 struct {
-	User  *id.ID
-	Nick  string
-	Email string
-}
-
-// ConvertSessionV1toV2 converts the session object from version 1 to version 2.
-// This conversion includes:
-//  1. Changing the RegState values to the new integer values (1 to 2000, and 2
-//     to 3000).
-func ConvertSessionV1toV2(inputWrappedSession *SessionStorageWrapper) (*SessionStorageWrapper, error) {
-	//extract teh session from the wrapper
-	var sessionBytes bytes.Buffer
-
-	//get the old session object
-	sessionBytes.Write(inputWrappedSession.Session)
-	dec := gob.NewDecoder(&sessionBytes)
-
-	sessionV1 := SessionObjV1{}
-
-	err := dec.Decode(&sessionV1)
-	if err != nil {
-		return nil, errors.Wrap(err, "Unable to decode session")
-	}
-
-	sessionV2 := SessionObj{}
-
-	// Convert RegState to new values
-	if *sessionV1.RegState == 1 {
-		*sessionV1.RegState = 2000
-	} else if *sessionV1.RegState == 2 {
-		*sessionV1.RegState = 3000
-	}
-
-	//convert the user object
-	sessionV2.CurrentUser = &User{
-		User:     sessionV1.CurrentUser.User,
-		Username: sessionV1.CurrentUser.Email,
-	}
-
-	//port identical values over
-	sessionV2.NodeKeys = sessionV1.Keys
-	sessionV2.RSAPrivateKey = sessionV1.RSAPrivateKey
-	sessionV2.RSAPublicKey = sessionV1.RSAPublicKey
-	sessionV2.CMIXDHPrivateKey = sessionV1.CMIXDHPrivateKey
-	sessionV2.CMIXDHPublicKey = sessionV1.CMIXDHPublicKey
-	sessionV2.E2EDHPrivateKey = sessionV1.E2EDHPrivateKey
-	sessionV2.E2EDHPublicKey = sessionV1.E2EDHPublicKey
-	sessionV2.CmixGrp = sessionV1.CmixGrp
-	sessionV2.E2EGrp = sessionV1.E2EGrp
-	sessionV2.Salt = sessionV1.Salt
-	sessionV2.LastMessageID = sessionV1.LastMessageID
-	sessionV2.InterfaceMap = sessionV1.InterfaceMap
-	sessionV2.KeyMaps = sessionV1.KeyMaps
-	sessionV2.RekeyManager = sessionV1.RekeyManager
-	sessionV2.RegValidationSignature = sessionV1.regValidationSignature
-	sessionV2.RegState = sessionV1.RegState
-	sessionV2.ContactsByValue = sessionV1.ContactsByValue
-
-	//re encode the session
-	var sessionBuffer bytes.Buffer
-
-	enc := gob.NewEncoder(&sessionBuffer)
-
-	err = enc.Encode(sessionV2)
-
-	if err != nil {
-		err = errors.New(fmt.Sprintf("ConvertSessionV1toV2: Could not "+
-			" store session v2: %s", err.Error()))
-		return nil, err
-	}
-
-	//build the session wrapper
-	ssw := SessionStorageWrapper{
-		Version:   2,
-		Timestamp: inputWrappedSession.Timestamp,
-		Session:   sessionBuffer.Bytes(),
-	}
-
-	return &ssw, nil
-}
diff --git a/user/user.go b/user/user.go
deleted file mode 100644
index 45e0bc65cf265247df30e08c830ee87d958f006c..0000000000000000000000000000000000000000
--- a/user/user.go
+++ /dev/null
@@ -1,178 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package user
-
-import (
-	"crypto/sha256"
-	"encoding/binary"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/primitives/id"
-)
-
-// Globally instantiated Registry
-var Users Registry
-
-const NumDemoUsers = 40
-
-var DemoUserNicks = []string{"David", "Payments", "UDB", "Jim", "Ben", "Steph",
-	"Rick", "Jake", "Niamh", "Stephanie", "Mario", "Jono", "Amanda",
-	"Margaux", "Kevin", "Bruno", "Konstantino", "Bernardo", "Tigran",
-	"Kate", "Will", "Katie", "Bryan"}
-var DemoChannelNames = []string{"#General", "#Engineering", "#Lunch",
-	"#Random"}
-
-func InitUserRegistry(grp *cyclic.Group) {
-	Users = newRegistry(grp)
-}
-
-// Interface for User Registry operations
-type Registry interface {
-	NewUser(id *id.ID, nickname string) *User
-	DeleteUser(id *id.ID)
-	GetUser(id *id.ID) (user *User, ok bool)
-	UpsertUser(user *User)
-	CountUsers() int
-	LookupUser(hid string) (uid *id.ID, ok bool)
-	LookupKeys(uid *id.ID) (*NodeKeys, bool)
-}
-
-type UserMap struct {
-	// Map acting as the User Registry containing User -> ID mapping
-	userCollection map[id.ID]*User
-	// Increments sequentially for User.ID values
-	idCounter uint64
-	// Temporary map acting as a lookup table for demo user registration codes
-	// Key type is string because keys must implement == and []byte doesn't
-	userLookup map[string]*id.ID
-	//Temporary placed to store the keys for each user
-	keysLookup map[id.ID]*NodeKeys
-}
-
-// newRegistry creates a new Registry interface
-func newRegistry(grp *cyclic.Group) Registry {
-	if len(DemoChannelNames) > 10 || len(DemoUserNicks) > 30 {
-		globals.Log.ERROR.Print("Not enough demo users have been hardcoded.")
-	}
-	userUserIdMap := make(map[id.ID]*User)
-	userRegCodeMap := make(map[string]*id.ID)
-	nk := make(map[id.ID]*NodeKeys)
-
-	// Deterministically create NumDemoUsers users
-	// TODO Replace this with real user registration/discovery
-	for i := uint64(1); i <= NumDemoUsers; i++ {
-		currentID := new(id.ID)
-		binary.BigEndian.PutUint64(currentID[:], i)
-		currentID.SetType(id.User)
-		newUsr := new(User)
-		nodeKey := new(NodeKeys)
-
-		// Generate user parameters
-		newUsr.User = currentID
-		newUsr.Precan = true
-		// TODO We need a better way to generate base/recursive keys
-		h := sha256.New()
-		h.Write([]byte(string(40000 + i)))
-		nodeKey.TransmissionKey = grp.NewIntFromBytes(h.Sum(nil))
-		h = sha256.New()
-		h.Write([]byte(string(60000 + i)))
-		nodeKey.ReceptionKey = grp.NewIntFromBytes(h.Sum(nil))
-
-		// Add user to collection and lookup table
-		userUserIdMap[*newUsr.User] = newUsr
-		// Detect collisions in the registration code
-		if _, ok := userRegCodeMap[RegistrationCode(newUsr.User)]; ok {
-			globals.Log.ERROR.Printf(
-				"Collision in demo user list creation at %v. "+
-					"Please fix ASAP (include more bits to the reg code.", i)
-		}
-		userRegCodeMap[RegistrationCode(newUsr.User)] = newUsr.User
-		nk[*newUsr.User] = nodeKey
-	}
-
-	// Channels have been hardcoded to users starting with 31
-	for i := 0; i < len(DemoUserNicks); i++ {
-		currentID := new(id.ID)
-		binary.BigEndian.PutUint64(currentID[:], uint64(i)+1)
-		currentID.SetType(id.User)
-		userUserIdMap[*currentID].Username = DemoUserNicks[i]
-	}
-
-	for i := 0; i < len(DemoChannelNames); i++ {
-		currentID := new(id.ID)
-		binary.BigEndian.PutUint64(currentID[:], uint64(i)+31)
-		currentID.SetType(id.User)
-		userUserIdMap[*currentID].Username = DemoChannelNames[i]
-	}
-
-	// With an underlying UserMap data structure
-	return Registry(&UserMap{userCollection: userUserIdMap,
-		idCounter:  uint64(NumDemoUsers),
-		userLookup: userRegCodeMap,
-		keysLookup: nk})
-}
-
-// Struct representing a User in the system
-type User struct {
-	User     *id.ID
-	Username string
-	Precan   bool
-}
-
-// DeepCopy performs a deep copy of a user and returns a pointer to the new copy
-func (u *User) DeepCopy() *User {
-	if u == nil {
-		return nil
-	}
-	nu := new(User)
-	nu.User = u.User
-	nu.Username = u.Username
-	nu.Precan = u.Precan
-	return nu
-}
-
-// NewUser creates a new User object with default fields and given address.
-func (m *UserMap) NewUser(id *id.ID, username string) *User {
-	return &User{User: id, Username: username}
-}
-
-// GetUser returns a user with the given ID from userCollection
-// and a boolean for whether the user exists
-func (m *UserMap) GetUser(id *id.ID) (user *User, ok bool) {
-	user, ok = m.userCollection[*id]
-	user = user.DeepCopy()
-	return
-}
-
-// DeleteContactKeys deletes a user with the given ID from userCollection.
-func (m *UserMap) DeleteUser(id *id.ID) {
-	// If key does not exist, do nothing
-	delete(m.userCollection, *id)
-}
-
-// UpsertUser inserts given user into userCollection or update the user if it
-// already exists (Upsert operation).
-func (m *UserMap) UpsertUser(user *User) {
-	m.userCollection[*user.User] = user
-}
-
-// CountUsers returns a count of the users in userCollection
-func (m *UserMap) CountUsers() int {
-	return len(m.userCollection)
-}
-
-// LookupUser returns the user id corresponding to the demo registration code
-func (m *UserMap) LookupUser(hid string) (*id.ID, bool) {
-	uid, ok := m.userLookup[hid]
-	return uid, ok
-}
-
-// LookupKeys returns the keys for the given user from the temporary key map
-func (m *UserMap) LookupKeys(uid *id.ID) (*NodeKeys, bool) {
-	nk, t := m.keysLookup[*uid]
-	return nk, t
-}
diff --git a/user/user_test.go b/user/user_test.go
deleted file mode 100644
index 52d303e432c7c0a686b30406db2b130a7b46ce73..0000000000000000000000000000000000000000
--- a/user/user_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2019 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-package user
-
-import (
-	"crypto/sha256"
-	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/large"
-	"gitlab.com/elixxir/primitives/id"
-	"testing"
-)
-
-// TestUserRegistry tests the constructors/getters/setters
-// surrounding the User struct and the Registry interface
-func TestUserRegistry(t *testing.T) {
-	InitUserRegistry(InitGroup())
-	// Test if CountUsers correctly counts the hard-coded demo users
-	if Users.CountUsers() != NumDemoUsers {
-		t.Errorf("CountUsers: Start size of userRegistry not zero!")
-	}
-	// Test the integration of the LookupUser, UserHash and GetUser functions
-	for i := 0; i < len(DemoUserNicks); i++ {
-		currentID := id.NewIdFromUInt(uint64(i+1), id.User, t)
-		reg, ok := Users.LookupUser(RegistrationCode(currentID))
-		if !ok {
-			t.Errorf("Couldn't lookup user %q with code %v", *currentID,
-				RegistrationCode(currentID))
-		}
-		usr, ok := Users.GetUser(reg)
-		if !ok {
-			t.Logf("Reg codes of both: %v, %v", RegistrationCode(reg),
-				RegistrationCode(currentID))
-			t.Errorf("Couldn't get user %q corresponding to user %q",
-				*reg, *currentID)
-		}
-		if usr.Username != DemoUserNicks[i] {
-			t.Errorf("Nickname incorrectly set. Expected: %v Actual: %v",
-				DemoUserNicks[i], usr.Username)
-		}
-	}
-	// Test the NewUser function
-	newID := id.NewIdFromUInt(2002, id.User, t)
-	usr := Users.NewUser(newID, "Will I am")
-
-	if usr.Username != "Will I am" {
-		t.Errorf("User name should be 'Will I am', but is %v instead", usr.Username)
-	}
-
-	// Test that UpsertUser successfully adds a user to the usermap
-	userCount := Users.CountUsers()
-	Users.UpsertUser(usr)
-	if Users.CountUsers() != userCount+1 {
-		t.Errorf("Upsert did not add a new user. User count is incorrect")
-	}
-	newUsr, suc := Users.GetUser(newID)
-	if !suc {
-		t.Errorf("Upsert did not add the test user correctly. " +
-			"The ID was not found by GetUser.")
-	}
-	if newUsr.Username != "Will I am" {
-		t.Errorf("Upsert did not add the test user correctly. "+
-			"The set nickname was incorrect. Expected: Will I am, "+
-			"Actual: %v", newUsr.Username)
-	}
-
-	// Initialize group
-	grp := InitGroup()
-
-	// Test LookupKeys
-	keys, suc := Users.LookupKeys(id.NewIdFromUInt(1, id.User, t))
-	if !suc {
-		t.Errorf("LookupKeys failed to find a valid user.")
-	}
-	h := sha256.New()
-	h.Write([]byte(string(40001)))
-	key := grp.NewIntFromBytes(h.Sum(nil))
-	if keys.TransmissionKey.Text(16) != key.Text(16) {
-		t.Errorf("LookupKeys returned an incorrect key. "+
-			"Expected:%v \nActual%v", key.Text(16),
-			keys.TransmissionKey.Text(16))
-	}
-
-	// Test delete user
-	Users.DeleteUser(id.NewIdFromUInt(2, id.User, t))
-
-	_, ok := Users.GetUser(id.NewIdFromUInt(2, id.User, t))
-	if ok {
-		t.Errorf("User %v has not been deleted succesfully!", usr.Username)
-	}
-}
-
-// Doesn't actually do any testing, but can print the registration codes for
-// the first several users
-func TestPrintRegCodes(t *testing.T) {
-	for i := 1; i <= NumDemoUsers; i++ {
-		currentID := id.NewIdFromUInt(uint64(i), id.User, t)
-		t.Logf("%v:\t%v", i, RegistrationCode(currentID))
-	}
-}
-
-// InitGroup sets up the cryptographic constants for cMix
-func InitGroup() *cyclic.Group {
-
-	base := 16
-
-	pString := "9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48" +
-		"C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44F" +
-		"FE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5" +
-		"B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE2" +
-		"35567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41" +
-		"F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE" +
-		"92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA15" +
-		"3E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B"
-
-	gString := "5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613" +
-		"D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C4" +
-		"6A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472" +
-		"085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5" +
-		"AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA" +
-		"3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71" +
-		"BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0" +
-		"DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7"
-
-	p := large.NewIntFromString(pString, base)
-	g := large.NewIntFromString(gString, base)
-
-	grp := cyclic.NewGroup(p, g)
-
-	return grp
-}
diff --git a/version_vars.go.bak b/version_vars.go.bak
deleted file mode 100644
index 069542095c787bc95ef023a7b59798f4df78e9b9..0000000000000000000000000000000000000000
--- a/version_vars.go.bak
+++ /dev/null
@@ -1,33 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-// This file was generated by robots at
-// 2020-09-17 15:15:34.009207648 -0700 PDT m=+0.006867337
-package cmd
-
-const GITVERSION = `4edecf1 Update minor version number`
-const SEMVER = "1.5.0"
-const DEPENDENCIES = `module gitlab.com/elixxir/client
-
-go 1.13
-
-require (
-	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
-	github.com/golang/protobuf v1.4.2
-	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
-	github.com/pelletier/go-toml v1.6.0 // indirect
-	github.com/pkg/errors v0.9.1
-	github.com/smartystreets/assertions v1.0.1 // indirect
-	github.com/spf13/afero v1.2.2 // indirect
-	github.com/spf13/cast v1.3.1 // indirect
-	github.com/spf13/cobra v1.0.0
-	github.com/spf13/jwalterweatherman v1.1.0
-	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.6.2
-	gitlab.com/elixxir/comms v0.0.0-20200917221445-8a509560122a
-	gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a
-	gitlab.com/elixxir/ekv v0.0.0-20200729182028-159355ea5842
-	gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6
-	gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820
-	golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
-	gopkg.in/ini.v1 v1.52.0 // indirect
-)
-`