diff --git a/bindings/ud.go b/bindings/ud.go
index 643066a2ad2417ab444698c63b717101454ea53c..a4c3cb5babca0236b546e5cb10fc74b10a98f0f3 100644
--- a/bindings/ud.go
+++ b/bindings/ud.go
@@ -10,6 +10,7 @@ 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"
@@ -19,7 +20,7 @@ import (
 //This package wraps the user discovery system
 
 // User Discovery object
-type UserDiscovery struct{
+type UserDiscovery struct {
 	ud *ud.Manager
 }
 
@@ -30,13 +31,13 @@ type UserDiscovery struct{
 // 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)
+func NewUserDiscovery(client *Client) (*UserDiscovery, error) {
+	m, err := ud.NewManager(&client.api, &single.Manager{})
 
-	if err!=nil{
+	if err != nil {
 		return nil, err
-	}else{
-		return &UserDiscovery{ud:m}, nil
+	} else {
+		return &UserDiscovery{ud: m}, nil
 	}
 }
 
@@ -45,7 +46,7 @@ func NewUserDiscovery(client *Client)(*UserDiscovery, error){
 // 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{
+func (ud *UserDiscovery) Register(username string) error {
 	return ud.ud.Register(username)
 }
 
@@ -57,10 +58,10 @@ func (ud *UserDiscovery)Register(username string)error{
 // 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){
+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 " +
+	if err != nil {
+		return "", errors.WithMessage(err, "Failed to add due to "+
 			"malformed fact")
 	}
 
@@ -69,16 +70,16 @@ func (ud *UserDiscovery)AddFact(fStr string)(string, 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 (ud *UserDiscovery)ConfirmFact(confirmationID, code string)error{
+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{
+func (ud *UserDiscovery) RemoveFact(fStr string) error {
 	f, err := fact.UnstringifyFact(fStr)
-	if err !=nil{
-		return errors.WithMessage(err, "Failed to remove due to " +
+	if err != nil {
+		return errors.WithMessage(err, "Failed to remove due to "+
 			"malformed fact")
 	}
 	return ud.ud.RemoveFact(f)
@@ -96,20 +97,20 @@ type SearchCallback interface {
 // 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{
+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 " +
+	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){
+	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{
+		if err == nil {
+			contactList = &ContactList{list: cl}
+		} else {
 			errStr = err.Error()
 		}
 		callback.Callback(contactList, errStr)
@@ -128,23 +129,23 @@ type SingleSearchCallback interface {
 // 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{
+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 " +
+	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 contact *Contact
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	cb := func(cl []contact.Contact, err error) {
+		var c *Contact
 		var errStr string
-		if err==nil{
-			contact = &Contact{c:&cl[0]}
-		}else{
+		if err == nil {
+			c = &Contact{c: &cl[0]}
+		} else {
 			errStr = err.Error()
 		}
-		callback.Callback(contact, errStr)
+		callback.Callback(c, errStr)
 	}
 	return ud.ud.Search([]fact.Fact{fObj}, cb, timeout)
 }
@@ -158,27 +159,27 @@ type LookupCallback interface {
 // 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 {
+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 " +
+	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 contact *Contact
+	timeout := time.Duration(timeoutMS) * time.Millisecond
+	cb := func(cl contact.Contact, err error) {
+		var c *Contact
 		var errStr string
-		if err==nil{
-			contact = &Contact{c:&cl}
-		}else{
+		if err == nil {
+			c = &Contact{c: &cl}
+		} else {
 			errStr = err.Error()
 		}
-		callback.Callback(contact, errStr)
+		callback.Callback(c, errStr)
 	}
 
 	return ud.ud.Lookup(uid, cb, timeout)
 
-}
\ No newline at end of file
+}
diff --git a/cmd/single.go b/cmd/single.go
index a7c8fafcc17af98cd355bdbbc2de807fd2f48432..92a3027a8501084752ff2f5acd8f1a298782a422 100644
--- a/cmd/single.go
+++ b/cmd/single.go
@@ -128,7 +128,7 @@ func init() {
 }
 
 // sendSingleUse sends a single use message.
-func sendSingleUse(m *single.Manager, partner *contact.Contact, payload []byte,
+func sendSingleUse(m *single.Manager, partner contact.Contact, payload []byte,
 	maxMessages uint8, timeout time.Duration) {
 	// Construct callback
 	callbackChan := make(chan struct {
@@ -238,7 +238,7 @@ func makeResponsePayloadPart(m *single.Manager, payload []byte) []byte {
 
 // 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 {
+func readSingleUseContact(key string) contact.Contact {
 	// Get path
 	filePath := viper.GetString(key)
 	if filePath == "" {
@@ -258,5 +258,5 @@ func readSingleUseContact(key string) *contact.Contact {
 		jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err)
 	}
 
-	return &c
+	return c
 }
diff --git a/cmd/ud.go b/cmd/ud.go
index c267a68edd50761d936430bee678bf8a0e6dab6a..3c890c2e39c300901df26e164d448b219c41299c 100644
--- a/cmd/ud.go
+++ b/cmd/ud.go
@@ -15,47 +15,47 @@ import (
 	"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 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.
+// 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 & search users using the xxnet user discovery " +
-		"service"),
-	Args: cobra.NoArgs,
+	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()
+		swBoard := client.GetSwitchboard()
 		recvCh := make(chan message.Receive, 10000)
-		listenerID := swboard.RegisterChannel("DefaultCLIReceiver",
+		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.
+		// 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(
-				requestor contact.Contact, message string) {
-				jww.INFO.Printf("Got Request: %s", requestor.ID)
-				err := client.ConfirmAuthenticatedChannel(
-					requestor)
+				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)
 				}
@@ -72,17 +72,21 @@ var udCmd = &cobra.Command{
 		client.GetHealth().AddChannel(connected)
 		waitUntilConnected(connected)
 
-		userDiscoveryMgr, err := ud.NewManager(client)
+		// 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("%+v", err)
+			jww.FATAL.Panicf("Failed to create new UD manager: %+v", err)
 		}
-		userDiscoveryMgr.StartProcesses()
 
 		userToRegister := viper.GetString("register")
 		if userToRegister != "" {
 			err = userDiscoveryMgr.Register(userToRegister)
 			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+				jww.FATAL.Panicf("Failed to register user %s: %+v", userToRegister, err)
 			}
 		}
 
@@ -91,15 +95,16 @@ var udCmd = &cobra.Command{
 		if phone != "" {
 			f, err := fact.NewFact(fact.Phone, phone)
 			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+				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("%+v", err)
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
 			}
 			newFacts = append(newFacts, f)
 		}
@@ -107,7 +112,7 @@ var udCmd = &cobra.Command{
 		for i := 0; i < len(newFacts); i++ {
 			r, err := userDiscoveryMgr.SendRegisterFact(newFacts[i])
 			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+				jww.FATAL.Panicf("Failed to send register fact: %+v", err)
 			}
 			// TODO Store the code?
 			jww.INFO.Printf("Fact Add Response: %+v", r)
@@ -115,9 +120,8 @@ var udCmd = &cobra.Command{
 
 		confirmID := viper.GetString("confirm")
 		if confirmID != "" {
-			// TODO Lookup code
-			err = userDiscoveryMgr.SendConfirmFact(confirmID,
-				confirmID)
+			// TODO: Lookup code
+			err = userDiscoveryMgr.SendConfirmFact(confirmID, confirmID)
 			if err != nil {
 				jww.FATAL.Panicf("%+v", err)
 			}
@@ -127,53 +131,56 @@ var udCmd = &cobra.Command{
 		if lookupIDStr != "" {
 			lookupID, ok := parseRecipient(lookupIDStr)
 			if !ok {
-				jww.FATAL.Panicf("Could not parse: %s",
-					lookupIDStr)
+				jww.FATAL.Panicf("Could not parse recipient: %s", lookupIDStr)
 			}
-			userDiscoveryMgr.Lookup(lookupID,
+			err = userDiscoveryMgr.Lookup(lookupID,
 				func(newContact contact.Contact, err error) {
 					if err != nil {
 						jww.FATAL.Panicf("%+v", err)
 					}
 					cBytes := newContact.Marshal()
-					if err != nil {
-						jww.FATAL.Panicf("%+v", err)
-					}
 					fmt.Printf(string(cBytes))
-				},
-				time.Duration(90*time.Second))
+				}, 90*time.Second)
+
+			if err != nil {
+				jww.WARN.Printf("Failed UD lookup: %+v", err)
+			}
+
 			time.Sleep(91 * time.Second)
 		}
 
-		usernameSrchStr := viper.GetString("searchusername")
-		emailSrchStr := viper.GetString("searchemail")
-		phoneSrchStr := viper.GetString("searchphone")
+		usernameSearchStr := viper.GetString("searchusername")
+		emailSearchStr := viper.GetString("searchemail")
+		phoneSearchStr := viper.GetString("searchphone")
 
 		var facts fact.FactList
-		if usernameSrchStr != "" {
-			f, err := fact.NewFact(fact.Username, usernameSrchStr)
+		if usernameSearchStr != "" {
+			f, err := fact.NewFact(fact.Username, usernameSearchStr)
 			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
 			}
 			facts = append(facts, f)
 		}
-		if emailSrchStr != "" {
-			f, err := fact.NewFact(fact.Email, emailSrchStr)
+		if emailSearchStr != "" {
+			f, err := fact.NewFact(fact.Email, emailSearchStr)
 			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
 			}
 			facts = append(facts, f)
 		}
-		if phoneSrchStr != "" {
-			f, err := fact.NewFact(fact.Phone, phoneSrchStr)
+		if phoneSearchStr != "" {
+			f, err := fact.NewFact(fact.Phone, phoneSearchStr)
 			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
+				jww.FATAL.Panicf("Failed to create new fact: %+v", err)
 			}
 			facts = append(facts, f)
 		}
 
 		if len(facts) == 0 {
-			client.StopNetworkFollower(10 * time.Second)
+			err = client.StopNetworkFollower(10 * time.Second)
+			if err != nil {
+				jww.WARN.Print(err)
+			}
 			return
 		}
 
@@ -184,9 +191,6 @@ var udCmd = &cobra.Command{
 				}
 				for i := 0; i < len(contacts); i++ {
 					cBytes := contacts[i].Marshal()
-					if err != nil {
-						jww.FATAL.Panicf("%+v", err)
-					}
 					jww.INFO.Printf("Size Printed: %d", len(cBytes))
 					fmt.Printf("%s", cBytes)
 				}
@@ -195,42 +199,45 @@ var udCmd = &cobra.Command{
 			jww.FATAL.Panicf("%+v", err)
 		}
 		time.Sleep(91 * time.Second)
-		client.StopNetworkFollower(90 * 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().StringP("addphone", "", "",
+		"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"))
+	_ = 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().StringP("confirm", "", "",
-		"Confirm fact with confirmation id")
-	viper.BindPFlag("confirm", udCmd.Flags().Lookup("confirm"))
+	_ = 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().StringP("searchusername", "", "",
-		"Search for users with this username")
-	viper.BindPFlag("searchusername",
-		udCmd.Flags().Lookup("searchusername"))
-	udCmd.Flags().StringP("searchemail", "", "",
-		"Search for users with this email address")
-	viper.BindPFlag("searchemail",
-		udCmd.Flags().Lookup("searchemail"))
-	udCmd.Flags().StringP("searchphone", "", "",
-		"Search for users with this email address")
-	viper.BindPFlag("searchphone",
-		udCmd.Flags().Lookup("searchphone"))
+		"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)
 }
diff --git a/go.mod b/go.mod
index 0792504a91217fe7d064204d8894e114382df4a5..27971d0f1c7c812a4106d9d8c7692f2b1bf96cd3 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
 	gitlab.com/elixxir/primitives v0.0.3-0.20210216174458-2a23825c1eb1
 	gitlab.com/xx_network/comms v0.0.4-0.20210216174438-0790d1f1f225
 	gitlab.com/xx_network/crypto v0.0.5-0.20210216174356-e81e1ddf8fb7
-	gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f
+	gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36
 	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
diff --git a/go.sum b/go.sum
index 0d535ad56dce99718be355c34655e8d41088e32e..287f8dadf43fc313b4686ec4aacb8fe54c36310c 100644
--- a/go.sum
+++ b/go.sum
@@ -299,6 +299,10 @@ gitlab.com/xx_network/primitives v0.0.4-0.20210212180522-50ec526a6c12 h1:dOQS9tz
 gitlab.com/xx_network/primitives v0.0.4-0.20210212180522-50ec526a6c12/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
 gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f h1:0wFEYIHuPkWJuDkbDXNrwC5yGwkd7Mugt2BwcTqQbFY=
 gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210219220414-99c389307a84 h1:dvc/2RMlgffJHy2mJd+GK9jhWLni42o5ChFPZ2oYgkw=
+gitlab.com/xx_network/primitives v0.0.4-0.20210219220414-99c389307a84/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36 h1:41qeW7XB9Rllsi6fe37+eaQCLjTGchpvcJqwEvZxeXE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36/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=
diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go
index 09f890b3b834f0de3b36addd1f94019ae8e05d19..102b4b7d53205670e605eaff99402081b4651365 100644
--- a/interfaces/contact/contact.go
+++ b/interfaces/contact/contact.go
@@ -150,3 +150,11 @@ func Unmarshal(b []byte) (Contact, error) {
 
 	return c, nil
 }
+
+// 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
index b87f983ca8d055de6da893d23f9d8ab8382c01a8..871b2f9bf863b071dbeeed52b862bbd1ca71954c 100644
--- a/interfaces/contact/contact_test.go
+++ b/interfaces/contact/contact_test.go
@@ -174,7 +174,42 @@ func TestContact_GetFingerprint_Consistency(t *testing.T) {
 		}
 
 	}
+}
+
+// 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: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongUsername", T: fact.Username},
+			{Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongEmail@elixxir.io", T: fact.Email},
+			{Fact: "6502530000US", T: fact.Phone},
+		},
+	}
+	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 {
diff --git a/single/callbackMap.go b/single/callbackMap.go
index be7b03380285b6d298f7b3344bfff08ce16228b0..39bd3cc166d96e8c3a712a39726b0c5d1cdf820a 100644
--- a/single/callbackMap.go
+++ b/single/callbackMap.go
@@ -13,7 +13,7 @@ import (
 	"sync"
 )
 
-type receiveComm func(payload []byte, c Contact)
+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
@@ -22,21 +22,21 @@ type receiveComm func(payload []byte, c Contact)
 // 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
+	callbacks map[singleUse.TagFP]ReceiveComm
 	sync.RWMutex
 }
 
 // newCallbackMap initialises a new map.
 func newCallbackMap() *callbackMap {
 	return &callbackMap{
-		callbacks: map[singleUse.TagFP]receiveComm{},
+		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) {
+func (cbm *callbackMap) registerCallback(tag string, callback ReceiveComm) {
 	cbm.Lock()
 	defer cbm.Unlock()
 
@@ -46,7 +46,7 @@ func (cbm *callbackMap) registerCallback(tag string, callback receiveComm) {
 
 // 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) {
+func (cbm *callbackMap) getCallback(tagFP singleUse.TagFP) (ReceiveComm, error) {
 	cbm.RLock()
 	defer cbm.RUnlock()
 
diff --git a/single/callbackMap_test.go b/single/callbackMap_test.go
index 59ccb2dde618ebf9256cb82dd27aebb5d4d79903..5af02c8cac5b5b64f372a391ecb031b515f52298 100644
--- a/single/callbackMap_test.go
+++ b/single/callbackMap_test.go
@@ -18,7 +18,7 @@ func Test_callbackMap_registerCallback(t *testing.T) {
 	callbackChan := make(chan int)
 	testCallbacks := []struct {
 		tag string
-		cb  receiveComm
+		cb  ReceiveComm
 	}{
 		{"tag1", func([]byte, Contact) { callbackChan <- 0 }},
 		{"tag2", func([]byte, Contact) { callbackChan <- 1 }},
@@ -45,7 +45,7 @@ func Test_callbackMap_getCallback(t *testing.T) {
 	callbackChan := make(chan int)
 	testCallbacks := []struct {
 		tagFP singleUse.TagFP
-		cb    receiveComm
+		cb    ReceiveComm
 	}{
 		{singleUse.UnmarshalTagFP([]byte("tag1")), func([]byte, Contact) { callbackChan <- 0 }},
 		{singleUse.UnmarshalTagFP([]byte("tag2")), func([]byte, Contact) { callbackChan <- 1 }},
diff --git a/single/contact.go b/single/contact.go
index 25d207ddebb3fe36faffb03032a639c26b9ff271..bf0599899609261f38475e0ee5eed245898709ae 100644
--- a/single/contact.go
+++ b/single/contact.go
@@ -29,9 +29,9 @@ func NewContact(partner *id.ID, partnerPubKey, dhKey *cyclic.Int,
 	tagFP singleUse.TagFP, maxParts uint8) Contact {
 	used := int32(0)
 	return Contact{
-		partner:       partner.DeepCopy(),
-		partnerPubKey: partnerPubKey.DeepCopy(),
-		dhKey:         dhKey.DeepCopy(),
+		partner:       partner,
+		partnerPubKey: partnerPubKey,
+		dhKey:         dhKey,
 		tagFP:         tagFP,
 		maxParts:      maxParts,
 		used:          &used,
diff --git a/single/manager.go b/single/manager.go
index cbf21d9524799129d96139c727a3371b54592f16..78481829180017f242ca45e43b2bacc4175cf4dd 100644
--- a/single/manager.go
+++ b/single/manager.go
@@ -91,7 +91,7 @@ func (m *Manager) StartProcesses() stoppable.Stoppable {
 }
 
 // RegisterCallback registers a callback for received messages.
-func (m *Manager) RegisterCallback(tag string, callback receiveComm) {
+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
index d2dcfa4beae708c435532589f5dc42497b682f77..33427010d53d967a2a2dd52fb1971b661974e2d9 100644
--- a/single/manager_test.go
+++ b/single/manager_test.go
@@ -57,7 +57,7 @@ func Test_newManager(t *testing.T) {
 // Happy path.
 func TestManager_StartProcesses(t *testing.T) {
 	m := newTestManager(0, false, t)
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
@@ -143,7 +143,7 @@ func TestManager_StartProcesses(t *testing.T) {
 // Happy path: tests that the stoppable stops both routines.
 func TestManager_StartProcesses_Stop(t *testing.T) {
 	m := newTestManager(0, false, t)
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
@@ -225,7 +225,7 @@ type receiveCommData struct {
 	c       Contact
 }
 
-func createReceiveComm() (receiveComm, chan receiveCommData) {
+func createReceiveComm() (ReceiveComm, chan receiveCommData) {
 	callbackChan := make(chan receiveCommData)
 
 	callback := func(payload []byte, c Contact) {
diff --git a/single/reception_test.go b/single/reception_test.go
index 5230f364533d183a8007ee465b47a9ef07cca1cd..6989406621f2300e2f78c057defaf55d2e5858e1 100644
--- a/single/reception_test.go
+++ b/single/reception_test.go
@@ -17,7 +17,7 @@ func TestManager_receiveTransmissionHandler(t *testing.T) {
 	m := newTestManager(0, false, t)
 	rawMessages := make(chan message.Receive, rawMessageBuffSize)
 	quitChan := make(chan struct{})
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
@@ -82,7 +82,7 @@ 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{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(42),
 	}
@@ -119,7 +119,7 @@ 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{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
@@ -158,7 +158,7 @@ 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{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
@@ -181,7 +181,7 @@ func TestManager_receiveTransmissionHandler_TagFpError(t *testing.T) {
 // Happy path.
 func TestManager_processTransmission(t *testing.T) {
 	m := newTestManager(0, false, t)
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("partnerID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
@@ -235,7 +235,7 @@ func TestManager_processTransmission_TransmitMessageUnmarshalError(t *testing.T)
 // Error path: MAC fails to verify.
 func TestManager_processTransmission_MacVerifyError(t *testing.T) {
 	m := newTestManager(0, false, t)
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("partnerID", id.User, t),
 		DhPubKey: m.store.E2e().GetDHPublicKey(),
 	}
diff --git a/single/singleUseMap.go b/single/singleUseMap.go
index 48c3f096f864d5d94e88cf1d7e4cd34b36811f07..01e1cca5b6b5472dc75063cf79f20b25e3cb65da 100644
--- a/single/singleUseMap.go
+++ b/single/singleUseMap.go
@@ -23,7 +23,7 @@ type pending struct {
 	sync.RWMutex
 }
 
-type replyComm func(payload []byte, err error)
+type ReplyComm func(payload []byte, err error)
 
 // state contains the information and state of each single-use message that is
 // being transmitted.
@@ -31,7 +31,7 @@ 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
+	callback ReplyComm       // Returns the error status of the communication
 	quitChan chan struct{}   // Sending on channel kills the timeout handler
 }
 
@@ -44,7 +44,7 @@ func newPending() *pending {
 
 // newState generates a new state object with the fingerprint map and collator
 // initialised.
-func newState(dhKey *cyclic.Int, messageCount uint8, callback replyComm) *state {
+func newState(dhKey *cyclic.Int, messageCount uint8, callback ReplyComm) *state {
 	return &state{
 		dhKey:    dhKey,
 		fpMap:    newFingerprintMap(dhKey, uint64(messageCount)),
@@ -57,7 +57,7 @@ func newState(dhKey *cyclic.Int, messageCount uint8, callback replyComm) *state
 // 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) {
+	callback ReplyComm, timeout time.Duration) (chan struct{}, *int32, error) {
 	p.Lock()
 
 	// Check if the state already exists
@@ -84,7 +84,7 @@ func (p *pending) addState(rid *id.ID, dhKey *cyclic.Int, maxMsgs uint8,
 
 // timeoutHandler waits for the signal to complete or times out and deletes the
 // state.
-func (p *pending) timeoutHandler(rid *id.ID, callback replyComm,
+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)
diff --git a/single/transmission.go b/single/transmission.go
index 2e5493d34ee19149ba04cb65c6ad4d5a3203ac54..13abe66dbd753c5f1b2e6a3036c0c6efa8f0c9b4 100644
--- a/single/transmission.go
+++ b/single/transmission.go
@@ -43,8 +43,8 @@ func (m *Manager) GetMaxTransmissionPayloadSize() int {
 }
 
 // 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 {
+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()
@@ -60,9 +60,8 @@ type roundEvents interface {
 }
 
 // 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 {
+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
@@ -174,7 +173,7 @@ func (m *Manager) transmitSingleUse(partner *contact2.Contact, payload []byte,
 
 // makeTransmitCmixMessage generates a CMIX message containing the transmission message,
 // which contains the encrypted payload.
-func (m *Manager) makeTransmitCmixMessage(partner *contact2.Contact,
+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) {
diff --git a/single/transmission_test.go b/single/transmission_test.go
index 82fc55376d1da759c6ff7c1b98f63b9da70ae4eb..fe6df60ba188ef5abe88e06ab4434c0d289618b5 100644
--- a/single/transmission_test.go
+++ b/single/transmission_test.go
@@ -38,7 +38,7 @@ func TestManager_GetMaxTransmissionPayloadSize(t *testing.T) {
 func TestManager_transmitSingleUse(t *testing.T) {
 	m := newTestManager(0, false, t)
 	prng := rand.New(rand.NewSource(42))
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("Contact ID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
 	}
@@ -84,7 +84,7 @@ func TestManager_transmitSingleUse(t *testing.T) {
 // 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{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("Contact ID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
 	}
@@ -117,7 +117,7 @@ func TestManager_transmitSingleUse_QuitChanError(t *testing.T) {
 func TestManager_transmitSingleUse_AddIdentityError(t *testing.T) {
 	timeout := 15 * time.Millisecond
 	m := newTestManager(timeout, false, t)
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("Contact ID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
 	}
@@ -149,7 +149,7 @@ func TestManager_transmitSingleUse_AddIdentityError(t *testing.T) {
 // Error path: SendCMIX fails to send message.
 func TestManager_transmitSingleUse_SendCMIXError(t *testing.T) {
 	m := newTestManager(0, true, t)
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("Contact ID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
 	}
@@ -181,7 +181,7 @@ func TestManager_transmitSingleUse_MakeTransmitCmixMessageError(t *testing.T) {
 	prng := rand.New(rand.NewSource(42))
 	payload := make([]byte, m.store.Cmix().GetGroup().GetP().ByteLen())
 
-	err := m.transmitSingleUse(nil, payload, "", 0, prng, nil, 0, nil)
+	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.")
@@ -191,7 +191,7 @@ func TestManager_transmitSingleUse_MakeTransmitCmixMessageError(t *testing.T) {
 // 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{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("Contact ID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
 	}
@@ -222,7 +222,7 @@ func TestManager_transmitSingleUse_AddStateError(t *testing.T) {
 func TestManager_transmitSingleUse_RoundTimeoutError(t *testing.T) {
 	m := newTestManager(0, false, t)
 	prng := rand.New(rand.NewSource(42))
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("Contact ID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(5),
 	}
@@ -254,7 +254,7 @@ func TestManager_transmitSingleUse_RoundTimeoutError(t *testing.T) {
 func TestManager_makeTransmitCmixMessage(t *testing.T) {
 	m := newTestManager(0, false, t)
 	prng := rand.New(rand.NewSource(42))
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(42),
 	}
@@ -324,7 +324,7 @@ func TestManager_makeTransmitCmixMessage_PayloadTooLargeError(t *testing.T) {
 	payload := make([]byte, 1000)
 	rand.New(rand.NewSource(42)).Read(payload)
 
-	_, _, _, _, err := m.makeTransmitCmixMessage(nil, payload, "", 8, 32,
+	_, _, _, _, err := m.makeTransmitCmixMessage(contact2.Contact{}, payload, "", 8, 32,
 		30*time.Second, time.Now(), prng)
 
 	if !check(err, "too long for message payload capacity") {
@@ -337,7 +337,7 @@ func TestManager_makeTransmitCmixMessage_PayloadTooLargeError(t *testing.T) {
 func TestManager_makeTransmitCmixMessage_KeyGenerationError(t *testing.T) {
 	m := newTestManager(0, false, t)
 	prng := strings.NewReader("a")
-	partner := &contact2.Contact{
+	partner := contact2.Contact{
 		ID:       id.NewIdFromString("recipientID", id.User, t),
 		DhPubKey: m.store.E2e().GetGroup().NewInt(42),
 	}
diff --git a/ud/commID.go b/ud/commID.go
deleted file mode 100644
index 018db5dc5f2a8023df465fac68610bd9ca87effe..0000000000000000000000000000000000000000
--- a/ud/commID.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package ud
-
-import (
-	"encoding/binary"
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/client/storage/versioned"
-	"time"
-)
-
-const commIDKey = "commIDKey"
-const commIDVersion = 0
-
-// getCommID returns the ID for the next comm. IDs are generated sequentially.
-func (m *Manager) getCommID() uint64 {
-
-	m.commIDLock.Lock()
-	defer m.commIDLock.Unlock()
-	returnedID := m.commID
-
-	// Increment ID for next get
-	m.commID++
-
-	// Save ID storage
-	data := make([]byte, 8)
-	binary.BigEndian.PutUint64(data, m.commID)
-
-	obj := &versioned.Object{
-		Version:   commIDVersion,
-		Timestamp: time.Now(),
-		Data:      data,
-	}
-
-	if err := m.storage.Set(commIDKey, obj); err != nil {
-		jww.FATAL.Panicf("Failed to store the next commID: %+v", err)
-	}
-
-	return returnedID
-}
-
-// loadCommID retrieves the next comm ID from storage.
-func (m *Manager) loadCommID() {
-	m.commIDLock.Lock()
-	defer m.commIDLock.Unlock()
-
-	obj, err := m.storage.Get(commIDKey)
-	if err != nil {
-		jww.WARN.Printf("Failed to get the commID; restarting at zero: %s", err)
-		return
-	}
-
-	m.commID = binary.BigEndian.Uint64(obj.Data)
-}
diff --git a/ud/lookup.go b/ud/lookup.go
index 3d57fcda5023bb6f6d9dfa2efa291a0cf7cfe10f..d7ce0fa69f2addc3bb09419f98ec60b3a6cc2985 100644
--- a/ud/lookup.go
+++ b/ud/lookup.go
@@ -6,161 +6,72 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/interfaces/contact"
-	"gitlab.com/elixxir/client/interfaces/message"
-	"gitlab.com/elixxir/client/interfaces/params"
-	"gitlab.com/elixxir/comms/network/dataStructures"
-	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
-type lookupCallback func(contact.Contact, error)
-
-func (m *Manager) lookupProcess(c chan message.Receive, quitCh <-chan struct{}) {
-	for true {
-		select {
-		case <-quitCh:
-			return
-		case response := <-c:
-
-			// Unmarshal the message
-			lookupResponse := &LookupResponse{}
-			if err := proto.Unmarshal(response.Payload, lookupResponse); err != nil {
-				jww.WARN.Printf("Dropped a lookup response from user "+
-					"discovery due to failed unmarshal: %s", err)
-			}
+// LookupTag specifies which callback to trigger when UD receives a lookup
+// request.
+const LookupTag = "xxNetwork_UdLookup"
 
-			// Get the appropriate channel from the lookup
-			m.inProgressLookupMux.RLock()
-			ch, ok := m.inProgressLookup[lookupResponse.CommID]
-			m.inProgressLookupMux.RUnlock()
-			if !ok {
-				jww.WARN.Printf("Dropped a lookup response from user "+
-					"discovery due to unknown comm ID: %d",
-					lookupResponse.CommID)
-			}
+// TODO: reconsider where this comes from
+const maxLookupMessages = 20
 
-			// Send the response on the correct channel
-			// Drop if the send cannot be completed
-			select {
-			case ch <- lookupResponse:
-			default:
-				jww.WARN.Printf("Dropped a lookup response from user "+
-					"discovery due to failure to transmit to handling thread: "+
-					"commID: %d", lookupResponse.CommID)
-			}
-		}
-	}
-}
+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")
-	}
-
-	// Get the ID of this comm so it can be connected to its response
-	commID := m.getCommID()
-
-	// Build the request
-	request := &LookupSend{
-		UserID: uid.Marshal(),
-		CommID: commID,
+	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 request")
+		return errors.WithMessage(err, "Failed to form outgoing lookup request.")
 	}
 
-	msg := message.Send{
-		Recipient:   m.udID,
-		Payload:     requestMarshaled,
-		MessageType: message.UdLookup,
+	f := func(payload []byte, err error) {
+		m.lookupResponseProcess(uid, callback, payload, err)
 	}
 
-	// Register the request in the response map so it can be processed on return
-	responseChan := make(chan *LookupResponse, 1)
-	m.inProgressLookupMux.Lock()
-	m.inProgressLookup[commID] = responseChan
-	m.inProgressLookupMux.Unlock()
-
-	// Send the request
-	rounds, err := m.net.SendUnsafe(msg, params.GetDefaultUnsafe())
+	err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, LookupTag,
+		maxLookupMessages, f, timeout)
 	if err != nil {
-		return errors.WithMessage(err, "Failed to send the lookup request")
+		return errors.WithMessage(err, "Failed to transmit lookup request.")
 	}
 
-	// Register the round event to capture if the round fails
-	roundFailChan := make(chan dataStructures.EventReturn, len(rounds))
+	return nil
+}
 
-	for _, round := range rounds {
-		// Subtract a millisecond to ensure this timeout will trigger before the
-		// one below
-		m.net.GetInstance().GetRoundEvents().AddRoundEventChan(round,
-			roundFailChan, timeout-1*time.Millisecond, states.FAILED,
-			states.COMPLETED)
+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
 	}
 
-	// Start the go routine which will trigger the callback
-	go func() {
-		timer := time.NewTimer(timeout)
-
-		var err error
-		var c contact.Contact
-
-		done := false
-		for !done {
-			select {
-			// Return an error if the round fails
-			case fail := <-roundFailChan:
-				if states.Round(fail.RoundInfo.State)==states.FAILED || fail.TimedOut{
-					fType := ""
-					if fail.TimedOut{
-						fType = "timeout"
-					}else{
-						fType = fmt.Sprintf("round failure: %v", fail.RoundInfo.ID)
-					}
-					err = errors.Errorf("One or more rounds (%v) failed to " +
-						"resolve due to: %s; search not delivered", rounds, fType)
-					done = true
-				}
-
-			// Return an error if the timeout is reached
-			case <-timer.C:
-				err = errors.New("Response from User Discovery did not come " +
-					"before timeout")
-				done = true
-
-			// Return the contact if one is returned
-			case response := <-responseChan:
-				if response.Error != "" {
-					err = errors.Errorf("User Discovery returned an error on "+
-						"lookup: %s", response.Error)
-				} else {
-					pubkey := m.grp.NewIntFromBytes(response.PubKey)
-					c = contact.Contact{
-						ID:             uid,
-						DhPubKey:       pubkey,
-						OwnershipProof: nil,
-						Facts:          nil,
-					}
-				}
-				done = true
-			}
-		}
-
-		// Delete the response channel from the map
-		m.inProgressLookupMux.Lock()
-		delete(m.inProgressLookup, commID)
-		m.inProgressLookupMux.Unlock()
+	// 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
+	}
 
-		// Call the callback last in case it is blocking
-		callback(c, err)
-	}()
+	fmt.Printf("pubKey: %+v\n", lookupResponse.PubKey)
+	c := contact.Contact{
+		ID:       uid,
+		DhPubKey: m.grp.NewIntFromBytes(lookupResponse.PubKey),
+	}
 
-	return nil
+	go callback(c, nil)
 }
diff --git a/ud/lookup_test.go b/ud/lookup_test.go
index b8545de0c39d7eec23d47f132a13a52a795ba83c..413b85f9d0443411977557937dfd74cee8833d63 100644
--- a/ud/lookup_test.go
+++ b/ud/lookup_test.go
@@ -2,43 +2,29 @@ package ud
 
 import (
 	"github.com/golang/protobuf/proto"
-	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/interfaces"
+	"github.com/pkg/errors"
 	"gitlab.com/elixxir/client/interfaces/contact"
-	"gitlab.com/elixxir/client/interfaces/message"
-	"gitlab.com/elixxir/client/interfaces/params"
+	"gitlab.com/elixxir/client/single"
 	"gitlab.com/elixxir/client/stoppable"
-	"gitlab.com/elixxir/client/storage"
-	"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/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/id/ephemeral"
-	"gitlab.com/xx_network/primitives/ndf"
 	"math/rand"
 	"reflect"
+	"strings"
 	"testing"
 	"time"
 )
 
 // Happy path.
 func TestManager_Lookup(t *testing.T) {
-	isReg := uint32(1)
-
 	// Set up manager
+	isReg := uint32(1)
 	m := &Manager{
-		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
-		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		storage:          storage.InitTestingSession(t),
-		udID:             &id.UDB,
-		inProgressLookup: map[uint64]chan *LookupResponse{},
-		net:              newTestNetworkManager(t),
-		registered:       &isReg,
+		grp:        cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
+		udContact:  contact.Contact{ID: &id.UDB},
+		single:     &mockSingleLookup{},
+		registered: &isReg,
 	}
 
 	// Generate callback function
@@ -52,17 +38,7 @@ func TestManager_Lookup(t *testing.T) {
 			err error
 		}{c: c, err: err}
 	}
-
-	// Trigger lookup response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		m.inProgressLookup[0] <- &LookupResponse{
-			PubKey: []byte{5},
-			Error:  "",
-		}
-	}()
-
-	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	uid := id.NewIdFromUInt(0x500000000000000, id.User, t)
 
 	// Run the lookup
 	err := m.Lookup(uid, callback, 10*time.Millisecond)
@@ -70,27 +46,6 @@ func TestManager_Lookup(t *testing.T) {
 		t.Errorf("Lookup() returned an error: %+v", err)
 	}
 
-	// Generate expected Send message
-	payload, err := proto.Marshal(&LookupSend{
-		UserID: uid.Marshal(),
-		CommID: m.commID - 1,
-	})
-	if err != nil {
-		t.Fatalf("Failed to marshal LookupSend: %+v", err)
-	}
-	expectedMsg := message.Send{
-		Recipient:   m.udID,
-		Payload:     payload,
-		MessageType: message.UdLookup,
-	}
-
-	// Verify the message is correct
-	if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).msg) {
-		t.Errorf("Failed to send correct message."+
-			"\n\texpected: %+v\n\treceived: %+v",
-			expectedMsg, m.net.(*testNetworkManager).msg)
-	}
-
 	// Verify the callback is called
 	select {
 	case cb := <-callbackChan:
@@ -109,27 +64,13 @@ func TestManager_Lookup(t *testing.T) {
 	case <-time.After(100 * time.Millisecond):
 		t.Error("Callback not called.")
 	}
-
-	if _, exists := m.inProgressLookup[m.commID-1]; exists {
-		t.Error("Failed to delete LookupResponse from inProgressLookup.")
-	}
 }
 
-// Error path: the callback returns an error.
-func TestManager_Lookup_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),
-		udID:             &id.UDB,
-		inProgressLookup: map[uint64]chan *LookupResponse{},
-		net:              newTestNetworkManager(t),
-		registered:       &isReg,
-	}
+// Happy path.
+func TestManager_lookupResponseProcess(t *testing.T) {
+	m := &Manager{grp: cyclic.NewGroup(large.NewInt(107), large.NewInt(2))}
 
-	// Generate callback function
+	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
 	callbackChan := make(chan struct {
 		c   contact.Contact
 		err error
@@ -140,59 +81,38 @@ func TestManager_Lookup_CallbackError(t *testing.T) {
 			err error
 		}{c: c, err: err}
 	}
+	pubKey := []byte{5}
+	expectedContact := contact.Contact{
+		ID:       uid,
+		DhPubKey: m.grp.NewIntFromBytes(pubKey),
+	}
 
-	// Trigger lookup response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		m.inProgressLookup[0] <- &LookupResponse{
-			PubKey: nil,
-			Error:  "Error",
-		}
-	}()
-
-	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
-
-	// Run the lookup
-	err := m.Lookup(uid, callback, 10*time.Millisecond)
+	// Generate expected Send message
+	payload, err := proto.Marshal(&LookupResponse{PubKey: pubKey})
 	if err != nil {
-		t.Errorf("Lookup() returned an error: %+v", err)
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
 	}
 
-	// Verify the callback is called
+	m.lookupResponseProcess(uid, callback, payload, nil)
+
 	select {
-	case cb := <-callbackChan:
-		if cb.err == nil {
-			t.Error("Callback did not return an expected error.")
+	case results := <-callbackChan:
+		if results.err != nil {
+			t.Errorf("Callback returned an error: %+v", results.err)
 		}
-
-		if !reflect.DeepEqual(contact.Contact{}, cb.c) {
-			t.Errorf("Failed to get expected Contact."+
-				"\n\texpected: %v\n\treceived: %v", contact.Contact{}, cb.c)
+		if !reflect.DeepEqual(expectedContact, results.c) {
+			t.Errorf("Callback returned unexpected Contact."+
+				"\nexpected: %+v\nreceived: %+v", expectedContact, results.c)
 		}
-	case <-time.After(100 * time.Millisecond):
-		t.Error("Callback not called.")
-	}
-
-	if _, exists := m.inProgressLookup[m.commID-1]; exists {
-		t.Error("Failed to delete LookupResponse from inProgressLookup.")
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
 	}
 }
 
-// Error path: the round event chan times out.
-func TestManager_Lookup_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),
-		udID:             &id.UDB,
-		inProgressLookup: map[uint64]chan *LookupResponse{},
-		net:              newTestNetworkManager(t),
-		registered:       &isReg,
-	}
+// 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))}
 
-	// Generate callback function
 	callbackChan := make(chan struct {
 		c   contact.Contact
 		err error
@@ -204,232 +124,80 @@ func TestManager_Lookup_EventChanTimeout(t *testing.T) {
 		}{c: c, err: err}
 	}
 
-	uid := id.NewIdFromUInt(rand.Uint64(), id.User, t)
+	testErr := errors.New("lookup failure")
 
-	// Run the lookup
-	err := m.Lookup(uid, callback, 10*time.Millisecond)
-	if err != nil {
-		t.Errorf("Lookup() returned an error: %+v", err)
-	}
+	m.lookupResponseProcess(nil, callback, []byte{}, testErr)
 
-	// Verify the callback is called
 	select {
-	case cb := <-callbackChan:
-		if cb.err == nil {
-			t.Error("Callback did not return an expected error.")
+	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)
 		}
-
-		if !reflect.DeepEqual(contact.Contact{}, cb.c) {
-			t.Errorf("Failed to get expected Contact."+
-				"\n\texpected: %v\n\treceived: %v", contact.Contact{}, cb.c)
-		}
-	case <-time.After(100 * time.Millisecond):
-		t.Error("Callback not called.")
-	}
-
-	if _, exists := m.inProgressLookup[m.commID-1]; exists {
-		t.Error("Failed to delete LookupResponse from inProgressLookup.")
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
 	}
 }
 
-// Happy path.
-func TestManager_lookupProcess(t *testing.T) {
-	isReg := uint32(1)
-	m := &Manager{
-		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
-		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		storage:          storage.InitTestingSession(t),
-		udID:             &id.UDB,
-		inProgressLookup: map[uint64]chan *LookupResponse{},
-		net:              newTestNetworkManager(t),
-		registered:       &isReg,
-	}
+// 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))}
 
-	c := make(chan message.Receive)
-	quitCh := make(chan struct{})
+	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
-	payload, err := proto.Marshal(&LookupSend{
-		UserID: id.NewIdFromUInt(rand.Uint64(), id.User, t).Marshal(),
-		CommID: m.commID,
-	})
+	testErr := "LookupResponse error occurred"
+	payload, err := proto.Marshal(&LookupResponse{Error: testErr})
 	if err != nil {
 		t.Fatalf("Failed to marshal LookupSend: %+v", err)
 	}
 
-	m.inProgressLookup[m.commID] = make(chan *LookupResponse, 1)
-
-	// Trigger response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		c <- message.Receive{
-			Payload:    payload,
-			Encryption: message.E2E,
-		}
-		time.Sleep(1 * time.Millisecond)
-		quitCh <- struct{}{}
-	}()
-
-	m.lookupProcess(c, quitCh)
+	m.lookupResponseProcess(uid, callback, payload, nil)
 
 	select {
-	case response := <-m.inProgressLookup[m.commID]:
-		expectedResponse := &LookupResponse{}
-		if err := proto.Unmarshal(payload, expectedResponse); err != nil {
-			t.Fatalf("Failed to unmarshal payload: %+v", err)
+	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)
 		}
-
-		if !reflect.DeepEqual(expectedResponse, response) {
-			t.Errorf("Recieved unexpected response."+
-				"\n\texpected: %+v\n\trecieved: %+v", expectedResponse, response)
-		}
-	case <-time.After(100 * time.Millisecond):
-		t.Error("Response not sent.")
-	}
-}
-
-// Error path: dropped lookup response due to incorrect message.Receive.
-func TestManager_lookupProcess_NoLookupResponse(t *testing.T) {
-	isReg := uint32(1)
-	m := &Manager{
-		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
-		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		storage:          storage.InitTestingSession(t),
-		udID:             &id.UDB,
-		inProgressLookup: map[uint64]chan *LookupResponse{},
-		net:              newTestNetworkManager(t),
-		registered:       &isReg,
+	case <-time.NewTimer(50 * time.Millisecond).C:
+		t.Error("Callback time out.")
 	}
-
-	c := make(chan message.Receive)
-	quitCh := make(chan struct{})
-
-	// Trigger response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		c <- message.Receive{}
-		time.Sleep(1 * time.Millisecond)
-		quitCh <- struct{}{}
-	}()
-
-	m.lookupProcess(c, quitCh)
-
-	select {
-	case response := <-m.inProgressLookup[m.commID]:
-		t.Errorf("Received unexpected response: %+v", response)
-	case <-time.After(10 * time.Millisecond):
-		return
-	}
-}
-
-// 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
+// mockSingleLookup is used to test the lookup function, which uses the single-
+// use manager. It adheres to the SingleInterface interface.
+type mockSingleLookup struct {
 }
 
-func (t *testNetworkManager) GetHealthTracker() interfaces.HealthTracker {
-	return nil
-}
+func (s *mockSingleLookup) TransmitSingleUse(_ contact.Contact, payload []byte,
+	_ string, _ uint8, callback single.ReplyComm, _ time.Duration) error {
 
-func (t *testNetworkManager) Follow() (stoppable.Stoppable, error) {
-	return nil, nil
-}
-
-func (t *testNetworkManager) CheckGarbledMessages() {}
-
-func newTestNetworkManager(i interface{}) interfaces.NetworkManager {
-	switch i.(type) {
-	case *testing.T, *testing.M, *testing.B:
-		break
-	default:
-		globals.Log.FATAL.Panicf("initTesting is restricted to testing only."+
-			"Got %T", i)
-	}
-
-	commsManager := connect.NewManagerTesting(i)
-	instanceComms := &connect.ProtoComms{
-		Manager: commsManager,
+	lookupMsg := &LookupSend{}
+	if err := proto.Unmarshal(payload, lookupMsg); err != nil {
+		return errors.Errorf("Failed to unmarshal LookupSend: %+v", err)
 	}
 
-	thisInstance, err := network.NewInstanceTesting(instanceComms, getNDF(), getNDF(), nil, nil, i)
+	lookupResponse := &LookupResponse{PubKey: lookupMsg.UserID[:1]}
+	msg, err := proto.Marshal(lookupResponse)
 	if err != nil {
-		globals.Log.FATAL.Panicf("Failed to create new test Instance: %v", err)
+		return errors.Errorf("Failed to marshal LookupResponse: %+v", err)
 	}
 
-	thisManager := &testNetworkManager{instance: thisInstance}
-
-	return thisManager
+	callback(msg, nil)
+	return nil
 }
 
-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 (s *mockSingleLookup) StartProcesses() stoppable.Stoppable {
+	return stoppable.NewSingle("")
 }
diff --git a/ud/manager.go b/ud/manager.go
index d0a3ba4a3209a93c1f0da8519ae24324bd6bcd6a..ad7519969b1c6c136c6c562565abcfa6038f7e22 100644
--- a/ud/manager.go
+++ b/ud/manager.go
@@ -5,7 +5,8 @@ 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/interfaces/contact"
+	"gitlab.com/elixxir/client/single"
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/comms/client"
@@ -14,11 +15,17 @@ import (
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/crypto/signature/rsa"
 	"gitlab.com/xx_network/primitives/id"
-	"sync"
+	"time"
 )
 
+type SingleInterface interface {
+	TransmitSingleUse(contact.Contact, []byte, string, uint8, single.ReplyComm,
+		time.Duration) error
+	StartProcesses() stoppable.Stoppable
+}
+
 type Manager struct {
-	//external
+	// External
 	client  *api.Client
 	comms   *client.Comms
 	rng     *fastRNG.StreamGenerator
@@ -26,104 +33,75 @@ type Manager struct {
 	storage *storage.Session
 	net     interfaces.NetworkManager
 
-	//loaded from external access
-	udID    *id.ID
-	privKey *rsa.PrivateKey
-	grp     *cyclic.Group
-
-	//internal maps
-	host                *connect.Host
-	inProgressLookup    map[uint64]chan *LookupResponse
-	inProgressLookupMux sync.RWMutex
-
-	inProgressSearch    map[uint64]chan *SearchResponse
-	inProgressSearchMux sync.RWMutex
+	// Loaded from external access
+	udContact contact.Contact
+	privKey   *rsa.PrivateKey
+	grp       *cyclic.Group
 
-	//State tracking
-	commID     uint64
-	commIDLock sync.Mutex
+	// Internal maps
+	host   *connect.Host
+	single SingleInterface
 
 	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) (*Manager, error) {
+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")
+		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(),
-		inProgressLookup: make(map[uint64]chan *LookupResponse),
-		inProgressSearch: make(map[uint64]chan *SearchResponse),
+		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
+	// check that user discovery is available in the ndf
 	def := m.net.GetInstance().GetPartialNdf().Get()
-	if m.udID, 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 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")
+		return nil, errors.New("NDF does not have User Discovery information, " +
+			"is there network access?: Cert not present.")
 	}
 
-	//create the user discovery host object
+	// 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()
-	if m.host, err = m.comms.AddHost(m.udID, def.UDB.Address, []byte(def.UDB.Cert),
-		hp); err != nil {
-		return nil, errors.WithMessage(err, "User Discovery host "+
-			"object could not be constructed")
+	m.host, err = m.comms.AddHost(m.udContact.ID, def.UDB.Address, []byte(def.UDB.Cert), hp)
+	if err != nil {
+		return nil, errors.WithMessage(err, "User Discovery host object could "+
+			"not be constructed.")
 	}
 
-	//get the commonly used data from storage
+	// Get the commonly used data from storage
 	m.privKey = m.storage.GetUser().ReceptionRSA
 
-	//load the last used commID
-	m.loadCommID()
-
-	//load if the client is registered
+	// Load if the client is registered
 	m.loadRegistered()
 
-	//store the pointer to the group locally for easy access
+	// Store the pointer to the group locally for easy access
 	m.grp = m.storage.E2e().GetGroup()
 
 	return m, nil
 }
-
-func (m *Manager) StartProcesses() {
-	m.client.AddService(m.startProcesses)
-}
-
-func (m *Manager) startProcesses() stoppable.Stoppable {
-
-	lookupStop := stoppable.NewSingle("UDLookup")
-	lookupChan := make(chan message.Receive, 100)
-	m.sw.RegisterChannel("UDLookupResponse", m.udID, message.UdLookupResponse, lookupChan)
-	go m.lookupProcess(lookupChan, lookupStop.Quit())
-
-	searchStop := stoppable.NewSingle("UDSearch")
-	searchChan := make(chan message.Receive, 100)
-	m.sw.RegisterChannel("UDSearchResponse", m.udID, message.UdSearchResponse, searchChan)
-	go m.searchProcess(searchChan, searchStop.Quit())
-
-	udMulti := stoppable.NewMulti("UD")
-	udMulti.Add(lookupStop)
-	udMulti.Add(searchStop)
-	return lookupStop
-}
diff --git a/ud/search.go b/ud/search.go
index b3a1cd190207a389bbede70911963458d356a06e..5367fe10c5d65b22814a799f6071584ffeca8e24 100644
--- a/ud/search.go
+++ b/ud/search.go
@@ -1,171 +1,86 @@
 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/elixxir/client/interfaces/message"
-	"gitlab.com/elixxir/client/interfaces/params"
-	"gitlab.com/elixxir/comms/network/dataStructures"
 	"gitlab.com/elixxir/crypto/factID"
 	"gitlab.com/elixxir/primitives/fact"
-	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
-type SearchCallback func([]contact.Contact, error)
-
-func (m *Manager) searchProcess(c chan message.Receive, quitCh <-chan struct{}) {
-	for true {
-		select {
-		case <-quitCh:
-			return
-		case response := <-c:
-			// Unmarshal the message
-			searchResponse := &SearchResponse{}
-			if err := proto.Unmarshal(response.Payload, searchResponse); err != nil {
-				jww.WARN.Printf("Dropped a search response from user "+
-					"discovery due to failed unmarshal: %s", err)
-			}
+// SearchTag specifies which callback to trigger when UD receives a search
+// request.
+const SearchTag = "xxNetwork_UdLookup"
 
-			// Get the appropriate channel from the lookup
-			m.inProgressSearchMux.RLock()
-			ch, ok := m.inProgressSearch[searchResponse.CommID]
-			m.inProgressSearchMux.RUnlock()
-			if !ok {
-				jww.WARN.Printf("Dropped a search response from user "+
-					"discovery due to unknown comm ID: %d",
-					searchResponse.CommID)
-			}
+// TODO: reconsider where this comes from
+const maxSearchMessages = 20
 
-			// Send the response on the correct channel
-			// Drop if the send cannot be completed
-			select {
-			case ch <- searchResponse:
-			default:
-				jww.WARN.Printf("Dropped a search response from user "+
-					"discovery due to failure to transmit to handling thread: "+
-					"commID: %d", searchResponse.CommID)
-			}
-		}
-	}
-}
+type searchCallback func([]contact.Contact, error)
 
-// 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 {
+// 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")
+		return errors.New("Failed to search: client is not registered.")
 	}
 
-	// Get the ID of this comm so it can be connected to its response
-	commID := m.getCommID()
-
 	factHashes, factMap := hashFactList(list)
 
-	// Build the request
-	request := &SearchSend{
-		Fact:   factHashes,
-		CommID: commID,
-	}
-
+	// 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")
+		return errors.WithMessage(err, "Failed to form outgoing search request.")
 	}
 
-	//cUID := m.client.GetUser().ID
-
-	msg := message.Send{
-		Recipient:   m.udID,
-		Payload:     requestMarshaled,
-		MessageType: message.UdSearch,
+	f := func(payload []byte, err error) {
+		m.searchResponseHandler(factMap, callback, payload, err)
 	}
 
-	// Register the request in the response map so it can be processed on return
-	responseChan := make(chan *SearchResponse)
-	m.inProgressSearchMux.Lock()
-	m.inProgressSearch[commID] = responseChan
-	m.inProgressSearchMux.Unlock()
-
-	// Send the request
-	rounds, err := m.net.SendUnsafe(msg, params.GetDefaultUnsafe())
+	err = m.single.TransmitSingleUse(m.udContact, requestMarshaled, SearchTag,
+		maxSearchMessages, f, timeout)
 	if err != nil {
-		return errors.WithMessage(err, "Failed to send the search request")
+		return errors.WithMessage(err, "Failed to transmit search request.")
 	}
 
-	// Register the round event to capture if the round fails
-	roundFailChan := make(chan dataStructures.EventReturn, len(rounds))
+	return nil
+}
 
-	for _, round := range rounds {
-		// Subtract a millisecond to ensure this timeout will trigger before the
-		// one below
-		m.net.GetInstance().GetRoundEvents().AddRoundEventChan(round,
-			roundFailChan, timeout-1*time.Millisecond, states.FAILED,
-			states.COMPLETED)
+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
 	}
 
-	// Start the go routine which will trigger the callback
-	go func() {
-		timer := time.NewTimer(timeout)
-
-		var err error
-		var c []contact.Contact
-
-		done := false
-		for !done {
-			select {
-			// Return an error if the round fails
-			case fail := <-roundFailChan:
-				if states.Round(fail.RoundInfo.State) == states.FAILED || fail.TimedOut {
-					fType := ""
-					if fail.TimedOut {
-						fType = "timeout"
-					} else {
-						fType = fmt.Sprintf("round failure: %v", fail.RoundInfo.ID)
-					}
-					err = errors.Errorf("One or more rounds (%v) failed to "+
-						"resolve due to: %s; search not delivered", rounds, fType)
-					done = true
-				}
-
-			// Return an error if the timeout is reached
-			case <-timer.C:
-				err = errors.New("Response from User Discovery did not come " +
-					"before timeout")
-				done = true
-
-			// Return the contacts if one is returned
-			case response := <-responseChan:
-				if response.Error != "" {
-					err = errors.Errorf("User Discovery returned an error on "+
-						"search: %s", response.Error)
-				} else {
-					jww.INFO.Printf("%v", response.Contacts)
-					c, err = m.parseContacts(response.Contacts, factMap)
-				}
-				done = true
-			}
-		}
-
-		// Delete the response channel from the map
-		m.inProgressSearchMux.Lock()
-		delete(m.inProgressSearch, commID)
-		m.inProgressSearchMux.Unlock()
+	// 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
+	}
 
-		// Call the callback last in case it is blocking
-		callback(c, err)
-	}()
+	c, err := m.parseContacts(searchResponse.Contacts, factMap)
+	if err != nil {
+		go callback(nil, errors.WithMessage(err, "Failed to parse contacts from "+
+			"remote server."))
+		return
+	}
 
-	return nil
+	go callback(c, nil)
 }
 
 // hashFactList hashes each fact in the FactList into a HashFact and returns a
@@ -188,7 +103,8 @@ func hashFactList(list fact.FactList) ([]*HashFact, map[string]fact.Fact) {
 
 // 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) {
+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
@@ -196,7 +112,7 @@ func (m *Manager) parseContacts(response []*Contact, hashMap map[string]fact.Fac
 		// 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)
+			return nil, errors.Errorf("failed to parse Contact user ID: %+v", err)
 		}
 
 		// Create new Contact
diff --git a/ud/search_test.go b/ud/search_test.go
index 4b4d7eba084967ea9165e0df3c1c9c7ce35af912..bbbb65c0d517f8abaa09df06628a5acecd663e27 100644
--- a/ud/search_test.go
+++ b/ud/search_test.go
@@ -1,33 +1,33 @@
 package ud
 
 import (
+	"fmt"
 	"github.com/golang/protobuf/proto"
+	errors "github.com/pkg/errors"
 	"gitlab.com/elixxir/client/interfaces/contact"
-	"gitlab.com/elixxir/client/interfaces/message"
-	"gitlab.com/elixxir/client/storage"
+	"gitlab.com/elixxir/client/single"
+	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/factID"
-	"gitlab.com/elixxir/crypto/fastRNG"
 	"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"
 	"time"
 )
 
 // Happy path.
 func TestManager_Search(t *testing.T) {
-	isReg := uint32(1)
 	// Set up manager
+	isReg := uint32(1)
+	grp := cyclic.NewGroup(large.NewInt(107), large.NewInt(2))
 	m := &Manager{
-		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
-		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		storage:          storage.InitTestingSession(t),
-		udID:             &id.UDB,
-		inProgressSearch: map[uint64]chan *SearchResponse{},
-		net:              newTestNetworkManager(t),
+		grp:        grp,
+		udContact:  contact.Contact{ID: &id.UDB, DhPubKey: grp.NewInt(42)},
+		single:     &mockSingleSearch{},
 		registered: &isReg,
 	}
 
@@ -44,60 +44,29 @@ func TestManager_Search(t *testing.T) {
 	}
 
 	// Generate fact list
-	factList := fact.FactList{
-		{Fact: "fact1", T: fact.Username},
-		{Fact: "fact2", T: fact.Email},
-		{Fact: "fact3", T: fact.Phone},
+	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)),
+		})
 	}
-
-	// Trigger lookup response chan
-	responseContacts := []*Contact{
-		{
-			UserID: id.NewIdFromUInt(5, id.User, t).Bytes(),
-			PubKey: []byte{42},
-			TrigFacts: []*HashFact{
-				{Hash: factID.Fingerprint(factList[0]), Type: int32(factList[0].T)},
-				{Hash: factID.Fingerprint(factList[1]), Type: int32(factList[1].T)},
-				{Hash: factID.Fingerprint(factList[2]), Type: int32(factList[2].T)},
-			},
-		},
+	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},
+		})
 	}
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		m.inProgressSearch[0] <- &SearchResponse{
-			Contacts: responseContacts,
-			Error:    "",
-		}
-	}()
 
-	// Run the search
-	err := m.Search(factList, callback, 20*time.Millisecond)
+	err := m.Search(factList, callback, 10*time.Millisecond)
 	if err != nil {
 		t.Errorf("Search() returned an error: %+v", err)
 	}
 
-	// Generate expected Send message
-	factHashes, factMap := hashFactList(factList)
-	payload, err := proto.Marshal(&SearchSend{
-		Fact:   factHashes,
-		CommID: m.commID - 1,
-	})
-	if err != nil {
-		t.Fatalf("Failed to marshal SearchSend: %+v", err)
-	}
-	expectedMsg := message.Send{
-		Recipient:   m.udID,
-		Payload:     payload,
-		MessageType: message.UdSearch,
-	}
-
-	// Verify the message is correct
-	if !reflect.DeepEqual(expectedMsg, m.net.(*testNetworkManager).msg) {
-		t.Errorf("Failed to send correct message."+
-			"\n\texpected: %+v\n\treceived: %+v",
-			expectedMsg, m.net.(*testNetworkManager).msg)
-	}
-
 	// Verify the callback is called
 	select {
 	case cb := <-callbackChan:
@@ -105,38 +74,146 @@ func TestManager_Search(t *testing.T) {
 			t.Errorf("Callback returned an error: %+v", cb.err)
 		}
 
-		expectedContacts, err := m.parseContacts(responseContacts, factMap)
-		if err != nil {
-			t.Fatalf("parseResponseContacts() returned an error: %+v", err)
-		}
-		if !reflect.DeepEqual(expectedContacts, cb.c) {
+		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)
+				"\n\texpected: %+v\n\treceived: %+v", expectedContacts, 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 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),
-		udID:             &id.UDB,
-		inProgressSearch: map[uint64]chan *SearchResponse{},
-		net:              newTestNetworkManager(t),
-		registered: &isReg,
-	}
+//
+// // 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))}
 
-	// Generate callback function
 	callbackChan := make(chan struct {
 		c   []contact.Contact
 		err error
@@ -149,62 +226,121 @@ func TestManager_Search_CallbackError(t *testing.T) {
 	}
 
 	// Generate fact list
-	factList := fact.FactList{
-		{Fact: "fact1", T: fact.Username},
-		{Fact: "fact2", T: fact.Email},
-		{Fact: "fact3", T: fact.Phone},
+	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)
 
-	// Trigger lookup response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		m.inProgressSearch[0] <- &SearchResponse{
-			Contacts: nil,
-			Error:    "Error",
-		}
-	}()
+	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)]},
+		})
+	}
 
-	// Run the search
-	err := m.Search(factList, callback, 10*time.Millisecond)
+	// Generate expected Send message
+	payload, err := proto.Marshal(&SearchResponse{Contacts: contacts})
 	if err != nil {
-		t.Errorf("Search() returned an error: %+v", err)
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
 	}
 
-	// Verify the callback is called
+	m.searchResponseHandler(factMap, callback, payload, nil)
+
 	select {
-	case cb := <-callbackChan:
-		if cb.err == nil {
-			t.Error("Callback did not return an expected error.")
+	case results := <-callbackChan:
+		if results.err != nil {
+			t.Errorf("Callback returned an error: %+v", results.err)
 		}
-
-		if cb.c != nil {
-			t.Errorf("Failed to get expected Contacts."+
-				"\n\texpected: %v\n\treceived: %v", nil, cb.c)
+		if !reflect.DeepEqual(expectedContacts, results.c) {
+			t.Errorf("Callback returned incorrect Contacts."+
+				"\nexpected: %+v\nreceived: %+v", expectedContacts, results.c)
 		}
-	case <-time.After(100 * time.Millisecond):
-		t.Error("Callback not called.")
+	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}
 	}
 
-	if _, exists := m.inProgressSearch[m.commID-1]; exists {
-		t.Error("Failed to delete SearchResponse from inProgressSearch.")
+	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: 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),
-		udID:             &id.UDB,
-		inProgressSearch: map[uint64]chan *SearchResponse{},
-		net:              newTestNetworkManager(t),
-		registered: &isReg,
+// 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 callback function
+	// 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
@@ -216,128 +352,141 @@ func TestManager_Search_EventChanTimeout(t *testing.T) {
 		}{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},
+	var contacts []*Contact
+	for i := 0; i < 10; i++ {
+		contacts = append(contacts, &Contact{
+			UserID: []byte{byte(i + 1)},
+		})
 	}
 
-	// Run the search
-	err := m.Search(factList, callback, 10*time.Millisecond)
+	// Generate expected Send message
+	payload, err := proto.Marshal(&SearchResponse{Contacts: contacts})
 	if err != nil {
-		t.Errorf("Search() returned an error: %+v", err)
+		t.Fatalf("Failed to marshal LookupSend: %+v", err)
 	}
 
-	// Verify the callback is called
+	m.searchResponseHandler(nil, callback, payload, nil)
+
 	select {
-	case cb := <-callbackChan:
-		if cb.err == nil {
-			t.Error("Callback did not return an expected error.")
+	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.")
+	}
+}
 
-		if cb.c != nil {
-			t.Errorf("Failed to get expected Contacts."+
-				"\n\texpected: %v\n\treceived: %v", nil, cb.c)
+// 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)),
 		}
-	case <-time.After(100 * time.Millisecond):
-		t.Error("Callback not called.")
+		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 _, exists := m.inProgressSearch[m.commID-1]; exists {
-		t.Error("Failed to delete SearchResponse from inProgressSearch.")
+	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_searchProcess(t *testing.T) {
-	isReg := uint32(1)
-	m := &Manager{
-		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
-		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		storage:          storage.InitTestingSession(t),
-		udID:             &id.UDB,
-		inProgressSearch: map[uint64]chan *SearchResponse{},
-		net:              newTestNetworkManager(t),
-		registered: &isReg,
+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)
 
-	c := make(chan message.Receive)
-	quitCh := make(chan struct{})
+	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(&SearchSend{
-		Fact: []*HashFact{&HashFact{
-			Hash:                 []byte{1},
-			Type:                 0,
-		}},
-		CommID: m.commID,
-	})
+	testContacts, err := m.parseContacts(contacts, factMap)
 	if err != nil {
-		t.Fatalf("Failed to marshal LookupSend: %+v", err)
+		t.Errorf("parseContacts() returned an error: %+v", err)
 	}
 
-	m.inProgressSearch[m.commID] = make(chan *SearchResponse, 1)
-
-	// Trigger response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		c <- message.Receive{
-			Payload:    payload,
-			Encryption: message.E2E,
-		}
-		time.Sleep(1 * time.Millisecond)
-		quitCh <- struct{}{}
-	}()
-
-	m.searchProcess(c, quitCh)
-
-	select {
-	case response := <-m.inProgressSearch[m.commID]:
-		expectedResponse := &SearchResponse{}
-		if err := proto.Unmarshal(payload, expectedResponse); err != nil {
-			t.Fatalf("Failed to unmarshal payload: %+v", err)
-		}
-
-		if !reflect.DeepEqual(expectedResponse, response) {
-			t.Errorf("Recieved unexpected response."+
-				"\n\texpected: %+v\n\trecieved: %+v", expectedResponse, response)
-		}
-	case <-time.After(100 * time.Millisecond):
-		t.Error("Response not sent.")
+	if !reflect.DeepEqual(expectedContacts, testContacts) {
+		t.Errorf("parseContacts() did not return the expected contacts."+
+			"\nexpected: %+v\nreceived: %+v", expectedContacts, testContacts)
 	}
 }
 
-// Error path: dropped lookup response due to incorrect message.Receive.
-func TestManager_searchpProcess_NoSearchResponse(t *testing.T) {
-	isReg := uint32(1)
-	m := &Manager{
-		rng:              fastRNG.NewStreamGenerator(12, 3, csprng.NewSystemRNG),
-		grp:              cyclic.NewGroup(large.NewInt(107), large.NewInt(2)),
-		storage:          storage.InitTestingSession(t),
-		udID:             &id.UDB,
-		inProgressSearch: map[uint64]chan *SearchResponse{},
-		net:              newTestNetworkManager(t),
-		registered: &isReg,
+// 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)
 	}
+}
 
-	c := make(chan message.Receive)
-	quitCh := make(chan struct{})
+// mockSingleSearch is used to test the search function, which uses the single-
+// use manager. It adheres to the SingleInterface interface.
+type mockSingleSearch struct {
+}
 
-	// Trigger response chan
-	go func() {
-		time.Sleep(1 * time.Millisecond)
-		c <- message.Receive{}
-		time.Sleep(1 * time.Millisecond)
-		quitCh <- struct{}{}
-	}()
+func (s *mockSingleSearch) TransmitSingleUse(partner contact.Contact, payload []byte,
+	_ string, _ uint8, callback single.ReplyComm, _ time.Duration) error {
 
-	m.lookupProcess(c, quitCh)
+	searchMsg := &SearchSend{}
+	if err := proto.Unmarshal(payload, searchMsg); err != nil {
+		return errors.Errorf("Failed to unmarshal SearchSend: %+v", err)
+	}
 
-	select {
-	case response := <-m.inProgressSearch[m.commID]:
-		t.Errorf("Received unexpected response: %+v", response)
-	case <-time.After(10 * time.Millisecond):
-		return
+	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)
 	}
-}
\ No newline at end of file
+
+	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
index 33035584df24e07f83b22349442cce568d176f80..1a245ab0c669f283d9ac5ee522d6e3f763501c4e 100644
--- a/ud/udMessages.pb.go
+++ b/ud/udMessages.pb.go
@@ -128,12 +128,10 @@ func (m *Contact) GetTrigFacts() []*HashFact {
 // 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"`
-	// ID of the session used to create this session
-	CommID               uint64   `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	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{} }
@@ -168,18 +166,10 @@ func (m *SearchSend) GetFact() []*HashFact {
 	return nil
 }
 
-func (m *SearchSend) GetCommID() uint64 {
-	if m != nil {
-		return m.CommID
-	}
-	return 0
-}
-
 // 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"`
-	CommID               uint64     `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"`
 	Error                string     `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
 	XXX_unrecognized     []byte     `json:"-"`
@@ -218,13 +208,6 @@ func (m *SearchResponse) GetContacts() []*Contact {
 	return nil
 }
 
-func (m *SearchResponse) GetCommID() uint64 {
-	if m != nil {
-		return m.CommID
-	}
-	return 0
-}
-
 func (m *SearchResponse) GetError() string {
 	if m != nil {
 		return m.Error
@@ -235,7 +218,6 @@ func (m *SearchResponse) GetError() string {
 // Message sent to UDB for looking up a user
 type LookupSend struct {
 	UserID               []byte   `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"`
-	CommID               uint64   `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -273,17 +255,9 @@ func (m *LookupSend) GetUserID() []byte {
 	return nil
 }
 
-func (m *LookupSend) GetCommID() uint64 {
-	if m != nil {
-		return m.CommID
-	}
-	return 0
-}
-
 // Message sent from UDB for looking up a user
 type LookupResponse struct {
 	PubKey               []byte   `protobuf:"bytes,1,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
-	CommID               uint64   `protobuf:"varint,2,opt,name=commID,proto3" json:"commID,omitempty"`
 	Error                string   `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
@@ -322,13 +296,6 @@ func (m *LookupResponse) GetPubKey() []byte {
 	return nil
 }
 
-func (m *LookupResponse) GetCommID() uint64 {
-	if m != nil {
-		return m.CommID
-	}
-	return 0
-}
-
 func (m *LookupResponse) GetError() string {
 	if m != nil {
 		return m.Error
@@ -345,28 +312,25 @@ func init() {
 	proto.RegisterType((*LookupResponse)(nil), "parse.LookupResponse")
 }
 
-func init() {
-	proto.RegisterFile("udMessages.proto", fileDescriptor_9e0cfdc16fb09bb6)
-}
+func init() { proto.RegisterFile("udMessages.proto", fileDescriptor_9e0cfdc16fb09bb6) }
 
 var fileDescriptor_9e0cfdc16fb09bb6 = []byte{
-	// 285 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x51, 0xc1, 0x4a, 0xc3, 0x40,
-	0x10, 0x65, 0x9b, 0xa4, 0xb6, 0x63, 0x89, 0xb2, 0x88, 0xe4, 0x18, 0xe2, 0x25, 0x08, 0xe6, 0x50,
-	0xaf, 0x9e, 0xb4, 0x88, 0x41, 0xbd, 0x6c, 0xc1, 0x83, 0xb7, 0x6d, 0x32, 0x36, 0x2a, 0xcd, 0x2e,
-	0x3b, 0x9b, 0x43, 0xff, 0x5e, 0xb2, 0x59, 0x5b, 0x84, 0x2a, 0x78, 0x9b, 0x37, 0xb3, 0xef, 0xcd,
-	0x9b, 0xb7, 0x70, 0xda, 0xd5, 0xcf, 0x48, 0x24, 0xd7, 0x48, 0x85, 0x36, 0xca, 0x2a, 0x1e, 0x69,
-	0x69, 0x08, 0xb3, 0x39, 0x4c, 0x1e, 0x24, 0x35, 0xf7, 0xb2, 0xb2, 0x9c, 0x43, 0xd8, 0x48, 0x6a,
-	0x12, 0x96, 0xb2, 0x7c, 0x26, 0x5c, 0xdd, 0xf7, 0xec, 0x56, 0x63, 0x32, 0x4a, 0x59, 0x1e, 0x09,
-	0x57, 0x67, 0x0d, 0x1c, 0xdd, 0xa9, 0xd6, 0xf6, 0x94, 0x73, 0x18, 0x77, 0x84, 0xa6, 0x5c, 0x78,
-	0x92, 0x47, 0x7d, 0x5f, 0x77, 0xab, 0x47, 0xdc, 0x3a, 0xe2, 0x4c, 0x78, 0xc4, 0xaf, 0x60, 0x6a,
-	0xcd, 0xfb, 0xba, 0x5f, 0x47, 0x49, 0x90, 0x06, 0xf9, 0xf1, 0xfc, 0xa4, 0x70, 0x4e, 0x8a, 0x6f,
-	0x1b, 0x62, 0xff, 0x22, 0x2b, 0x01, 0x96, 0x28, 0x4d, 0xd5, 0x2c, 0xb1, 0xad, 0xf9, 0x05, 0x84,
-	0x6f, 0xb2, 0xb2, 0x09, 0x3b, 0xcc, 0x73, 0xc3, 0x7e, 0x73, 0xa5, 0x36, 0x9b, 0x72, 0xe1, 0x36,
-	0x87, 0xc2, 0xa3, 0xec, 0x03, 0xe2, 0x41, 0x4a, 0x20, 0x69, 0xd5, 0x12, 0xf2, 0x4b, 0x98, 0x54,
-	0xc3, 0x19, 0xe4, 0x25, 0x63, 0x2f, 0xe9, 0xaf, 0x13, 0xbb, 0xf9, 0x6f, 0xaa, 0xfc, 0x0c, 0x22,
-	0x34, 0x46, 0x99, 0x24, 0x48, 0x59, 0x3e, 0x15, 0x03, 0xc8, 0x6e, 0x00, 0x9e, 0x94, 0xfa, 0xec,
-	0xb4, 0xb3, 0xfd, 0x47, 0x46, 0x07, 0x9d, 0xbe, 0x40, 0x3c, 0xb0, 0x77, 0x4e, 0xf7, 0x69, 0xb2,
-	0x1f, 0x69, 0xfe, 0xcb, 0xd5, 0x6d, 0xf8, 0x3a, 0xea, 0xea, 0xd5, 0xd8, 0x7d, 0xff, 0xf5, 0x57,
-	0x00, 0x00, 0x00, 0xff, 0xff, 0x3d, 0x49, 0xa1, 0x5e, 0x12, 0x02, 0x00, 0x00,
+	// 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
index fba57dcf6c35db303cb24e477042304d895e482b..0fdc127500d548390c5a8385b445b0ab31130f90 100644
--- a/ud/udMessages.proto
+++ b/ud/udMessages.proto
@@ -30,27 +30,22 @@ message Contact {
 message SearchSend {
   // PublicKey used in the registration
   repeated HashFact fact = 1;
-  // ID of the session used to create this session
-  uint64 commID = 2;
 }
 
 // Message sent from UDB to client in response to a search
 message SearchResponse {
   // ID of the session created
   repeated Contact contacts = 1;
-  uint64 commID = 2;
   string error = 3;
 }
 
 // Message sent to UDB for looking up a user
 message LookupSend {
   bytes userID = 1;
-  uint64 commID = 2;
 }
 
 // Message sent from UDB for looking up a user
 message LookupResponse {
   bytes pubKey = 1;
-  uint64 commID = 2;
   string error = 3;
 }
\ No newline at end of file