diff --git a/single/cypher.go b/single/cypher.go
index 065cfe2d6525c60a8ae59b770b60b192287c4b16..6776c0cc673c5d768bf18935ea7c5f66e0ce0de4 100644
--- a/single/cypher.go
+++ b/single/cypher.go
@@ -9,13 +9,18 @@ package single
 
 import (
 	"github.com/pkg/errors"
-	"gitlab.com/elixxir/client/single/message"
 	"gitlab.com/elixxir/crypto/cyclic"
 	cAuth "gitlab.com/elixxir/crypto/e2e/auth"
 	"gitlab.com/elixxir/crypto/e2e/singleUse"
 	"gitlab.com/elixxir/primitives/format"
 )
 
+// Error messages.
+const (
+	// cypher.decrypt
+	errMacVerification = "failed to verify the single-use MAC"
+)
+
 type newKeyFn func(dhKey *cyclic.Int, keyNum uint64) []byte
 type newFpFn func(dhKey *cyclic.Int, keyNum uint64) format.Fingerprint
 
@@ -25,10 +30,10 @@ func makeCyphers(dhKey *cyclic.Int, messageCount uint8, newKey newKeyFn,
 
 	cypherList := make([]cypher, messageCount)
 
-	for i := uint8(0); i < messageCount; i++ {
+	for i := range cypherList {
 		cypherList[i] = cypher{
 			dhKey:  dhKey,
-			num:    i,
+			num:    uint8(i),
 			newKey: newKey,
 			newFp:  newFp,
 		}
@@ -44,33 +49,38 @@ type cypher struct {
 	newFp  newFpFn  // Function used to create new fingerprint
 }
 
-func (rk *cypher) getKey() []byte {
-	return rk.newKey(rk.dhKey, uint64(rk.num))
+// getKey generates a new encryption/description key from the DH key and number.
+func (c *cypher) getKey() []byte {
+	return c.newKey(c.dhKey, uint64(c.num))
 }
 
-func (rk *cypher) GetFingerprint() format.Fingerprint {
-	return rk.newFp(rk.dhKey, uint64(rk.num))
+// getFingerprint generates a new key fingerprint from the DH key and number.
+func (c *cypher) getFingerprint() format.Fingerprint {
+	return c.newFp(c.dhKey, uint64(c.num))
 }
 
-func (rk *cypher) Encrypt(rp message.ResponsePart) (
-	fp format.Fingerprint, encryptedPayload, mac []byte) {
-	fp = rk.GetFingerprint()
-	key := rk.getKey()
+// encrypt encrypts the payload.
+func (c *cypher) encrypt(
+	payload []byte) (fp format.Fingerprint, encryptedPayload, mac []byte) {
+	fp = c.getFingerprint()
+	key := c.getKey()
 
 	// FIXME: Encryption is identical to what is used by e2e.Crypt, lets make
 	//  them the same code path.
-	encryptedPayload = cAuth.Crypt(key, fp[:24], rp.Marshal())
+	encryptedPayload = cAuth.Crypt(key, fp[:24], payload)
 	mac = singleUse.MakeMAC(key, encryptedPayload)
+
 	return fp, encryptedPayload, mac
 }
 
-func (rk *cypher) Decrypt(contents, mac []byte) ([]byte, error) {
-	fp := rk.GetFingerprint()
-	key := rk.getKey()
+// decrypt decrypts the payload.
+func (c *cypher) decrypt(contents, mac []byte) ([]byte, error) {
+	fp := c.getFingerprint()
+	key := c.getKey()
 
-	// Verify the CMix message MAC
+	// Verify the cMix message MAC
 	if !singleUse.VerifyMAC(key, contents, mac) {
-		return nil, errors.New("failed to verify the single-use MAC")
+		return nil, errors.New(errMacVerification)
 	}
 
 	// Decrypt the payload
diff --git a/single/cypher_test.go b/single/cypher_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..13a47e354db61d1c96f913b6d10a7bf97f029744
--- /dev/null
+++ b/single/cypher_test.go
@@ -0,0 +1,150 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"bytes"
+	"fmt"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/large"
+	"testing"
+)
+
+// Tests that makeCyphers returns a list of cyphers of the correct length and
+// that each cypher in the list has the correct DH key and number.
+func Test_makeCyphers(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	dhKey := diffieHellman.GeneratePublicKey(grp.NewInt(42), grp)
+	messageCount := 16
+
+	cyphers := makeCyphers(dhKey, uint8(messageCount), nil, nil)
+
+	if len(cyphers) != messageCount {
+		t.Errorf("Wrong number of cyphers.\nexpected: %d\nreceived: %d",
+			messageCount, len(cyphers))
+	}
+
+	for i, c := range cyphers {
+		if dhKey.Cmp(c.dhKey) != 0 {
+			t.Errorf("Cypher #%d has incorrect DH key."+
+				"\nexpected: %s\nreceived: %s",
+				i, dhKey.Text(10), c.dhKey.Text(10))
+		}
+		if int(c.num) != i {
+			t.Errorf("Cypher #%d has incorrect number."+
+				"\nexpected: %d\nreceived: %d", i, i, c.num)
+		}
+	}
+}
+
+// Tests that cypher.getKey returns the expected key from the passed in newKey
+// function. Also tests that the expected DH key and number are passed to the
+// new key function.
+func Test_cypher_getKey(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	newKey := func(dhKey *cyclic.Int, keyNum uint64) []byte {
+		return []byte(fmt.Sprintf("KEY #%d  %s", keyNum, dhKey.Text(10)))
+	}
+
+	c := &cypher{
+		dhKey:  diffieHellman.GeneratePublicKey(grp.NewInt(42), grp),
+		num:    6,
+		newKey: newKey,
+	}
+
+	expectedKey := newKey(c.dhKey, uint64(c.num))
+
+	key := c.getKey()
+	if !bytes.Equal(expectedKey, key) {
+		t.Errorf(
+			"Unexpected key.\nexpected: %q\nreceived: %q", expectedKey, key)
+	}
+}
+
+// Tests that cypher.getFingerprint returns the expected fingerprint from the
+// passed in newFp function. Also tests that the expected DH key and number are
+// passed to the new fingerprint function.
+func Test_cypher_getFingerprint(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	newFp := func(dhKey *cyclic.Int, keyNum uint64) format.Fingerprint {
+		return format.NewFingerprint([]byte(
+			fmt.Sprintf("FP #%d  %s", keyNum, dhKey.Text(10))))
+	}
+
+	c := &cypher{
+		dhKey: diffieHellman.GeneratePublicKey(grp.NewInt(42), grp),
+		num:   6,
+		newFp: newFp,
+	}
+
+	expectedFp := newFp(c.dhKey, uint64(c.num))
+
+	fp := c.getFingerprint()
+	if expectedFp != fp {
+		t.Errorf("Unexpected fingerprint.\nexpected: %s\nreceived: %s",
+			expectedFp, fp)
+	}
+}
+
+// Tests that a payload encrypted by cypher.encrypt and decrypted by
+// cypher.decrypt matches the original. Tests with both the response and request
+// part key and fingerprint functions.
+func Test_cypher_encrypt_decrypt(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	funcs := []struct {
+		newKey newKeyFn
+		newFp  newFpFn
+	}{
+		{singleUse.NewResponseKey, singleUse.NewResponseFingerprint},
+		{singleUse.NewRequestPartKey, singleUse.NewRequestPartFingerprint},
+	}
+	c := &cypher{
+		dhKey: diffieHellman.GeneratePublicKey(grp.NewInt(42), grp),
+		num:   6,
+	}
+
+	for i, fn := range funcs {
+		c.newKey = fn.newKey
+		c.newFp = fn.newFp
+
+		payload := []byte("I am a single-use payload message.")
+
+		_, encryptedPayload, mac := c.encrypt(payload)
+
+		decryptedPayload, err := c.decrypt(encryptedPayload, mac)
+		if err != nil {
+			t.Errorf("decrypt returned an error (%d): %+v", i, err)
+		}
+
+		if !bytes.Equal(payload, decryptedPayload) {
+			t.Errorf("Decrypted payload does not match original (%d)."+
+				"\nexpected: %q\nreceived: %q", i, payload, decryptedPayload)
+		}
+	}
+}
+
+// Error path: tests that cypher.decrypt returns the expected error when the MAC
+// is invalid.
+func Test_cypher_decrypt_InvalidMacError(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	c := &cypher{
+		dhKey:  diffieHellman.GeneratePublicKey(grp.NewInt(42), grp),
+		num:    6,
+		newKey: singleUse.NewResponseKey,
+		newFp:  singleUse.NewResponseFingerprint,
+	}
+
+	_, err := c.decrypt([]byte("contents"), []byte("mac"))
+	if err == nil || err.Error() != errMacVerification {
+		t.Errorf("decrypt did not return the expected error with invalid MAC."+
+			"\nexpected: %s\nreceived: %+v", errMacVerification, err)
+	}
+}
diff --git a/single/interfaces.go b/single/interfaces.go
index 8ee9bf00212ff187cc5059798d7f44a2fb2a9cda..d72ba2fa178555e827544358b03a9c2bbfdc3af8 100644
--- a/single/interfaces.go
+++ b/single/interfaces.go
@@ -10,9 +10,9 @@ import (
 	"time"
 )
 
-// CMix is a sub-interface of the cmix.Client. It contains the methods
-// relevant to what is used in this package.
-type CMix interface {
+// Cmix is a sub-interface of the cmix.Client. It contains the methods relevant
+// to what is used in this package.
+type Cmix interface {
 	IsHealthy() bool
 	GetAddressSpace() uint8
 	GetMaxMessageLength() int
diff --git a/single/listener.go b/single/listener.go
index 6ba7d365ca212427e62cdecf02f18ce70fe2cb28..869cbac443b93baf6fed142f89653a7f88113987 100644
--- a/single/listener.go
+++ b/single/listener.go
@@ -2,10 +2,10 @@ package single
 
 import (
 	"fmt"
-
+	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/cmix/identity/receptionID"
-	cmixMsg "gitlab.com/elixxir/client/cmix/message"
+	cMixMsg "gitlab.com/elixxir/client/cmix/message"
 	"gitlab.com/elixxir/client/cmix/rounds"
 	"gitlab.com/elixxir/client/single/message"
 	"gitlab.com/elixxir/crypto/cyclic"
@@ -13,10 +13,9 @@ import (
 	"gitlab.com/elixxir/crypto/e2e/singleUse"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/primitives/id"
+	"strings"
 )
 
-const listenerProcessorName = "listenerProcessorName"
-
 type Receiver interface {
 	Callback(*Request, receptionID.EphemeralIdentity, []rounds.Round)
 }
@@ -27,12 +26,12 @@ type Listener interface {
 }
 
 type listener struct {
-	myId      *id.ID
-	myPrivKey *cyclic.Int
 	tag       string
 	grp       *cyclic.Group
+	myID      *id.ID
+	myPrivKey *cyclic.Int
 	cb        Receiver
-	net       CMix
+	net       ListenCmix
 }
 
 // Listen allows a server to listen for single use requests. It will register a
@@ -40,38 +39,60 @@ type listener struct {
 // listener can be active for a tag-myID pair, and an error will be returned if
 // that is violated. When requests are received, they will be called on the
 // Receiver interface.
-func Listen(tag string, myId *id.ID, privKey *cyclic.Int, net CMix,
+func Listen(tag string, myID *id.ID, privKey *cyclic.Int, net ListenCmix,
 	e2eGrp *cyclic.Group, cb Receiver) Listener {
 
 	l := &listener{
-		myId:      myId,
-		myPrivKey: privKey,
 		tag:       tag,
 		grp:       e2eGrp,
+		myID:      myID,
+		myPrivKey: privKey,
 		cb:        cb,
 		net:       net,
 	}
 
-	svc := cmixMsg.Service{
-		Identifier: myId[:],
+	svc := cMixMsg.Service{
+		Identifier: myID[:],
 		Tag:        tag,
-		Metadata:   myId[:],
+		Metadata:   myID[:],
 	}
 
-	net.AddService(myId, svc, l)
+	net.AddService(myID, svc, l)
 
 	return l
 }
 
+type ListenCmix interface {
+	RequestCmix
+	AddFingerprint(identity *id.ID, fingerprint format.Fingerprint,
+		mp cMixMsg.Processor) error
+	AddService(
+		clientID *id.ID, newService cMixMsg.Service, response cMixMsg.Processor)
+	DeleteService(
+		clientID *id.ID, toDelete cMixMsg.Service, processor cMixMsg.Processor)
+	CheckInProgressMessages()
+}
+
+// Process decrypts and collates the encrypted single-use request message.
 func (l *listener) Process(ecrMsg format.Message,
 	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
+	err := l.process(ecrMsg, receptionID, round)
+	if err != nil {
+		jww.ERROR.Printf(
+			"[SU] Failed to process single-use request to %s on tag %q: %+v",
+			l.myID, l.tag, err)
+	}
+}
+
+// process is a helper functions for Process that returns errors for easier
+// testing.
+func (l *listener) process(ecrMsg format.Message,
+	receptionID receptionID.EphemeralIdentity, round rounds.Round) error {
 	// Unmarshal the cMix message contents to a request message
 	requestMsg, err := message.UnmarshalRequest(ecrMsg.GetContents(),
 		l.grp.GetP().ByteLen())
 	if err != nil {
-		jww.WARN.Printf("Failed to unmarshal contents on single use "+
-			"request to %s on tag %s: %+v", l.myId, l.tag, err)
-		return
+		return errors.Errorf("could not unmarshal contents: %+v", err)
 	}
 
 	// Generate DH key and symmetric key
@@ -81,25 +102,21 @@ func (l *listener) Process(ecrMsg format.Message,
 
 	// Verify the MAC
 	if !singleUse.VerifyMAC(key, requestMsg.GetPayload(), ecrMsg.GetMac()) {
-		jww.WARN.Printf("mac check failed on single use request to %s "+
-			"on tag %s", l.myId, l.tag)
-		return
+		return errors.New("failed to verify MAC")
 	}
 
 	// Decrypt the request message payload
 	fp := ecrMsg.GetKeyFP()
 	decryptedPayload := cAuth.Crypt(key, fp[:24], requestMsg.GetPayload())
+
 	// Unmarshal payload
 	payload, err := message.UnmarshalRequestPayload(decryptedPayload)
 	if err != nil {
-		jww.WARN.Printf("[SU] Failed to unmarshal decrypted payload on "+
-			"single use request to %s on tag %s: %+v", l.myId, l.tag, err)
-		return
+		return errors.Errorf("could not unmarshal decrypted payload: %+v", err)
 	}
 
 	cbFunc := func(payloadContents []byte, rounds []rounds.Round) {
 		used := uint32(0)
-
 		r := Request{
 			sender:         payload.GetRecipientID(requestMsg.GetPubKey(l.grp)),
 			senderPubKey:   senderPubkey,
@@ -118,28 +135,29 @@ func (l *listener) Process(ecrMsg format.Message,
 		c := message.NewCollator(numParts)
 		_, _, err = c.Collate(payload)
 		if err != nil {
-
-			return
+			return errors.Errorf("could not collate initial payload: %+v", err)
 		}
+
 		cyphers := makeCyphers(dhKey, numParts,
 			singleUse.NewRequestPartKey, singleUse.NewRequestPartFingerprint)
 		ridCollector := newRoundIdCollector(int(numParts))
+
 		for i, cy := range cyphers {
 			key = singleUse.NewRequestPartKey(dhKey, uint64(i+1))
 			fp = singleUse.NewRequestPartFingerprint(dhKey, uint64(i+1))
 			p := &requestPartProcessor{
-				myId:     l.myId,
+				myId:     l.myID,
 				tag:      l.tag,
 				cb:       cbFunc,
 				c:        c,
 				cy:       cy,
 				roundIDs: ridCollector,
 			}
-			err = l.net.AddFingerprint(l.myId, fp, p)
+
+			err = l.net.AddFingerprint(l.myID, fp, p)
 			if err != nil {
-				jww.ERROR.Printf("Failed to add fingerprint for request part "+
-					"%d of %d (%s): %+v", i, numParts, l.tag, err)
-				return
+				return errors.Errorf("could not add fingerprint for single-"+
+					"use request part %d of %d: %+v", i, numParts, err)
 			}
 		}
 
@@ -147,16 +165,45 @@ func (l *listener) Process(ecrMsg format.Message,
 	} else {
 		cbFunc(payload.GetContents(), []rounds.Round{round})
 	}
+
+	return nil
 }
 
+// Stop stops the listener from receiving messages.
 func (l *listener) Stop() {
-	svc := cmixMsg.Service{
-		Identifier: l.myId[:],
+	svc := cMixMsg.Service{
+		Identifier: l.myID[:],
 		Tag:        l.tag,
 	}
-	l.net.DeleteService(l.myId, svc, l)
+	l.net.DeleteService(l.myID, svc, l)
 }
 
+// String prints a name that identifies this single use listener. Adheres to the
+// fmt.Stringer interface.
 func (l *listener) String() string {
-	return fmt.Sprintf("SingleUse(%s)", l.myId)
+	return "SingleUse(" + l.myID.String() + ")"
+}
+
+// GoString prints the fields of the listener in a human-readable form.
+// Adheres to the fmt.GoStringer interface and prints values passed as an
+// operand to a %#v format.
+func (l *listener) GoString() string {
+	cb := "<nil>"
+	if l.cb != nil {
+		cb = fmt.Sprintf("%p", l.cb)
+	}
+	net := "<nil>"
+	if l.net != nil {
+		net = fmt.Sprintf("%p", l.net)
+	}
+	fields := []string{
+		"tag:" + fmt.Sprintf("%q", l.tag),
+		"grp:" + l.grp.GetFingerprintText(),
+		"myID:" + l.myID.String(),
+		"myPrivKey:\"" + l.myPrivKey.Text(10) + "\"",
+		"cb:" + cb,
+		"net:" + net,
+	}
+
+	return strings.Join(fields, " ")
 }
diff --git a/single/listener_test.go b/single/listener_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e2755a8f1d85404d2a0af6bdc7a0a8bf0c8be07
--- /dev/null
+++ b/single/listener_test.go
@@ -0,0 +1,337 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package single
+
+import (
+	"gitlab.com/elixxir/client/cmix"
+	"gitlab.com/elixxir/client/cmix/identity/receptionID"
+	cMixMsg "gitlab.com/elixxir/client/cmix/message"
+	"gitlab.com/elixxir/client/cmix/rounds"
+	"gitlab.com/elixxir/client/single/message"
+	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/contact"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/e2e/auth"
+	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
+	"gitlab.com/xx_network/crypto/large"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
+	"gitlab.com/xx_network/primitives/netTime"
+	"io"
+	"math/rand"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+)
+
+type cbReceiver struct {
+	requestChan chan *Request
+}
+
+func newCbReceiver(requestChan chan *Request) *cbReceiver {
+	return &cbReceiver{requestChan: requestChan}
+}
+
+func (cr *cbReceiver) Callback(
+	r *Request, _ receptionID.EphemeralIdentity, _ []rounds.Round) {
+	cr.requestChan <- r
+}
+
+// Tests that Listen returns the expected listener.
+func TestListen(t *testing.T) {
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	tag := "myTag"
+	myID := id.NewIdFromString("myID", id.User, t)
+	privKey := grp.NewInt(34)
+	handler := newListenMockCmixHandler()
+
+	expected := &listener{
+		tag:       tag,
+		grp:       grp,
+		myID:      myID,
+		myPrivKey: privKey,
+		net:       newMockListenCmix(handler),
+	}
+
+	l := Listen(tag, myID, privKey,
+		newMockListenCmix(handler), grp, nil)
+
+	if !reflect.DeepEqual(expected, l) {
+		t.Errorf("New Listener does not match expected."+
+			"\nexpected: %#v\nreceived: %#v", expected, l)
+	}
+}
+
+// Tests that listener.process correctly unmarshalls the payload and returns the
+// Request with the expected fields on the callback.
+func Test_listener_Process(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	privKey := grp.NewInt(42)
+	recipient := contact.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: grp.ExpG(privKey, grp.NewInt(1)),
+	}
+	handler := newListenMockCmixHandler()
+
+	payload := []byte("I am the payload!")
+	msg, sendingID, publicKey, dhKey := newRequestMessage(
+		payload, grp, recipient, prng, handler, t)
+
+	requestChan := make(chan *Request, 10)
+	l := &listener{
+		tag:       "tag",
+		grp:       grp,
+		myID:      id.NewIdFromString("myID", id.User, t),
+		myPrivKey: privKey,
+		cb:        newCbReceiver(requestChan),
+		net:       newMockListenCmix(handler),
+	}
+
+	err := l.process(msg, sendingID, rounds.Round{})
+	if err != nil {
+		t.Errorf("process returned an error: %+v", err)
+	}
+
+	used := uint32(0)
+	expected := &Request{
+		sender:         sendingID.Source,
+		senderPubKey:   publicKey,
+		dhKey:          dhKey,
+		tag:            l.tag,
+		maxParts:       6,
+		used:           &used,
+		requestPayload: payload,
+		net:            l.net,
+	}
+
+	select {
+	case r := <-requestChan:
+		if !reflect.DeepEqual(expected, r) {
+			t.Errorf("Received unexpected values."+
+				"\nexpected: %+v\nreceived: %+v", expected, r)
+		}
+	case <-time.After(15 * time.Millisecond):
+		t.Error("Timed out waiting to receive callback.")
+	}
+}
+
+// newRequestMessage creates a new encrypted request message for testing.
+func newRequestMessage(payload []byte, grp *cyclic.Group, recipient contact.Contact,
+	rng io.Reader, handler *mockListenCmixHandler, t *testing.T) (
+	format.Message, receptionID.EphemeralIdentity, *cyclic.Int, *cyclic.Int) {
+
+	net := newMockListenCmix(handler)
+	maxResponseMessages := uint8(6)
+	params := GetDefaultRequestParams()
+	timeStart := netTime.Now()
+
+	// Generate DH key and public key
+	dhKey, publicKey, err := generateDhKeys(grp, recipient.DhPubKey, rng)
+	if err != nil {
+		t.Errorf("Failed to generate DH keys: %+v", err)
+	}
+
+	// Build the message payload
+	request := message.NewRequest(
+		net.GetMaxMessageLength(), grp.GetP().ByteLen())
+	requestPayload := message.NewRequestPayload(
+		request.GetPayloadSize(), payload, maxResponseMessages)
+
+	// Generate new user ID and address ID
+	var sendingID receptionID.EphemeralIdentity
+	requestPayload, sendingID, err = makeIDs(
+		requestPayload, publicKey, 8, params.Timeout, timeStart, rng)
+	if err != nil {
+		t.Errorf("Failed to make new sending ID: %+v", err)
+	}
+
+	// Encrypt and assemble payload
+	fp := singleUse.NewRequestFingerprint(recipient.DhPubKey)
+	key := singleUse.NewRequestKey(dhKey)
+	encryptedPayload := auth.Crypt(key, fp[:24], requestPayload.Marshal())
+
+	// Generate cMix message MAC
+	mac := singleUse.MakeMAC(key, encryptedPayload)
+
+	// Assemble the payload
+	request.SetPubKey(publicKey)
+	request.SetPayload(encryptedPayload)
+
+	msg := format.NewMessage(net.numPrimeBytes)
+	msg.SetMac(mac)
+	msg.SetContents(request.Marshal())
+	msg.SetKeyFP(fp)
+
+	return msg, sendingID, publicKey, dhKey
+}
+
+// First successfully sends and receives request. Then, once listener.Stop is
+// called, the next send is never received.
+func Test_listener_Stop(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	grp := cyclic.NewGroup(large.NewInt(173), large.NewInt(2))
+	privKey := grp.NewInt(42)
+	recipient := contact.Contact{
+		ID:       id.NewIdFromString("recipientID", id.User, t),
+		DhPubKey: grp.ExpG(privKey, grp.NewInt(1)),
+	}
+	handler := newListenMockCmixHandler()
+	net := newMockListenCmix(handler)
+
+	payload := []byte("I am the payload!")
+	msg, _, _, _ := newRequestMessage(
+		payload, grp, recipient, prng, handler, t)
+
+	requestChan := make(chan *Request, 10)
+	myID := id.NewIdFromString("myID", id.User, t)
+	tag := "tag"
+	l := Listen(tag, myID, privKey, net, grp, newCbReceiver(requestChan))
+
+	svc := cMixMsg.Service{
+		Identifier: myID[:],
+		Tag:        tag,
+		Metadata:   myID[:],
+	}
+	_, _, _ = net.Send(myID, msg.GetKeyFP(), svc, msg.GetContents(),
+		msg.GetMac(), cmix.CMIXParams{})
+
+	select {
+	case <-requestChan:
+	case <-time.After(15 * time.Millisecond):
+		t.Error("Timed out waiting to receive callback.")
+	}
+
+	l.Stop()
+	_, _, _ = net.Send(myID, msg.GetKeyFP(), svc, msg.GetContents(),
+		msg.GetMac(), cmix.CMIXParams{})
+
+	select {
+	case r := <-requestChan:
+		t.Errorf("Received callback when it should have been stopped: %+v", r)
+	case <-time.After(15 * time.Millisecond):
+	}
+}
+
+type mockListenCmixHandler struct {
+	fingerprintMap map[id.ID]map[format.Fingerprint][]cMixMsg.Processor
+	serviceMap     map[id.ID]map[string][]cMixMsg.Processor
+	sync.Mutex
+}
+
+func newListenMockCmixHandler() *mockListenCmixHandler {
+	return &mockListenCmixHandler{
+		serviceMap: make(map[id.ID]map[string][]cMixMsg.Processor),
+	}
+}
+
+type mockListenCmix struct {
+	numPrimeBytes int
+	handler       *mockListenCmixHandler
+}
+
+func newMockListenCmix(handler *mockListenCmixHandler) *mockListenCmix {
+	return &mockListenCmix{
+		numPrimeBytes: 4096,
+		handler:       handler,
+	}
+}
+
+func (m mockListenCmix) GetMaxMessageLength() int {
+	return format.NewMessage(m.numPrimeBytes).ContentsSize()
+}
+
+func (m mockListenCmix) Send(recipient *id.ID, fingerprint format.Fingerprint,
+	service cMixMsg.Service, payload, mac []byte, _ cmix.CMIXParams) (
+	id.Round, ephemeral.Id, error) {
+	msg := format.NewMessage(m.numPrimeBytes)
+	msg.SetContents(payload)
+	msg.SetMac(mac)
+	msg.SetKeyFP(fingerprint)
+
+	m.handler.Lock()
+	defer m.handler.Unlock()
+	for _, p := range m.handler.serviceMap[*recipient][service.Tag] {
+		p.Process(msg, receptionID.EphemeralIdentity{}, rounds.Round{})
+	}
+	for _, p := range m.handler.fingerprintMap[*recipient][fingerprint] {
+		p.Process(msg, receptionID.EphemeralIdentity{}, rounds.Round{})
+	}
+
+	return 0, ephemeral.Id{}, nil
+}
+
+func (m mockListenCmix) GetInstance() *network.Instance {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (m mockListenCmix) AddFingerprint(identity *id.ID, fp format.Fingerprint,
+	mp cMixMsg.Processor) error {
+	m.handler.Lock()
+	defer m.handler.Unlock()
+
+	if _, exists := m.handler.fingerprintMap[*identity]; !exists {
+		m.handler.fingerprintMap[*identity] =
+			map[format.Fingerprint][]cMixMsg.Processor{fp: {mp}}
+		return nil
+	} else if _, exists = m.handler.fingerprintMap[*identity][fp]; !exists {
+		m.handler.fingerprintMap[*identity][fp] =
+			[]cMixMsg.Processor{mp}
+		return nil
+	}
+
+	m.handler.fingerprintMap[*identity][fp] =
+		append(m.handler.fingerprintMap[*identity][fp], mp)
+	return nil
+}
+
+func (m mockListenCmix) AddService(
+	clientID *id.ID, ms cMixMsg.Service, mp cMixMsg.Processor) {
+	m.handler.Lock()
+	defer m.handler.Unlock()
+
+	if _, exists := m.handler.serviceMap[*clientID]; !exists {
+		m.handler.serviceMap[*clientID] =
+			map[string][]cMixMsg.Processor{ms.Tag: {mp}}
+		return
+	} else if _, exists = m.handler.serviceMap[*clientID][ms.Tag]; !exists {
+		m.handler.serviceMap[*clientID][ms.Tag] =
+			[]cMixMsg.Processor{mp}
+		return
+	}
+
+	m.handler.serviceMap[*clientID][ms.Tag] =
+		append(m.handler.serviceMap[*clientID][ms.Tag], mp)
+}
+
+func (m mockListenCmix) DeleteService(
+	clientID *id.ID, toDelete cMixMsg.Service, processor cMixMsg.Processor) {
+	m.handler.Lock()
+	defer m.handler.Unlock()
+
+	for i, p := range m.handler.serviceMap[*clientID][toDelete.Tag] {
+		if p == processor {
+			m.handler.serviceMap[*clientID][toDelete.Tag] =
+				remove(m.handler.serviceMap[*clientID][toDelete.Tag], i)
+		}
+	}
+}
+
+func remove(s []cMixMsg.Processor, i int) []cMixMsg.Processor {
+	s2 := make([]cMixMsg.Processor, 0)
+	s2 = append(s2, s[:i]...)
+	return append(s2, s[i+1:]...)
+}
+
+func (m mockListenCmix) CheckInProgressMessages() {
+	// TODO implement me
+	panic("implement me")
+}
diff --git a/single/receivedRequest.go b/single/receivedRequest.go
index c0fdb2956055958f392db7c3e80dc010d937adb5..534ac46376b503cc2d1b5c89d2b5f12caeaffc35 100644
--- a/single/receivedRequest.go
+++ b/single/receivedRequest.go
@@ -8,11 +8,14 @@ import (
 	"gitlab.com/elixxir/client/cmix"
 	cmixMsg "gitlab.com/elixxir/client/cmix/message"
 	"gitlab.com/elixxir/client/single/message"
+	"gitlab.com/elixxir/comms/network"
 	ds "gitlab.com/elixxir/comms/network/dataStructures"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/e2e/singleUse"
+	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/id/ephemeral"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -27,7 +30,15 @@ type Request struct {
 	maxParts       uint8       // Max number of messages allowed in reply
 	used           *uint32     // Atomic variable
 	requestPayload []byte
-	net            CMix
+	net            RequestCmix
+}
+
+type RequestCmix interface {
+	GetMaxMessageLength() int
+	Send(recipient *id.ID, fingerprint format.Fingerprint,
+		service cmixMsg.Service, payload, mac []byte,
+		cmixParams cmix.CMIXParams) (id.Round, ephemeral.Id, error)
+	GetInstance() *network.Instance
 }
 
 // GetMaxParts returns the maximum number of message parts that can be sent in a
@@ -61,31 +72,31 @@ func (r Request) GetPayload() []byte {
 
 // String returns a string of the Contact structure.
 func (r Request) String() string {
-	format := "Request{sender:%s  senderPubKey:%s  dhKey:%s  tagFP:%s  " +
-		"maxParts:%d}"
-	return fmt.Sprintf(format, r.sender, r.senderPubKey.Text(10),
-		r.dhKey.Text(10), r.tag, r.maxParts)
+	return fmt.Sprintf(
+		"{sender:%s senderPubKey:%s dhKey:%s tag:%q maxParts:%d used:%p(%d) "+
+			"requestPayload:%q net:%p}",
+		r.sender, r.senderPubKey.Text(10), r.dhKey.Text(10), r.tag, r.maxParts,
+		r.used, atomic.LoadUint32(r.used), r.requestPayload, r.net)
 }
 
 // Respond is used to respond to the request. It sends a payload up to
 // Request.GetMaxResponseLength. It will chunk the message into multiple cMix
 // messages if it is too long for a single message. It will fail if a single
 // cMix message cannot be sent.
-func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams,
+func (r Request) Respond(payload []byte, cMixParams cmix.CMIXParams,
 	timeout time.Duration) ([]id.Round, error) {
 	// make sure this has only been run once
 	newRun := atomic.CompareAndSwapUint32(r.used, 0, 1)
 	if !newRun {
-		return nil, errors.Errorf("cannot respond to " +
-			"single-use response that has already been responded to.")
+		return nil, errors.Errorf("cannot respond to single-use response " +
+			"that has already been responded to")
 	}
 
 	// Check that the payload is not too long
 	if len(payload) > r.GetMaxResponseLength() {
-		return nil, errors.Errorf("length of provided "+
-			"payload too long for message payload capacity, max: %d, "+
-			"received: %d", r.GetMaxResponseLength(),
-			len(payload))
+		return nil, errors.Errorf("length of provided payload too long for "+
+			"message payload capacity, max: %d, received: %d",
+			r.GetMaxResponseLength(), len(payload))
 	}
 
 	// Partition the payload
@@ -97,11 +108,8 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams,
 	rounds := make([]id.Round, len(parts))
 	sendResults := make(chan ds.EventReturn, len(parts))
 
-	wg := sync.WaitGroup{}
-	wg.Add(len(parts))
-
-	if cmixParams.DebugTag != cmix.DefaultDebugTag {
-		cmixParams.DebugTag = "single.Response"
+	if cMixParams.DebugTag != cmix.DefaultDebugTag {
+		cMixParams.DebugTag = "single.Response"
 	}
 
 	// fixme: should the above debug tag and the below service tag be flipped??
@@ -111,28 +119,32 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams,
 		Metadata:   nil,
 	}
 
+	var wg sync.WaitGroup
+	wg.Add(len(parts))
 	failed := uint32(0)
 
 	for i := 0; i < len(parts); i++ {
-		go func(j int) {
+		go func(i int, part []byte) {
 			defer wg.Done()
-			partFP, ecrPart, mac := cyphers[j].Encrypt(parts[j])
+			partFP, ecrPart, mac := cyphers[i].encrypt(part)
+
 			// Send Message
-			round, ephID, err := r.net.Send(r.sender, partFP, svc,
-				ecrPart, mac,
-				cmixParams)
+			round, ephID, err := r.net.Send(
+				r.sender, partFP, svc, ecrPart, mac, cMixParams)
 			if err != nil {
 				atomic.AddUint32(&failed, 1)
-				jww.ERROR.Printf("Failed to send single-use response CMIX "+
-					"message part %d: %+v", j, err)
+				jww.ERROR.Printf("[SU] Failed to send single-use response "+
+					"cMix message part %d of %d: %+v", i, len(parts), err)
+				return
 			}
-			jww.DEBUG.Printf("Sending single-use response CMIX message part "+
-				"%d on round %d to address ID %d.", j, round, ephID.Int64())
-			rounds[j] = round
 
-			r.net.GetInstance().GetRoundEvents().AddRoundEventChan(round, sendResults,
-				timeout, states.COMPLETED, states.FAILED)
-		}(i)
+			jww.DEBUG.Printf("[SU] Sent single-use response cMix message part "+
+				"%d on round %d to address ID %d.", i, round, ephID.Int64())
+			rounds[i] = round
+
+			r.net.GetInstance().GetRoundEvents().AddRoundEventChan(
+				round, sendResults, timeout, states.COMPLETED, states.FAILED)
+		}(i, parts[i].Marshal())
 	}
 
 	// Wait for all go routines to finish
@@ -158,7 +170,6 @@ func (r Request) Respond(payload []byte, cmixParams cmix.CMIXParams,
 	success, numRoundFail, numTimeOut := cmix.TrackResults(
 		sendResults, len(roundMap))
 	if !success {
-
 		return nil, errors.Errorf("tracking results of %d rounds: %d round "+
 			"failures, %d round event time outs; the send cannot be retried.",
 			len(rounds), numRoundFail, numTimeOut)
@@ -195,7 +206,7 @@ func partitionResponse(payload []byte, cmixMessageLength int, maxParts uint8) []
 // them in a slice. Each part's size is less than or equal to maxSize. Any extra
 // data in the payload is not used if it is longer than the maximum capacity.
 func splitPayload(payload []byte, maxSize, maxParts int) [][]byte {
-	var parts [][]byte
+	parts := make([][]byte, 0, len(payload)/maxSize)
 	buff := bytes.NewBuffer(payload)
 
 	for i := 0; i < maxParts && buff.Len() > 0; i++ {
diff --git a/single/request.go b/single/request.go
index 2b8a11bb2d5fa29ce6dfea434bdee910fa5be3f9..6f2d71ae8183a3f45f0f6dde7edaff80d104fc29 100644
--- a/single/request.go
+++ b/single/request.go
@@ -68,7 +68,7 @@ const (
 const maxNumRequestParts = 255
 
 // GetMaxRequestSize returns the maximum size of a request payload.
-func GetMaxRequestSize(net CMix, e2eGrp *cyclic.Group) int {
+func GetMaxRequestSize(net Cmix, e2eGrp *cyclic.Group) int {
 	payloadSize := message.GetRequestPayloadSize(net.GetMaxMessageLength(),
 		e2eGrp.GetP().ByteLen())
 	requestSize := message.GetRequestContentsSize(payloadSize)
@@ -96,7 +96,7 @@ func GetMaxRequestSize(net CMix, e2eGrp *cyclic.Group) int {
 //
 // The network follower must be running and healthy to transmit.
 func TransmitRequest(recipient contact.Contact, tag string, payload []byte,
-	callback Response, param RequestParams, net CMix, rng csprng.Source,
+	callback Response, param RequestParams, net Cmix, rng csprng.Source,
 	e2eGrp *cyclic.Group) ([]id.Round, receptionID.EphemeralIdentity, error) {
 
 	if len(payload) > GetMaxRequestSize(net, e2eGrp) {
@@ -146,9 +146,9 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte,
 	// Encrypt and assemble payload
 	fp := singleUse.NewRequestFingerprint(recipient.DhPubKey)
 	key := singleUse.NewRequestKey(dhKey)
-
 	encryptedPayload := auth.Crypt(key, fp[:24], requestPayload.Marshal())
-	// Generate CMix message MAC
+
+	// Generate cMix message MAC
 	mac := singleUse.MakeMAC(key, encryptedPayload)
 
 	// Assemble the payload
@@ -188,7 +188,7 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte,
 		}
 
 		err = net.AddFingerprint(
-			sendingID.Source, processor.cy.GetFingerprint(), &processor)
+			sendingID.Source, processor.cy.getFingerprint(), &processor)
 		if err != nil {
 			return nil, receptionID.EphemeralIdentity{}, errors.Errorf(
 				errAddFingerprint, i, len(cyphers), tag, recipient, err)
@@ -205,8 +205,8 @@ func TransmitRequest(recipient contact.Contact, tag string, payload []byte,
 	}
 	param.CmixParam.Timeout = param.Timeout
 
-	rid, _, err := net.Send(recipient.ID, fp, svc,
-		request.Marshal(), mac, param.CmixParam)
+	rid, _, err := net.Send(
+		recipient.ID, fp, svc, request.Marshal(), mac, param.CmixParam)
 	if err != nil {
 		return nil, receptionID.EphemeralIdentity{},
 			errors.Errorf(errSendRequest, tag, recipient, err)
@@ -251,11 +251,12 @@ func generateDhKeys(grp *cyclic.Group, dhPubKey *cyclic.Int, rng io.Reader) (
 		return nil, nil, errors.Errorf(errGenerateInGroup, err)
 	}
 	privKey := grp.NewIntFromBytes(privKeyBytes)
+
 	// Generate public key and DH key
 	publicKey = grp.ExpG(privKey, grp.NewInt(1))
 	dhKey = grp.Exp(dhPubKey, privKey, grp.NewInt(1))
 
-	return
+	return dhKey, publicKey, nil
 }
 
 // makeIDs generates a new user ID and address ID with a start and end within
diff --git a/single/requestPartProcessor.go b/single/requestPartProcessor.go
index b6564d1e8712741b01a6ff675372455c6c5307a0..55575023ebcb62dad92c10d3a0e5045191c27ba5 100644
--- a/single/requestPartProcessor.go
+++ b/single/requestPartProcessor.go
@@ -31,7 +31,7 @@ type requestPartProcessor struct {
 func (rpp *requestPartProcessor) Process(msg format.Message,
 	_ receptionID.EphemeralIdentity, round rounds.Round) {
 
-	decrypted, err := rpp.cy.Decrypt(msg.GetContents(), msg.GetMac())
+	decrypted, err := rpp.cy.decrypt(msg.GetContents(), msg.GetMac())
 	if err != nil {
 		jww.ERROR.Printf("[SU] Failed to decrypt single-use request payload "+
 			"for %s to %s: %+v", rpp.tag, rpp.myId, err)
diff --git a/single/responseProcessor.go b/single/responseProcessor.go
index dac81e10cf77ad0bbaa98ad12953fa2746a05007..99b4be25a286f0fc501eb8999bd4006ecb8c7920 100644
--- a/single/responseProcessor.go
+++ b/single/responseProcessor.go
@@ -33,7 +33,7 @@ type responseProcessor struct {
 func (rsp *responseProcessor) Process(ecrMsg format.Message,
 	receptionID receptionID.EphemeralIdentity, round rounds.Round) {
 
-	decrypted, err := rsp.cy.Decrypt(ecrMsg.GetContents(), ecrMsg.GetMac())
+	decrypted, err := rsp.cy.decrypt(ecrMsg.GetContents(), ecrMsg.GetMac())
 	if err != nil {
 		jww.ERROR.Printf("[SU] Failed to decrypt single-use response "+
 			"payload for %s to %s: %+v",
diff --git a/ud/interfaces.go b/ud/interfaces.go
index 33db0140789e9185b671f7dceee8f05dcc177098..6042ae755fd34b770a3dcf503768d76346ebf50c 100644
--- a/ud/interfaces.go
+++ b/ud/interfaces.go
@@ -13,7 +13,7 @@ import (
 type CMix interface {
 	// CMix is passed down into the single use package,
 	// and thus has to adhere to the sub-interface defined in that package
-	single.CMix
+	single.Cmix
 }
 
 // E2E is a sub-interface of the e2e.Handler. It contains the methods