diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5d5bbe80f56f1e601b59d1c2d15b1ee269e36c0a..4a344591818545dcf0ebccf75d15fe54874d8ee4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -90,7 +90,7 @@ bindings-ios:
     - go get golang.org/x/mobile/bind
     - go install golang.org/x/mobile/cmd/gomobile@latest
     - gomobile init
-    - gomobile bind -target ios gitlab.com/elixxir/client/bindings
+    - gomobile bind -target ios,iossimulator,macos gitlab.com/elixxir/client/bindings
     - ls
     - zip -r iOS.zip Bindings.xcframework
   artifacts:
diff --git a/auth/request.go b/auth/request.go
index d37385815b55d2f6b1e87fabc5e1f167bb704360..31a064f8f0bf14e6f508b349ee508460a5e92ae3 100644
--- a/auth/request.go
+++ b/auth/request.go
@@ -82,6 +82,11 @@ func (s *state) request(partner contact.Contact, myfacts fact.FactList,
 	historicalDHPub := diffieHellman.GeneratePublicKey(historicalDHPriv,
 		dhGrp)
 
+	if !dhGrp.Inside(partner.DhPubKey.GetLargeInt()) {
+		return 0, errors.Errorf("partner's DH public key is not in the E2E "+
+			"group; E2E group fingerprint is %d and DH key has %d",
+			dhGrp.GetFingerprint(), partner.DhPubKey.GetGroupFingerprint())
+	}
 	ownership := cAuth.MakeOwnershipProof(historicalDHPriv,
 		partner.DhPubKey, dhGrp)
 	confirmFp := cAuth.MakeOwnershipProofFP(ownership)
diff --git a/auth/utils_test.go b/auth/utils_test.go
index 5f27a218e0651736ace0ca165ef44e0ef581b05d..4136860e379218229be76f900d4484642741c919 100644
--- a/auth/utils_test.go
+++ b/auth/utils_test.go
@@ -61,9 +61,8 @@ func (m mockE2eHandler) StartProcesses() (stoppable.Stoppable, error) {
 }
 
 func (m mockE2eHandler) SendE2E(mt catalog.MessageType, recipient *id.ID,
-	payload []byte, params e2e.Params) ([]id.Round, cryptoE2e.MessageID,
-	time.Time, error) {
-	return nil, cryptoE2e.MessageID{}, time.Time{}, nil
+	payload []byte, params e2e.Params) (cryptoE2e.SendReport, error) {
+	return cryptoE2e.SendReport{}, nil
 }
 
 func (m mockE2eHandler) RegisterListener(senderID *id.ID,
diff --git a/bindings/connect.go b/bindings/connect.go
index 912ee6ffa9744e12a5b44ae626a198cb6e9bb36e..927d162aa921fae460b9849e8259c77f56afbb99 100644
--- a/bindings/connect.go
+++ b/bindings/connect.go
@@ -85,7 +85,7 @@ func (c *Cmix) Connect(e2eId int, recipientContact, e2eParamsJSON []byte) (
 //  - []byte - the JSON marshalled bytes of the E2ESendReport object, which can
 //    be passed into Cmix.WaitForRoundResult to see if the send succeeded.
 func (c *Connection) SendE2E(mt int, payload []byte) ([]byte, error) {
-	rounds, mid, ts, err := c.connection.SendE2E(catalog.MessageType(mt), payload,
+	sendReport, err := c.connection.SendE2E(catalog.MessageType(mt), payload,
 		c.params.Base)
 
 	if err != nil {
@@ -93,9 +93,10 @@ func (c *Connection) SendE2E(mt int, payload []byte) ([]byte, error) {
 	}
 
 	sr := E2ESendReport{
-		RoundsList: makeRoundsList(rounds...),
-		MessageID:  mid.Marshal(),
-		Timestamp:  ts.UnixNano(),
+		RoundsList: makeRoundsList(sendReport.RoundList...),
+		MessageID:  sendReport.MessageId.Marshal(),
+		Timestamp:  sendReport.SentTime.UnixNano(),
+		KeyResidue: sendReport.KeyResidue.Marshal(),
 	}
 
 	return json.Marshal(&sr)
diff --git a/bindings/e2eHandler.go b/bindings/e2eHandler.go
index b71d6646d85eec5cea9a4450979d75003e20b9d2..95256801441ed19654d1d516e824149c3ee040ea 100644
--- a/bindings/e2eHandler.go
+++ b/bindings/e2eHandler.go
@@ -10,12 +10,12 @@ package bindings
 import (
 	"encoding/json"
 	"fmt"
+
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/catalog"
 	"gitlab.com/elixxir/client/cmix/identity/receptionID"
 	"gitlab.com/elixxir/client/cmix/rounds"
-	"gitlab.com/elixxir/client/e2e"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/primitives/id"
 )
@@ -24,15 +24,17 @@ import (
 // SendE2E.
 //
 // Example E2ESendReport:
-//  {
-//   "Rounds":[1,5,9],
-//   "MessageID":"51Yy47uZbP0o2Y9B/kkreDLTB6opUol3M3mYiY2dcdQ=",
-//   "Timestamp":1653582683183384000
-//  }
+//{
+//"Rounds": [ 1, 4, 9],
+//"MessageID": "iM34yCIr4Je8ZIzL9iAAG1UWAeDiHybxMTioMAaezvs=",
+//"Timestamp": 1661532254302612000,
+//"KeyResidue": "9q2/A69EAuFM1hFAT7Bzy5uGOQ4T6bPFF72h5PlgCWE="
+//}
 type E2ESendReport struct {
 	RoundsList
-	MessageID []byte
-	Timestamp int64
+	MessageID  []byte
+	Timestamp  int64
+	KeyResidue []byte
 }
 
 // GetReceptionID returns the marshalled default IDs.
@@ -121,28 +123,33 @@ func (e *E2e) RemoveService(tag string) error {
 //  - []byte - the JSON marshalled bytes of the E2ESendReport object, which can
 //    be passed into Cmix.WaitForRoundResult to see if the send succeeded.
 func (e *E2e) SendE2E(messageType int, recipientId, payload,
-	e2eParams []byte) ([]byte, error) {
-	// Note that specifically these are the Base params from xxdk.E2EParams
-	params := e2e.GetDefaultParams()
-	err := params.UnmarshalJSON(e2eParams)
+	e2eParamsJSON []byte) ([]byte, error) {
+	if len(e2eParamsJSON) == 0 {
+		jww.WARN.Printf("e2e params not specified, using defaults...")
+		e2eParamsJSON = GetDefaultE2EParams()
+	}
+	params, err := parseE2EParams(e2eParamsJSON)
 	if err != nil {
 		return nil, err
 	}
+
 	recipient, err := id.Unmarshal(recipientId)
 	if err != nil {
 		return nil, err
 	}
 
-	roundIds, messageId, ts, err := e.api.GetE2E().SendE2E(
-		catalog.MessageType(messageType), recipient, payload, params)
+	sendReport, err := e.api.GetE2E().SendE2E(
+		catalog.MessageType(messageType), recipient, payload,
+		params.Base)
 	if err != nil {
 		return nil, err
 	}
 
 	result := E2ESendReport{
-		RoundsList: makeRoundsList(roundIds...),
-		MessageID:  messageId.Marshal(),
-		Timestamp:  ts.UnixNano(),
+		RoundsList: makeRoundsList(sendReport.RoundList...),
+		MessageID:  sendReport.MessageId.Marshal(),
+		Timestamp:  sendReport.SentTime.UnixNano(),
+		KeyResidue: sendReport.KeyResidue.Marshal(),
 	}
 	return json.Marshal(result)
 }
diff --git a/bindings/follow.go b/bindings/follow.go
index 6267a34125388a681827437d7a1e1cea1aa1c8dd..199990bd69ee51e2b377d501ec537399dca89eab 100644
--- a/bindings/follow.go
+++ b/bindings/follow.go
@@ -10,6 +10,7 @@ package bindings
 import (
 	"encoding/json"
 	"fmt"
+	"gitlab.com/elixxir/client/cmix/message"
 	"time"
 
 	"github.com/pkg/errors"
@@ -168,3 +169,50 @@ func (c *Cmix) RegisterClientErrorCallback(clientError ClientError) {
 		}
 	}()
 }
+
+// TrackServicesCallback is the callback for Cmix.TrackServices.
+// This will pass to the user a JSON-marshalled list of backend services.
+// If there was an error retrieving or marshalling the service list,
+// there is an error for the second parameter which will be non-null.
+//
+// Example JSON:
+//
+// [
+//  {
+//    "Id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
+//    "Services": [
+//      {
+//        "Identifier": null,
+//        "Tag": "test",
+//        "Metadata": null
+//      }
+//    ]
+//  },
+//  {
+//    "Id": "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
+//    "Services": [
+//      {
+//        "Identifier": null,
+//        "Tag": "test",
+//        "Metadata": null
+//      }
+//    ]
+//  },
+//]
+type TrackServicesCallback interface {
+	Callback(marshalData []byte, err error)
+}
+
+// TrackServices will return via a callback the list of services the
+// backend keeps track of, which is formally referred to as a
+// [message.ServiceList]. This may be passed into other bindings call which
+// may need context on the available services for this client.
+//
+// Parameters:
+//   - cb - A TrackServicesCallback, which will be passed the marshalled
+//     message.ServiceList.
+func (c *Cmix) TrackServices(cb TrackServicesCallback) {
+	c.api.GetCmix().TrackServices(func(list message.ServiceList) {
+		cb.Callback(json.Marshal(list))
+	})
+}
diff --git a/bindings/group.go b/bindings/group.go
index 709a969091b8bbddd5a2d242e8ddab0e51312e9a..4d8af5a16450d7c451cec10f16600383b5250500 100644
--- a/bindings/group.go
+++ b/bindings/group.go
@@ -46,15 +46,15 @@ func (ut *groupTracker) make(g gs.Group) *Group {
 	ut.mux.Lock()
 	defer ut.mux.Unlock()
 
-	id := ut.count
+	utID := ut.count
 	ut.count++
 
-	ut.tracked[id] = &Group{
+	ut.tracked[utID] = &Group{
 		g:  g,
-		id: id,
+		id: utID,
 	}
 
-	return ut.tracked[id]
+	return ut.tracked[utID]
 }
 
 // get a Group from the groupChatTracker given its ID.
@@ -148,7 +148,7 @@ func (g *GroupChat) MakeGroup(
 	}
 
 	// Construct group
-	grp, rounds, status, err := g.m.MakeGroup(members, name, message)
+	grp, roundIDs, status, err := g.m.MakeGroup(members, name, message)
 	if err != nil {
 		return nil, err
 	}
@@ -156,7 +156,7 @@ func (g *GroupChat) MakeGroup(
 	// Construct the group report
 	report := GroupReport{
 		Id:         grp.ID.Bytes(),
-		RoundsList: makeRoundsList(rounds...),
+		RoundsList: makeRoundsList(roundIDs...),
 		Status:     int(status),
 	}
 
@@ -365,7 +365,26 @@ func (g *Group) GetCreatedMS() int64 {
 // All subsequent members are ordered by their ID.
 //
 // Returns:
-//  - []byte - a JSON marshalled version of the member list.
+//  - []byte - JSON marshalled [group.Membership], which is an array of
+//    [group.Member].
+//
+// Example JSON [group.Membership] return:
+//  [
+//    {
+//      "ID": "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID",
+//      "DhKey": {
+//        "Value": 3534334367214237261,
+//        "Fingerprint": 16801541511233098363
+//      }
+//    },
+//    {
+//      "ID": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
+//      "DhKey": {
+//        "Value": 7497468244883513247,
+//        "Fingerprint": 16801541511233098363
+//      }
+//    }
+//  ]
 func (g *Group) GetMembership() ([]byte, error) {
 	return json.Marshal(g.g.Members)
 }
diff --git a/bindings/json_test.go b/bindings/json_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b42bc6806ab14d52f7a731513e72d4234164d084
--- /dev/null
+++ b/bindings/json_test.go
@@ -0,0 +1,33 @@
+package bindings
+
+import (
+	"encoding/json"
+	"gitlab.com/elixxir/crypto/e2e"
+	"math/rand"
+	"testing"
+	"time"
+)
+
+func TestName(t *testing.T) {
+	rl := []uint64{1, 4, 9}
+	prng := rand.New(rand.NewSource(42))
+	rfp := make([]byte, 32)
+	prng.Read(rfp)
+	mid := e2e.NewMessageID(rfp, prng.Uint64())
+
+	randData := make([]byte, 32)
+	prng.Read(randData)
+	k := e2e.Key{}
+	copy(k[:], randData)
+	kr := e2e.NewKeyResidue(k)
+
+	report := E2ESendReport{
+		RoundsList: RoundsList{rl},
+		MessageID:  mid.Marshal(),
+		Timestamp:  time.Now().UnixNano(),
+		KeyResidue: kr.Marshal(),
+	}
+
+	marshal, _ := json.Marshal(report)
+	t.Logf("%s", marshal)
+}
diff --git a/bindings/notifications.go b/bindings/notifications.go
index bbc79ca79c028ceac33a5ba1e972119f904aa4ba..e028ae0ce7a2186a2afd67873e668e7109a33022 100644
--- a/bindings/notifications.go
+++ b/bindings/notifications.go
@@ -7,67 +7,141 @@
 
 package bindings
 
-// FIXME: This is the old NotificationsForMe code that needs to be fixed
-/*
-type NotificationForMeReport struct {
-	ForMe  bool
-	Type   string
+import (
+	"encoding/json"
+	"gitlab.com/elixxir/client/cmix/message"
+	"gitlab.com/elixxir/primitives/notifications"
+)
+
+// NotificationReports is a list of NotificationReport's. This will be returned
+// via GetNotificationsReport as a JSON marshalled byte data.
+//
+// Example JSON:
+//
+// [
+//  {
+//    "ForMe": true,
+//    "Type": "e2e",
+//    "Source": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"
+//  },
+//  {
+//    "ForMe": true,
+//    "Type": "e2e",
+//    "Source": "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"
+//  },
+//  {
+//    "ForMe": true,
+//    "Type": "e2e",
+//    "Source": "AAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"
+//  }
+//]
+type NotificationReports []NotificationReport
+
+// NotificationReport is the bindings' representation for notifications for
+// this user.
+//
+// Example NotificationReport JSON:
+//
+// {
+//  "ForMe": true,
+//  "Type": "e2e",
+//  "Source": "dGVzdGVyMTIzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+//}
+//
+// Given the Type, the Source value will have specific contextual meanings.
+// Below is a table that will define the contextual meaning of the Source field
+// given all possible Type fields.
+//
+//      TYPE     |     SOURCE         |    DESCRIPTION
+//     ________________________________________________________________________________________
+//     "default" |  recipient user ID |  A message with no association.
+//	   "request" |  sender user ID    |  A channel request has been received, from Source.
+//     "reset"   |  sender user ID    |  A channel reset has been received.
+//     "confirm" |  sender user ID    |  A channel request has been accepted.
+//     "silent"  |  sender user ID    |  A message where the user should not be notified.
+//     "e2e"     |  sender user ID    |  A reception of an E2E message.
+//     "group"   |  group ID          |  A reception of a group chat message.
+//     "endFT"   |  sender user ID    |  The last message sent confirming end of file transfer.
+//     "groupRQ" |  sender user ID    |  A request from Source to join a group chat.
+//  todo iterate over this docstring, ensure descriptions/sources are
+//    still accurate (they are from the old implementation
+type NotificationReport struct {
+	// ForMe determines whether this value is for the user. If it is
+	// false, this report may be ignored.
+	ForMe bool
+	// Type is the type of notification. The list can be seen
+	Type string
+	// Source is the source of the notification.
 	Source []byte
 }
 
-type ManyNotificationForMeReport struct {
-	Many []*NotificationForMeReport
-}
+// GetNotificationsReport parses the received notification data to determine which
+// notifications are for this user. // This returns the JSON-marshalled
+// NotificationReports.
+//
+// Parameters:
+//  - e2eID - e2e object ID in the tracker
+//  - notificationCSV - the notification data received from the
+//    notifications' server.
+//  - marshalledServices - the JSON-marshalled list of services the backend
+//    keeps track of. Refer to Cmix.TrackServices for information about this.
+//
+// Returns:
+//  - []byte - A JSON marshalled NotificationReports. Some NotificationReport's
+//    within in this structure may have their NotificationReport.ForMe
+//    set to false. These may be ignored.
+func GetNotificationsReport(e2eId int, notificationCSV string,
+	marshalledServices []byte) ([]byte, error) {
+	// Retrieve user
+	user, err := e2eTrackerSingleton.get(e2eId)
+	if err != nil {
+		return nil, err
+	}
 
-// NotificationsForMe Check if a notification received is for me
-// It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-// a Type, and a source. These are as follows:
-//	TYPE       	SOURCE				DESCRIPTION
-// 	"default"	recipient user ID	A message with no association
-//	"request"	sender user ID		A channel request has been received
-//	"reset"	    sender user ID		A channel reset has been received
-//	"confirm"	sender user ID		A channel request has been accepted
-//	"silent"	sender user ID		A message which should not be notified on
-//	"e2e"		sender user ID		reception of an E2E message
-//	"group"		group ID			reception of a group chat message
-//  "endFT"     sender user ID		Last message sent confirming end of file transfer
-//  "groupRQ"   sender user ID		Request from sender to join a group chat
-func NotificationsForMe(notifCSV, preimages string) (*ManyNotificationForMeReport, error) {
-	// Handle deserialization of preimages
-	var preimageList []edge.Preimage
-	if err := json.Unmarshal([]byte(preimages), &preimageList); err != nil {
-		return nil, errors.WithMessagef(err, "Failed to unmarshal the " +
-			"preimages list, cannot check if notification is for me")
+	serviceList := message.ServiceList{}
+	err = json.Unmarshal(marshalledServices, &serviceList)
+	if err != nil {
+		return nil, err
 	}
 
-	list, err := notifications.DecodeNotificationsCSV(notifCSV)
+	// Retrieve the services for this user
+	services := serviceList[*user.api.GetReceptionIdentity().ID]
+
+	// Decode notifications' server data
+	notificationList, err := notifications.DecodeNotificationsCSV(notificationCSV)
 	if err != nil {
 		return nil, err
 	}
 
-	notifList := make([]*NotificationForMeReport, len(list))
+	// Construct  a report list
+	reportList := make([]*NotificationReport, len(notificationList))
 
-	for i, notifData := range list {
-		notifList[i] = &NotificationForMeReport{
-			ForMe:  false,
-			Type:   "",
-			Source: nil,
-		}
-		// check if any preimages match with the passed in data
-		for _, preimage := range preimageList {
-			if fingerprint.CheckIdentityFpFromMessageHash(notifData.IdentityFP, notifData.MessageHash, preimage.Data) {
-				notifList[i] = &NotificationForMeReport{
+	// Iterate over data provided by server
+	for i := range notificationList {
+		notifData := notificationList[i]
+
+		// Iterate over all services
+		for j := range services {
+			// Pull data from services and from notification data
+			service := services[j]
+			messageHash := notifData.MessageHash
+			hash := service.HashFromMessageHash(notifData.MessageHash)
+
+			// Check if this notification data is recognized by
+			// this service, ie "ForMe"
+			if service.ForMeFromMessageHash(messageHash, hash) {
+				// Fill report list with service data
+				reportList[i] = &NotificationReport{
 					ForMe:  true,
-					Type:   preimage.Type,
-					Source: preimage.Source,
+					Type:   service.Tag,
+					Source: service.Identifier,
 				}
-				break
 			}
 		}
 	}
 
-	return &ManyNotificationForMeReport{notifList}, nil
-}*/
+	return json.Marshal(reportList)
+}
 
 // RegisterForNotifications allows a client to register for push notifications.
 // The token is a firebase messaging token.
diff --git a/bindings/notifications_test.go b/bindings/notifications_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..390062f08456961cb3e18eb84675e64f18e867b8
--- /dev/null
+++ b/bindings/notifications_test.go
@@ -0,0 +1,28 @@
+package bindings
+
+import (
+	"encoding/json"
+	"fmt"
+	"gitlab.com/elixxir/client/e2e/ratchet"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+func TestNotificationReport(t *testing.T) {
+	reports := []NotificationReport{}
+
+	for i := 0; i < 3; i++ {
+		nr := NotificationReport{
+			ForMe:  true,
+			Type:   ratchet.E2e,
+			Source: id.NewIdFromUInt(uint64(i), id.User, t).Bytes(),
+		}
+
+		reports = append(reports, nr)
+	}
+
+	nrs := NotificationReports(reports)
+
+	marshal, _ := json.Marshal(nrs)
+	fmt.Printf("%s\n", marshal)
+}
diff --git a/bindings/single.go b/bindings/single.go
index ca0e855cb2bdd53baa1da576ec198f2db5587976..a3bacbce28c6db129ae233ea0c684ad9b90a9817 100644
--- a/bindings/single.go
+++ b/bindings/single.go
@@ -166,7 +166,7 @@ type Stopper interface {
 // Parameters:
 //  - callbackReport - the JSON marshalled bytes of the SingleUseCallbackReport
 //    object, which can be passed into Cmix.WaitForRoundResult to see if the
-//    send succeeded.
+//    send operation succeeded.
 type SingleUseCallback interface {
 	Callback(callbackReport []byte, err error)
 }
@@ -177,7 +177,7 @@ type SingleUseCallback interface {
 // Parameters:
 //  - callbackReport - the JSON marshalled bytes of the SingleUseResponseReport
 //    object, which can be passed into Cmix.WaitForRoundResult to see if the
-//    send succeeded.
+//    send operation succeeded.
 type SingleUseResponse interface {
 	Callback(responseReport []byte, err error)
 }
diff --git a/bindings/ud.go b/bindings/ud.go
index 217fb964b7f4b5d313f323fca5df46d95cf9d577..3a3f21a0d43a4905ba5645d40cd38f4e82ed4854 100644
--- a/bindings/ud.go
+++ b/bindings/ud.go
@@ -429,8 +429,15 @@ func LookupUD(e2eID int, udContact []byte, cb UdLookupCallback,
 //
 // Parameters:
 //  - contactListJSON - the JSON marshalled bytes of []contact.Contact, or nil
-//    if an error occurs
-//  - err - any errors that occurred in the search
+//    if an error occurs.
+//
+//   JSON Example:
+//   {
+//  	"<xxc(2)F8dL9EC6gy+RMJuk3R+Au6eGExo02Wfio5cacjBcJRwDEgB7Ugdw/BAr6RkCABkWAFV1c2VybmFtZTA7c4LzV05sG+DMt+rFB0NIJg==xxc>",
+//  	"<xxc(2)eMhAi/pYkW5jCmvKE5ZaTglQb+fTo1D8NxVitr5CCFADEgB7Ugdw/BAr6RoCABkWAFV1c2VybmFtZTE7fElAa7z3IcrYrrkwNjMS2w==xxc>",
+//  	"<xxc(2)d7RJTu61Vy1lDThDMn8rYIiKSe1uXA/RCvvcIhq5Yg4DEgB7Ugdw/BAr6RsCABkWAFV1c2VybmFtZTI7N3XWrxIUpR29atpFMkcR6A==xxc>"
+//	}
+//  - err - any errors that occurred in the search.
 type UdSearchCallback interface {
 	Callback(contactListJSON []byte, err error)
 }
@@ -451,7 +458,7 @@ type UdSearchCallback interface {
 // Returns:
 //  - []byte - the JSON marshalled bytes of the SingleUseSendReport object,
 //    which can be passed into Cmix.WaitForRoundResult to see if the send
-//    succeeded.
+//    operation succeeded.
 func SearchUD(e2eID int, udContact []byte, cb UdSearchCallback,
 	factListJSON, singleRequestParamsJSON []byte) ([]byte, error) {
 
@@ -479,7 +486,20 @@ func SearchUD(e2eID int, udContact []byte, cb UdSearchCallback,
 	}
 
 	callback := func(contactList []contact.Contact, err error) {
-		contactListJSON, err2 := json.Marshal(contactList)
+		marshaledContactList := make([][]byte, 0)
+		// fixme: it may be wiser to change this callback interface
+		//   to simply do the work below when parsing the response from UD.
+		//   that would change ud/search.go in two places:
+		//    - searchCallback
+		//    - parseContacts
+		//  I avoid doing that as it changes interfaces w/o approval
+		for i := range contactList {
+			con := contactList[i]
+			marshaledContactList = append(
+				marshaledContactList, con.Marshal())
+		}
+
+		contactListJSON, err2 := json.Marshal(marshaledContactList)
 		if err2 != nil {
 			jww.FATAL.Panicf(
 				"Failed to marshal list of contact.Contact: %+v", err2)
diff --git a/cmd/connect.go b/cmd/connect.go
index e8697dc60b8f0b5181df175274e12e881ce12b78..7b31c2cd8be6557f64f214ddf781c375fe6ce319 100644
--- a/cmd/connect.go
+++ b/cmd/connect.go
@@ -486,7 +486,7 @@ func miscConnectionFunctions(user *xxdk.E2e, conn connect.Connection) {
 			conn.GetPartner().PartnerId())
 		payload := []byte(msgBody)
 		for {
-			roundIDs, _, _, err := conn.SendE2E(catalog.XxMessage, payload,
+			sendReport, err := conn.SendE2E(catalog.XxMessage, payload,
 				paramsE2E)
 			if err != nil {
 				jww.FATAL.Panicf("[CONN] Failed to send E2E message: %v", err)
@@ -494,7 +494,7 @@ func miscConnectionFunctions(user *xxdk.E2e, conn connect.Connection) {
 
 			// Verify message sends were successful when verifySendFlag is present
 			if viper.GetBool(verifySendFlag) {
-				if !verifySendSuccess(user, paramsE2E, roundIDs,
+				if !verifySendSuccess(user, paramsE2E, sendReport.RoundList,
 					conn.GetPartner().PartnerId(), payload) {
 					continue
 				}
diff --git a/cmd/flags.go b/cmd/flags.go
index 93b5d505d77f4846f80d1cda6b32bfa29a0ad3cf..edd40259a56eb227c267811c125ffab746ec0f67 100644
--- a/cmd/flags.go
+++ b/cmd/flags.go
@@ -80,7 +80,9 @@ const (
 	// Misc
 	sendIdFlag       = "sendid"
 	profileCpuFlag   = "profile-cpu"
+	profileMemFlag   = "profile-mem"
 	userIdPrefixFlag = "userid-prefix"
+	legacyFlag       = "legacy"
 
 	///////////////// Broadcast subcommand flags //////////////////////////////
 	broadcastNameFlag        = "name"
diff --git a/cmd/init.go b/cmd/init.go
index e78ef22bb78387d64210e9d7365bc76fa765fd94..00294f2c4a79d6bd38ce9c330df5b86466a608c3 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -45,16 +45,27 @@ var initCmd = &cobra.Command{
 			jww.FATAL.Panicf("%+v", err)
 		}
 
-		identity, err := xxdk.MakeReceptionIdentity(net)
+		// Generate identity
+		var identity xxdk.ReceptionIdentity
+		if viper.GetBool(legacyFlag) {
+			identity, err = xxdk.MakeLegacyReceptionIdentity(net)
+		} else {
+			identity, err = xxdk.MakeReceptionIdentity(net)
+
+		}
+
+		// Panic if conditional branch fails
 		if err != nil {
 			jww.FATAL.Panicf("%+v", err)
 		}
 
+		// Store identity
 		err = xxdk.StoreReceptionIdentity(identityStorageKey, identity, net)
 		if err != nil {
 			jww.FATAL.Panicf("%+v", err)
 		}
 
+		// Write contact to file
 		jww.INFO.Printf("User: %s", identity.ID)
 		writeContact(identity.GetContact())
 
@@ -68,6 +79,11 @@ func init() {
 		"Desired prefix of userID to brute force when running init command. Prepend (?i) for case-insensitive. Only Base64 characters are valid.")
 	bindFlagHelper(userIdPrefixFlag, initCmd)
 
+	initCmd.Flags().BoolP(legacyFlag, "", false,
+		"Generates a legacy identity if set. "+
+			"If this flag is absent, a standard identity will be generated.")
+	bindFlagHelper(legacyFlag, initCmd)
+
 	rootCmd.AddCommand(initCmd)
 }
 
diff --git a/cmd/root.go b/cmd/root.go
index 837b68e1af7d13f8476844c3bb0f525be4bcbd63..c3366aaf76240b114c3e6e4b0d0899058a4fbe91 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -14,10 +14,13 @@ import (
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"io/ioutil"
 	"log"
 	"os"
-	"runtime/pprof"
+
+	"github.com/pkg/profile"
+
 	"strconv"
 	"strings"
 	"sync"
@@ -58,13 +61,17 @@ var rootCmd = &cobra.Command{
 	Short: "Runs a client for cMix anonymous communication platform",
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
-		profileOut := viper.GetString(profileCpuFlag)
-		if profileOut != "" {
-			f, err := os.Create(profileOut)
-			if err != nil {
-				jww.FATAL.Panicf("%+v", err)
-			}
-			pprof.StartCPUProfile(f)
+		cpuProfileOut := viper.GetString(profileCpuFlag)
+		if cpuProfileOut != "" {
+			defer profile.Start(profile.CPUProfile,
+				profile.ProfilePath(cpuProfileOut),
+				profile.NoShutdownHook).Stop()
+		}
+		memProfileOut := viper.GetString(profileMemFlag)
+		if memProfileOut != "" {
+			defer profile.Start(profile.MemProfile,
+				profile.ProfilePath(memProfileOut),
+				profile.NoShutdownHook).Stop()
 		}
 
 		cmixParams, e2eParams := initParams()
@@ -288,8 +295,10 @@ var rootCmd = &cobra.Command{
 								e2eParams.Base)
 						} else {
 							e2eParams.Base.DebugTag = "cmd.E2E"
-							roundIDs, _, _, err = user.GetE2E().SendE2E(mt,
+							var sendReport cryptoE2e.SendReport
+							sendReport, err = user.GetE2E().SendE2E(mt,
 								recipient, payload, e2eParams.Base)
+							roundIDs = sendReport.RoundList
 						}
 						if err != nil {
 							jww.FATAL.Panicf("%+v", err)
@@ -382,9 +391,6 @@ var rootCmd = &cobra.Command{
 				"Failed to cleanly close threads: %+v\n",
 				err)
 		}
-		if profileOut != "" {
-			pprof.StopCPUProfile()
-		}
 		jww.INFO.Printf("Client exiting!")
 	},
 }
@@ -1101,6 +1107,10 @@ func init() {
 		"Enable cpu profiling to this file")
 	viper.BindPFlag(profileCpuFlag, rootCmd.Flags().Lookup(profileCpuFlag))
 
+	rootCmd.Flags().String(profileMemFlag, "",
+		"Enable memory profiling to this file")
+	viper.BindPFlag(profileMemFlag, rootCmd.Flags().Lookup(profileMemFlag))
+
 	// Proto user flags
 	rootCmd.Flags().String(protoUserPathFlag, "",
 		"Path to proto user JSON file containing cryptographic primitives "+
diff --git a/cmix/client.go b/cmix/client.go
index 996cacdb46016a7675cde906cbcd9c66dc68c766..fb326f72ac1fcdc8c75cf7e61645bbca8f4fc061 100644
--- a/cmix/client.go
+++ b/cmix/client.go
@@ -246,6 +246,9 @@ func (c *client) Follow(report ClientErrorReport) (stoppable.Stoppable, error) {
 	// Start the processes for the identity handler
 	multi.Add(c.Tracker.StartProcesses())
 
+	//Start the critical processing thread
+	multi.Add(c.crit.startProcessies())
+
 	return multi, nil
 }
 
diff --git a/cmix/critical.go b/cmix/critical.go
index 03ebfc7b56429b0fd0e8f588fa5ca2fe2e809491..719e6d9f0eff0774bdf083f01f07034a58ab6127 100644
--- a/cmix/critical.go
+++ b/cmix/critical.go
@@ -1,6 +1,8 @@
 package cmix
 
 import (
+	"time"
+
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/cmix/health"
 	"gitlab.com/elixxir/client/stoppable"
@@ -10,7 +12,6 @@ import (
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
-	"time"
 )
 
 const criticalRawMessagesKey = "RawCriticalMessages"
@@ -58,6 +59,12 @@ func newCritical(kv *versioned.KV, hm health.Monitor,
 	return c
 }
 
+func (c *critical) startProcessies() *stoppable.Single {
+	stop := stoppable.NewSingle("criticalStopper")
+	go c.runCriticalMessages(stop)
+	return stop
+}
+
 func (c *critical) runCriticalMessages(stop *stoppable.Single) {
 	for {
 		select {
diff --git a/cmix/identity/tracker.go b/cmix/identity/tracker.go
index 961aac77627f0f5ef15ed35b839c2c5eaafa9b4b..8d9f8a6d2a168d27147f9fe280f2d777e0b9935c 100644
--- a/cmix/identity/tracker.go
+++ b/cmix/identity/tracker.go
@@ -55,7 +55,7 @@ type Tracker interface {
 }
 
 type manager struct {
-	tracked        []TrackedID
+	tracked        []*TrackedID
 	ephemeral      *receptionID.Store
 	session        storage.Session
 	newIdentity    chan TrackedID
@@ -76,7 +76,7 @@ type TrackedID struct {
 func NewOrLoadTracker(session storage.Session, addrSpace address.Space) *manager {
 	// Initialization
 	t := &manager{
-		tracked:        make([]TrackedID, 0),
+		tracked:        make([]*TrackedID, 0),
 		session:        session,
 		newIdentity:    make(chan TrackedID, trackedIDChanSize),
 		deleteIdentity: make(chan *id.ID, deleteIDChanSize),
@@ -92,7 +92,7 @@ func NewOrLoadTracker(session storage.Session, addrSpace address.Space) *manager
 			jww.WARN.Printf("No tracked identities found, creating a new " +
 				"tracked identity from legacy stored timestamp.")
 
-			t.tracked = append(t.tracked, TrackedID{
+			t.tracked = append(t.tracked, &TrackedID{
 				// Make the next generation now so a generation triggers on
 				// first run
 				NextGeneration: netTime.Now(),
@@ -155,7 +155,7 @@ func (t *manager) GetIdentity(get *id.ID) (TrackedID, error) {
 	defer t.mux.Unlock()
 	for i := range t.tracked {
 		if get.Cmp(t.tracked[i].Source) {
-			return t.tracked[i], nil
+			return *t.tracked[i], nil
 		}
 	}
 	return TrackedID{}, errors.Errorf("could not find id %s", get)
@@ -201,7 +201,7 @@ func (t *manager) track(stop *stoppable.Single) {
 			if !isOld {
 				jww.DEBUG.Printf("Tracking new identity %s", newIdentity.Source)
 				// Otherwise, add it to the list and run
-				t.tracked = append(t.tracked, newIdentity)
+				t.tracked = append(t.tracked, &newIdentity)
 			}
 
 			t.save()
@@ -236,7 +236,8 @@ func (t *manager) processIdentities(addressSize uint8) time.Time {
 	nextEvent := netTime.Now().Add(time.Duration(ephemeral.Period))
 
 	// Loop through every tracked ID and see if any operations are needed
-	for i, inQuestion := range t.tracked {
+	for i := range t.tracked {
+		inQuestion := t.tracked[i]
 		// Generate new ephemeral if is time for it
 		if netTime.Now().After(inQuestion.NextGeneration) {
 			nextGeneration := t.generateIdentitiesOverRange(inQuestion, addressSize)
@@ -267,7 +268,7 @@ func (t *manager) processIdentities(addressSize uint8) time.Time {
 
 	// Process any deletions
 	if len(toRemove) > 0 {
-		newTracked := make([]TrackedID, 0, len(t.tracked))
+		newTracked := make([]*TrackedID, 0, len(t.tracked))
 		for i := range t.tracked {
 			if _, remove := toRemove[i]; !remove {
 				newTracked = append(newTracked, t.tracked[i])
@@ -305,7 +306,7 @@ func unmarshalTimestamp(lastTimestampObj *versioned.Object) (time.Time, error) {
 
 // generateIdentitiesOverRange generates and adds all not yet existing ephemeral Ids
 // and returns the timestamp of the next generation for the given TrackedID
-func (t *manager) generateIdentitiesOverRange(inQuestion TrackedID,
+func (t *manager) generateIdentitiesOverRange(inQuestion *TrackedID,
 	addressSize uint8) time.Time {
 	// Ensure that ephemeral IDs will not be generated after the
 	// identity is invalid
@@ -374,7 +375,7 @@ func (t *manager) save() {
 
 	for i := range t.tracked {
 		if t.tracked[i].Persistent {
-			persistent = append(persistent, t.tracked[i])
+			persistent = append(persistent, *t.tracked[i])
 		}
 	}
 
diff --git a/cmix/identity/tracker_test.go b/cmix/identity/tracker_test.go
index d16f64b5526f30e2d9719974ad1f82560b7f6b1f..dc9a895764c996abf1cf947c92139f2b7a23c71e 100644
--- a/cmix/identity/tracker_test.go
+++ b/cmix/identity/tracker_test.go
@@ -67,7 +67,7 @@ func TestManager_processIdentities(t *testing.T) {
 	addrSpace.UpdateAddressSpace(18)
 	session := storage.InitTestingSession(t)
 	m := &manager{
-		tracked:        make([]TrackedID, 0),
+		tracked:        make([]*TrackedID, 0),
 		session:        session,
 		newIdentity:    make(chan TrackedID, trackedIDChanSize),
 		deleteIdentity: make(chan *id.ID, deleteIDChanSize),
@@ -79,7 +79,7 @@ func TestManager_processIdentities(t *testing.T) {
 	// Add some expired test IDs
 	testId := id.NewIdFromUInt(0, id.User, t)
 	validUntil := netTime.Now().Add(time.Minute)
-	m.tracked = append(m.tracked, TrackedID{
+	m.tracked = append(m.tracked, &TrackedID{
 		NextGeneration: netTime.Now(),
 		LastGeneration: time.Time{},
 		Source:         testId,
diff --git a/cmix/message/serviceTracker.go b/cmix/message/serviceTracker.go
index 616f6dfda392ba4cb65c8d02179bbae493fe26ec..e3313a4de659d935b26d6e4477c1e5056a558497 100644
--- a/cmix/message/serviceTracker.go
+++ b/cmix/message/serviceTracker.go
@@ -26,7 +26,6 @@ func (sm *ServicesManager) triggerServiceTracking() {
 	if len(sm.trackers) == 0 {
 		return
 	}
-
 	services := make(ServiceList)
 	for uid, tmap := range sm.tmap {
 		tList := make([]Service, 0, len(tmap))
diff --git a/cmix/message/serviceTracker_test.go b/cmix/message/serviceTracker_test.go
index c7b06b46a853bcc23e267830c9e2cda925385b3a..a1a487762c271bde32476a07652cfd4264b311bf 100644
--- a/cmix/message/serviceTracker_test.go
+++ b/cmix/message/serviceTracker_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestServiceList_Marshal_UnmarshalJSON(t *testing.T) {
 	var sl ServiceList = make(map[id.ID][]Service)
-	numServices := 10
+	numServices := 3
 	testString := "test"
 	for i := 0; i < numServices; i++ {
 		uid := id.NewIdFromUInt(uint64(i), id.User, t)
@@ -24,6 +24,8 @@ func TestServiceList_Marshal_UnmarshalJSON(t *testing.T) {
 		t.Errorf(err.Error())
 	}
 
+	t.Logf("%s", jsonResult)
+
 	sl = make(map[id.ID][]Service)
 	err = sl.UnmarshalJSON(jsonResult)
 	if err != nil {
diff --git a/cmix/sendCmix.go b/cmix/sendCmix.go
index 73dfcfbd2c02b6f1cafcf49cd4662826edf7e3e8..f0c97a6ffc47f7611bc73d22132f2d0e087abe72 100644
--- a/cmix/sendCmix.go
+++ b/cmix/sendCmix.go
@@ -109,6 +109,12 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 	nodes nodes.Registrar, rng *fastRNG.StreamGenerator, events event.Reporter,
 	senderId *id.ID, comms SendCmixCommsInterface) (id.Round, ephemeral.Id, error) {
 
+	if cmixParams.RoundTries == 0 {
+		return 0, ephemeral.Id{},
+			errors.Errorf("invalid parameter set, "+
+				"RoundTries cannot be 0: %+v", cmixParams)
+	}
+
 	timeStart := netTime.Now()
 	maxTimeout := sender.GetHostParams().SendTimeout
 
@@ -120,7 +126,8 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 	}
 
 	jww.INFO.Printf("[Send-%s] Looking for round to send cMix message to "+
-		"%s (msgDigest: %s)", cmixParams.DebugTag, recipient, msg.Digest())
+		"%s (msgDigest: %s)", cmixParams.DebugTag, recipient,
+		msg.Digest())
 
 	stream := rng.GetStream()
 	defer stream.Close()
@@ -129,7 +136,8 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 	// See cmix.SetGroupBits for more info.
 	cmix.SetGroupBits(msg, grp, stream)
 
-	for numRoundTries := uint(0); numRoundTries < cmixParams.RoundTries; numRoundTries++ {
+	for numRoundTries := uint(
+		0); numRoundTries < cmixParams.RoundTries; numRoundTries++ {
 		elapsed := netTime.Since(timeStart)
 		jww.TRACE.Printf("[Send-%s] try %d, elapsed: %s",
 			cmixParams.DebugTag, numRoundTries, elapsed)
@@ -137,42 +145,50 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 		if elapsed > cmixParams.Timeout {
 			jww.INFO.Printf("[Send-%s] No rounds to send to %s "+
 				"(msgDigest: %s) were found before timeout %s",
-				cmixParams.DebugTag, recipient, msg.Digest(), cmixParams.Timeout)
-			return 0, ephemeral.Id{}, errors.New("Sending cmix message timed out")
+				cmixParams.DebugTag, recipient, msg.Digest(),
+				cmixParams.Timeout)
+			return 0, ephemeral.Id{}, errors.New(
+				"Sending cmix message timed out")
 		}
 
 		if numRoundTries > 0 {
-			jww.INFO.Printf("[Send-%s] Attempt %d to find round to send "+
-				"message to %s (msgDigest: %s)", cmixParams.DebugTag,
+			jww.INFO.Printf("[Send-%s] Attempt %d to find round"+
+				" to send message to %s (msgDigest: %s)",
+				cmixParams.DebugTag,
 				numRoundTries+1, recipient, msg.Digest())
 		}
 
 		// Find the best round to send to, excluding attempted rounds
 		remainingTime := cmixParams.Timeout - elapsed
-		bestRound, err := instance.GetWaitingRounds().GetUpcomingRealtime(
+		waitingRounds := instance.GetWaitingRounds()
+		bestRound, err := waitingRounds.GetUpcomingRealtime(
 			remainingTime, attempted, sendTimeBuffer)
 		if err != nil {
-			jww.WARN.Printf("[Send-%s] Failed to GetUpcomingRealtime "+
-				"(msgDigest: %s): %+v", cmixParams.DebugTag, msg.Digest(), err)
+			jww.WARN.Printf("[Send-%s] GetUpcomingRealtime failed "+
+				"(msgDigest: %s): %+v", cmixParams.DebugTag,
+				msg.Digest(), err)
 		}
 
 		if bestRound == nil {
 			jww.WARN.Printf(
-				"[Send-%s] Best round on send is nil", cmixParams.DebugTag)
+				"[Send-%s] Best round on send is nil",
+				cmixParams.DebugTag)
 			continue
 		}
 
 		jww.TRACE.Printf("[Send-%s] Best round found: %+v",
 			cmixParams.DebugTag, bestRound)
 
-		// Determine whether the selected round contains any nodes that are
-		// blacklisted by the CMIXParams object
+		// Determine whether the selected round contains any
+		// nodes that are blacklisted by the CMIXParams object
 		containsBlacklisted := false
 		if cmixParams.BlacklistedNodes != nil {
+			blacklist := cmixParams.BlacklistedNodes
 			for _, nodeId := range bestRound.Topology {
 				var nid id.ID
 				copy(nid[:], nodeId)
-				if _, isBlacklisted := cmixParams.BlacklistedNodes[nid]; isBlacklisted {
+				_, isBlacklisted := blacklist[nid]
+				if isBlacklisted {
 					containsBlacklisted = true
 					break
 				}
@@ -180,8 +196,10 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 		}
 
 		if containsBlacklisted {
-			jww.WARN.Printf("[Send-%s] Round %d contains blacklisted "+
-				"nodes, skipping...", cmixParams.DebugTag, bestRound.ID)
+			jww.WARN.Printf("[Send-%s] Round %d "+
+				"contains blacklisted nodes, skipping...",
+				cmixParams.DebugTag,
+				bestRound.ID)
 			continue
 		}
 
@@ -290,5 +308,5 @@ func sendCmixHelper(sender gateway.Sender, msg format.Message, recipient *id.ID,
 
 	}
 	return 0, ephemeral.Id{},
-		errors.New("failed to send the message, unknown error")
+		errors.New("failed to send the message, out of round retries")
 }
diff --git a/connect/authenticated.go b/connect/authenticated.go
index c45489fbc78ab87ab1115ab6da02d2b0fe5167f4..b2d63f7582380f2116b6f78c14d00bedbcdc5da7 100644
--- a/connect/authenticated.go
+++ b/connect/authenticated.go
@@ -98,7 +98,7 @@ func connectWithAuthentication(conn Connection, timeStart time.Time,
 	}
 
 	// Send message to server
-	rids, _, _, err := conn.SendE2E(catalog.ConnectionAuthenticationRequest,
+	sendReport, err := conn.SendE2E(catalog.ConnectionAuthenticationRequest,
 		payload, clientE2e.GetDefaultParams())
 	if err != nil {
 		// Close connection on an error
@@ -139,7 +139,7 @@ func connectWithAuthentication(conn Connection, timeStart time.Time,
 	// Track the result of the round(s) we sent the
 	// identity authentication message on
 	err = net.GetRoundResults(remainingTime,
-		roundCb, rids...)
+		roundCb, sendReport.RoundList...)
 	if err != nil {
 		return nil, errors.Errorf("could not track rounds for successful " +
 			"identity confirmation message delivery")
diff --git a/connect/connect.go b/connect/connect.go
index ba87dc91873e5d3153e38c60474d42b2f3e23cbc..5cc9f60a2462ed565cfdc8dd5de02fb591c29e20 100644
--- a/connect/connect.go
+++ b/connect/connect.go
@@ -7,6 +7,7 @@
 package connect
 
 import (
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"io"
 	"sync/atomic"
 	"time"
@@ -22,8 +23,6 @@ import (
 	"gitlab.com/elixxir/client/e2e/ratchet/partner"
 	"gitlab.com/elixxir/client/e2e/receive"
 	"gitlab.com/elixxir/crypto/contact"
-	"gitlab.com/elixxir/crypto/e2e"
-	"gitlab.com/xx_network/primitives/id"
 )
 
 var alreadyClosedErr = errors.New("connection is closed")
@@ -43,7 +42,7 @@ type Connection interface {
 	// SendE2E is a wrapper for sending specifically to the Connection's
 	// partner.Manager
 	SendE2E(mt catalog.MessageType, payload []byte, params clientE2e.Params) (
-		[]id.Round, e2e.MessageID, time.Time, error)
+		cryptoE2e.SendReport, error)
 
 	// RegisterListener is used for E2E reception
 	// and allows for reading data sent from the partner.Manager
@@ -221,9 +220,9 @@ func (h *handler) GetPartner() partner.Manager {
 // SendE2E is a wrapper for sending specifically to the Connection's
 // partner.Manager.
 func (h *handler) SendE2E(mt catalog.MessageType, payload []byte,
-	params clientE2e.Params) ([]id.Round, e2e.MessageID, time.Time, error) {
+	params clientE2e.Params) (cryptoE2e.SendReport, error) {
 	if h.isClosed() {
-		return nil, e2e.MessageID{}, time.Time{}, alreadyClosedErr
+		return cryptoE2e.SendReport{}, alreadyClosedErr
 	}
 
 	h.updateLastUse(netTime.Now())
diff --git a/connect/utils_test.go b/connect/utils_test.go
index 6e4be5173d00cd8ba4b671d6abee3a8669e1c77c..7fc60f5398b6361a7254cae0f72082acb17e975d 100644
--- a/connect/utils_test.go
+++ b/connect/utils_test.go
@@ -115,8 +115,7 @@ func (m *mockConnection) Close() error {
 func (m *mockConnection) GetPartner() partner.Manager { return m.partner }
 
 func (m *mockConnection) SendE2E(
-	mt catalog.MessageType, payload []byte, _ e2e.Params) (
-	[]id.Round, cryptoE2e.MessageID, time.Time, error) {
+	mt catalog.MessageType, payload []byte, _ e2e.Params) (cryptoE2e.SendReport, error) {
 	m.payloadChan <- payload
 	m.listener.Hear(receive.Message{
 		MessageType: mt,
@@ -124,7 +123,7 @@ func (m *mockConnection) SendE2E(
 		Sender:      m.partner.myID,
 		RecipientID: m.partner.partnerId,
 	})
-	return nil, cryptoE2e.MessageID{}, time.Time{}, nil
+	return cryptoE2e.SendReport{}, nil
 }
 
 func (m *mockConnection) RegisterListener(
diff --git a/e2e/critical.go b/e2e/critical.go
index 9a803886ff81faeded4e546a8462af9e78c73dc9..cc5a1ee9268e93923ae80520b5c9f211878d16bf 100644
--- a/e2e/critical.go
+++ b/e2e/critical.go
@@ -1,6 +1,7 @@
 package e2e
 
 import (
+	"gitlab.com/elixxir/crypto/e2e"
 	"time"
 
 	jww "github.com/spf13/jwalterweatherman"
@@ -9,7 +10,6 @@ import (
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/client/storage/versioned"
 	ds "gitlab.com/elixxir/comms/network/dataStructures"
-	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/primitives/id"
@@ -29,7 +29,7 @@ type roundEventRegistrar interface {
 // anonymous function to include the structures from manager that critical is
 // not aware of.
 type criticalSender func(mt catalog.MessageType, recipient *id.ID,
-	payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error)
+	payload []byte, params Params) (e2e.SendReport, error)
 
 // critical is a structure that allows the auto resending of messages that must
 // be received.
@@ -138,11 +138,11 @@ func (c *critical) evaluate(stop *stoppable.Single) {
 				format.DigestContents(payload))
 
 			// Send the message
-			round, _, _, err := c.send(mt, recipient, payload,
+			sendReport, err := c.send(mt, recipient, payload,
 				params)
 
 			// Pass to the handler
-			c.handle(mt, recipient, payload, round, err)
+			c.handle(mt, recipient, payload, sendReport.RoundList, err)
 		}(mt, recipient, payload, params)
 	}
 
diff --git a/e2e/fpGenerator_test.go b/e2e/fpGenerator_test.go
index ebd2d7cc381a3d3ccc908b9118b697226bb0af59..1360d14e6c527aa7f063e49c6d19760b9a8a8b0a 100644
--- a/e2e/fpGenerator_test.go
+++ b/e2e/fpGenerator_test.go
@@ -16,6 +16,7 @@ import (
 	"gitlab.com/elixxir/client/e2e/ratchet/partner/session"
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/comms/network"
+	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/comms/connect"
 	"gitlab.com/xx_network/primitives/id"
@@ -90,11 +91,15 @@ type mockSessionCypher struct {
 	fp format.Fingerprint
 }
 
-func (m mockSessionCypher) GetSession() *session.Session             { return nil }
-func (m mockSessionCypher) Fingerprint() format.Fingerprint          { return m.fp }
-func (m mockSessionCypher) Encrypt([]byte) (ecrContents, mac []byte) { return nil, nil }
-func (m mockSessionCypher) Decrypt(format.Message) ([]byte, error)   { return nil, nil }
-func (m mockSessionCypher) Use()                                     {}
+func (m mockSessionCypher) GetSession() *session.Session    { return nil }
+func (m mockSessionCypher) Fingerprint() format.Fingerprint { return m.fp }
+func (m mockSessionCypher) Encrypt([]byte) (ecrContents, mac []byte, residue e2e.KeyResidue) {
+	return nil, nil, e2e.KeyResidue{}
+}
+func (m mockSessionCypher) Decrypt(format.Message) ([]byte, e2e.KeyResidue, error) {
+	return nil, e2e.KeyResidue{}, nil
+}
+func (m mockSessionCypher) Use() {}
 
 ////////////////////////////////////////////////////////////////////////////////
 // Mock cMix                                                           //
diff --git a/e2e/interface.go b/e2e/interface.go
index 0578bbc719731f5d1c83a7e16330506aac8bac66..db4d4a59087883e17db9ce6f74a604e0f7645daa 100644
--- a/e2e/interface.go
+++ b/e2e/interface.go
@@ -2,6 +2,7 @@ package e2e
 
 import (
 	"gitlab.com/elixxir/client/cmix/rounds"
+	"gitlab.com/elixxir/crypto/e2e"
 	"time"
 
 	"github.com/cloudflare/circl/dh/sidh"
@@ -12,7 +13,6 @@ import (
 	"gitlab.com/elixxir/client/e2e/receive"
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/xx_network/primitives/id"
 )
 
@@ -35,7 +35,7 @@ type Handler interface {
 	// Will return an error if the network is not healthy or in
 	// the event of a failed send
 	SendE2E(mt catalog.MessageType, recipient *id.ID, payload []byte,
-		params Params) ([]id.Round, e2e.MessageID, time.Time, error)
+		params Params) (e2e.SendReport, error)
 
 	/* === Reception ==================================================== */
 
diff --git a/e2e/manager.go b/e2e/manager.go
index bd7c313811a792ddc5535f8e4ab0f00cd97406b0..9fc1b4b3d123a4d0b6657cc2ddfa955f72866802 100644
--- a/e2e/manager.go
+++ b/e2e/manager.go
@@ -4,10 +4,9 @@ import (
 	"bytes"
 	"encoding/base64"
 	"encoding/json"
-	"sync"
-	"time"
-
 	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/e2e"
+	"sync"
 
 	"gitlab.com/xx_network/primitives/netTime"
 
@@ -23,7 +22,6 @@ import (
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/xx_network/primitives/id"
 )
@@ -214,8 +212,7 @@ func (m *manager) StartProcesses() (stoppable.Stoppable, error) {
 
 	rekeySendFunc := func(mt catalog.MessageType,
 		recipient *id.ID, payload []byte,
-		cmixParams cmix.CMIXParams) (
-		[]id.Round, e2e.MessageID, time.Time, error) {
+		cmixParams cmix.CMIXParams) (e2e.SendReport, error) {
 		// FIXME: we should have access to the e2e params here...
 		par := GetDefaultParams()
 		par.CMIXParams = cmixParams
@@ -284,14 +281,15 @@ func (m *manager) DeletePartnerNotify(partnerId *id.ID, params Params) error {
 	m.DeletePartnerCallbacks(partnerId)
 
 	// Send closing E2E message
-	rounds, msgID, timestamp, err := sendFunc()
+
+	sendReport, err := sendFunc()
 	if err != nil {
 		jww.ERROR.Printf("Failed to send %s E2E message to %s: %+v",
 			catalog.E2eClose, partnerId, err)
 	} else {
 		jww.INFO.Printf(
 			"Sent %s E2E message to %s on rounds %v with message ID %s at %s",
-			catalog.E2eClose, partnerId, rounds, msgID, timestamp)
+			catalog.E2eClose, partnerId, sendReport.RoundList, sendReport.MessageId, sendReport.SentTime)
 	}
 
 	return nil
diff --git a/e2e/params.go b/e2e/params.go
index 2702d490656342e975883177b115f63dbf2df961..ce0b72ff54282a5798c0ea2e085d8cd3143bc734 100644
--- a/e2e/params.go
+++ b/e2e/params.go
@@ -100,3 +100,9 @@ func (p *Params) UnmarshalJSON(data []byte) error {
 
 	return nil
 }
+
+// String implements stringer interface by returning a json string
+func (p *Params) String() string {
+	json, _ := p.MarshalJSON()
+	return string(json)
+}
diff --git a/e2e/parse/partition.go b/e2e/parse/partition.go
index da7628381175971f748d456761ccc836345bd227..9e21827586a2601766e675c548ffcbfad61050ea 100644
--- a/e2e/parse/partition.go
+++ b/e2e/parse/partition.go
@@ -8,6 +8,7 @@
 package parse
 
 import (
+	"gitlab.com/elixxir/crypto/e2e"
 	"time"
 
 	"github.com/pkg/errors"
@@ -78,7 +79,8 @@ func (p *Partitioner) Partition(recipient *id.ID, mt catalog.MessageType,
 }
 
 func (p *Partitioner) HandlePartition(sender *id.ID,
-	contents []byte, relationshipFingerprint []byte) (receive.Message, bool) {
+	contents []byte, relationshipFingerprint []byte,
+	residue e2e.KeyResidue) (receive.Message, e2e.KeyResidue, bool) {
 
 	if isFirst(contents) {
 		// If it is the first message in a set, then handle it as so
@@ -92,7 +94,7 @@ func (p *Partitioner) HandlePartition(sender *id.ID,
 		storageTimestamp := netTime.Now()
 		return p.partition.AddFirst(sender, fm.getType(), messageID,
 			fm.getPart(), fm.getNumParts(), fm.getTimestamp(), storageTimestamp,
-			fm.getSizedContents(), relationshipFingerprint)
+			fm.getSizedContents(), relationshipFingerprint, residue)
 	} else {
 		// If it is a subsequent message part, handle it as so
 		mp := messagePartFromBytes(contents)
diff --git a/e2e/parse/partition/multiPartMessage.go b/e2e/parse/partition/multiPartMessage.go
index d067248d2a792fe846a9cb2e42c92ba001d5b3d5..f7fd42085929395ea4fa3d4265909aeecf0fb467 100644
--- a/e2e/parse/partition/multiPartMessage.go
+++ b/e2e/parse/partition/multiPartMessage.go
@@ -41,6 +41,8 @@ type multiPartMessage struct {
 	StorageTimestamp time.Time
 	MessageType      catalog.MessageType
 
+	KeyResidue e2e.KeyResidue
+
 	parts [][]byte
 	kv    *versioned.KV
 	mux   sync.Mutex
diff --git a/e2e/parse/partition/store.go b/e2e/parse/partition/store.go
index 7774e3187b90235d79f2ce4082024e8f4a547dec..868737ea996f4f30516fbadae66112286955d704 100644
--- a/e2e/parse/partition/store.go
+++ b/e2e/parse/partition/store.go
@@ -8,12 +8,14 @@
 package partition
 
 import (
+	"bytes"
 	"encoding/binary"
 	"encoding/json"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/catalog"
 	"gitlab.com/elixxir/client/e2e/receive"
 	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/netTime"
 	"golang.org/x/crypto/blake2b"
@@ -49,47 +51,60 @@ func NewOrLoad(kv *versioned.KV) *Store {
 	return partitionStore
 }
 
+// AddFirst adds the first partition message to the Store object.
 func (s *Store) AddFirst(partner *id.ID, mt catalog.MessageType,
 	messageID uint64, partNum, numParts uint8, senderTimestamp,
-	storageTimestamp time.Time, part []byte, relationshipFingerprint []byte) (
-	receive.Message, bool) {
+	storageTimestamp time.Time, part []byte, relationshipFingerprint []byte,
+	residue e2e.KeyResidue) (
+	receive.Message, e2e.KeyResidue, bool) {
 
 	mpm := s.load(partner, messageID)
-
 	mpm.AddFirst(mt, partNum, numParts, senderTimestamp, storageTimestamp, part)
+	if bytes.Equal(residue.Marshal(), []byte{}) {
+		// fixme: should this error or crash?
+		jww.WARN.Printf("Key reside from first message " +
+			"is empty, continuing...")
+	}
+
+	mpm.KeyResidue = residue
 	msg, ok := mpm.IsComplete(relationshipFingerprint)
 
 	s.mux.Lock()
 	defer s.mux.Unlock()
 
+	keyRes := e2e.KeyResidue{}
 	if !ok {
 		s.activeParts[mpm] = true
 		s.saveActiveParts()
 	} else {
+		keyRes = mpm.KeyResidue
 		mpID := getMultiPartID(mpm.Sender, mpm.MessageID)
 		delete(s.multiParts, mpID)
 	}
 
-	return msg, ok
+	return msg, keyRes, ok
 }
 
 func (s *Store) Add(partner *id.ID, messageID uint64, partNum uint8,
-	part []byte, relationshipFingerprint []byte) (receive.Message, bool) {
+	part []byte, relationshipFingerprint []byte) (
+	receive.Message, e2e.KeyResidue, bool) {
 
 	mpm := s.load(partner, messageID)
 
 	mpm.Add(partNum, part)
 
 	msg, ok := mpm.IsComplete(relationshipFingerprint)
+	keyRes := e2e.KeyResidue{}
 	if !ok {
 		s.activeParts[mpm] = true
 		s.saveActiveParts()
 	} else {
+		keyRes = mpm.KeyResidue
 		mpID := getMultiPartID(mpm.Sender, mpm.MessageID)
 		delete(s.multiParts, mpID)
 	}
 
-	return msg, ok
+	return msg, keyRes, ok
 }
 
 // prune clears old messages on it's stored timestamp.
diff --git a/e2e/parse/partition/store_test.go b/e2e/parse/partition/store_test.go
index 909de9b829d54e7b07fc4936ed82bdd2337ccc5f..29526a2b829ad23e53f17ada0e2c393daf5fda5b 100644
--- a/e2e/parse/partition/store_test.go
+++ b/e2e/parse/partition/store_test.go
@@ -11,6 +11,7 @@ import (
 	"bytes"
 	"gitlab.com/elixxir/client/catalog"
 	"gitlab.com/elixxir/client/storage/versioned"
+	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/netTime"
@@ -39,15 +40,27 @@ func TestNewOrLoad(t *testing.T) {
 func TestStore_AddFirst(t *testing.T) {
 	part := []byte("Test message.")
 	s := NewOrLoad(versioned.NewKV(ekv.MakeMemstore()))
+	b := make([]byte, e2e.KeyResidueLength)
+	kr, err := e2e.UnmarshalKeyResidue(b)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal key residue: %+v", err)
+	}
 
-	msg, complete := s.AddFirst(id.NewIdFromString("User", id.User, t),
+	msg, receivedKr, complete := s.AddFirst(id.NewIdFromString("User", id.User, t),
 		catalog.XxMessage, 5, 0, 1, netTime.Now(), netTime.Now(), part,
-		[]byte{0})
+		[]byte{0}, kr)
 
 	if !complete {
 		t.Errorf("AddFirst returned that the message was not complete.")
 	}
 
+	if !bytes.Equal(receivedKr[:], kr[:]) {
+		t.Fatalf("Key residue returned from complete partition did not "+
+			"match first key signature."+
+			"\nExpected: %v"+
+			"\nReceived: %v", kr, receivedKr)
+	}
+
 	if !bytes.Equal(part, msg.Payload) {
 		t.Errorf("AddFirst returned message with invalid payload."+
 			"\nexpected: %v\nreceived: %v", part, msg.Payload)
@@ -59,21 +72,33 @@ func TestStore_Add(t *testing.T) {
 	part1 := []byte("Test message.")
 	part2 := []byte("Second Sentence.")
 	s := NewOrLoad(versioned.NewKV(ekv.MakeMemstore()))
+	b := make([]byte, e2e.KeyResidueLength)
+	kr, err := e2e.UnmarshalKeyResidue(b)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal key residue: %+v", err)
+	}
 
-	msg, complete := s.AddFirst(id.NewIdFromString("User", id.User, t),
+	msg, _, complete := s.AddFirst(id.NewIdFromString("User", id.User, t),
 		catalog.XxMessage, 5, 0, 2, netTime.Now(), netTime.Now(), part1,
-		[]byte{0})
+		[]byte{0}, kr)
 
 	if complete {
 		t.Errorf("AddFirst returned that the message was complete.")
 	}
 
-	msg, complete = s.Add(id.NewIdFromString("User", id.User, t),
+	msg, receivedKr, complete := s.Add(id.NewIdFromString("User", id.User, t),
 		5, 1, part2, []byte{0})
 	if !complete {
 		t.Errorf("AddFirst returned that the message was not complete.")
 	}
 
+	if !bytes.Equal(receivedKr[:], kr[:]) {
+		t.Fatalf("Key residue returned from complete partition did not "+
+			"match first key signature."+
+			"\nExpected: %v"+
+			"\nReceived: %v", kr, receivedKr)
+	}
+
 	part := append(part1, part2...)
 	if !bytes.Equal(part, msg.Payload) {
 		t.Errorf("AddFirst returned message with invalid payload."+
@@ -92,10 +117,15 @@ func TestStore_prune(t *testing.T) {
 	partner1 := id.NewIdFromString("User", id.User, t)
 	messageId1 := uint64(5)
 	oldTimestamp := netTime.Now().Add(-2 * clearPartitionThreshold)
+	b := make([]byte, e2e.KeyResidueLength)
+	kr, err := e2e.UnmarshalKeyResidue(b)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal key residue: %+v", err)
+	}
 	s.AddFirst(partner1,
 		catalog.XxMessage, messageId1, 0, 2, netTime.Now(),
 		oldTimestamp, part1,
-		[]byte{0})
+		[]byte{0}, kr)
 	s.Add(partner1, messageId1, 1, part2, []byte{0})
 
 	partner2 := id.NewIdFromString("User1", id.User, t)
@@ -103,7 +133,7 @@ func TestStore_prune(t *testing.T) {
 	newTimestamp := netTime.Now()
 	s.AddFirst(partner2, catalog.XxMessage, messageId2, 0, 2, netTime.Now(),
 		newTimestamp, part1,
-		[]byte{0})
+		[]byte{0}, kr)
 
 	// Call clear messages
 	s.prune()
diff --git a/e2e/parse/partition_test.go b/e2e/parse/partition_test.go
index 22c1f238eda4b2f2f0be5575e68143c28970a749..3abf6ff1f7db7d41eeaff31ba6dee0bcd3e82fb2 100644
--- a/e2e/parse/partition_test.go
+++ b/e2e/parse/partition_test.go
@@ -8,6 +8,7 @@
 package parse
 
 import (
+	"gitlab.com/elixxir/crypto/e2e"
 	"testing"
 
 	"gitlab.com/elixxir/client/catalog"
@@ -72,10 +73,11 @@ func TestPartitioner_HandlePartition(t *testing.T) {
 	p := NewPartitioner(versioned.NewKV(ekv.MakeMemstore()), len(ipsumTestStr))
 	m := newMessagePart(1107, 1, []byte(ipsumTestStr), len(ipsumTestStr)+headerLen)
 
-	_, _ = p.HandlePartition(
+	_, _, _ = p.HandlePartition(
 		&id.DummyUser,
 		m.bytes(),
 		[]byte{'t', 'e', 's', 't', 'i', 'n', 'g', 's', 't', 'r', 'i', 'n', 'g'},
+		e2e.KeyResidue{},
 	)
 }
 
@@ -85,9 +87,10 @@ func TestPartitioner_HandleFirstPartition(t *testing.T) {
 	m := newFirstMessagePart(
 		catalog.XxMessage, 1107, 1, netTime.Now(), []byte(ipsumTestStr), len([]byte(ipsumTestStr))+firstHeaderLen)
 
-	_, _ = p.HandlePartition(
+	_, _, _ = p.HandlePartition(
 		&id.DummyUser,
 		m.bytes(),
 		[]byte{'t', 'e', 's', 't', 'i', 'n', 'g', 's', 't', 'r', 'i', 'n', 'g'},
+		e2e.KeyResidue{},
 	)
 }
diff --git a/e2e/processor.go b/e2e/processor.go
index 17e2293401209f84783f4855182f607cc8f6bd0e..b1a8411eb473f511b974e132086a917e51469994 100644
--- a/e2e/processor.go
+++ b/e2e/processor.go
@@ -23,7 +23,7 @@ func (p *processor) Process(ecrMsg format.Message,
 	// ensure the key will be marked used before returning
 	defer p.cy.Use()
 
-	contents, err := p.cy.Decrypt(ecrMsg)
+	contents, residue, err := p.cy.Decrypt(ecrMsg)
 	if err != nil {
 		jww.ERROR.Printf("decrypt failed of %s (fp: %s), dropping: %+v",
 			ecrMsg.Digest(), p.cy.Fingerprint(), err)
@@ -31,8 +31,9 @@ func (p *processor) Process(ecrMsg format.Message,
 	}
 
 	sess := p.cy.GetSession()
-	message, done := p.m.partitioner.HandlePartition(sess.GetPartner(),
-		contents, sess.GetRelationshipFingerprint())
+	// todo: handle residue here
+	message, _, done := p.m.partitioner.HandlePartition(sess.GetPartner(),
+		contents, sess.GetRelationshipFingerprint(), residue)
 	if done {
 		message.RecipientID = receptionID.Source
 		message.EphemeralID = receptionID.EphId
diff --git a/e2e/ratchet/partner/session/cypher.go b/e2e/ratchet/partner/session/cypher.go
index ef4094a40f0967f199dd4f89786fd6a2dcda5459..12ea988916ccffaf91d54a013246f09beaaafcb0 100644
--- a/e2e/ratchet/partner/session/cypher.go
+++ b/e2e/ratchet/partner/session/cypher.go
@@ -63,13 +63,15 @@ type Cypher interface {
 
 	// Encrypt uses the E2E key to encrypt the message to its intended
 	// recipient. It also properly populates the associated data, including the
-	// MAC, fingerprint, and encrypted timestamp.
-	Encrypt(contents []byte) (ecrContents, mac []byte)
+	// MAC, fingerprint, and encrypted timestamp. It generates a residue of the
+	// key used to encrypt the contents.
+	Encrypt(contents []byte) (ecrContents, mac []byte, residue e2eCrypto.KeyResidue)
 
 	// Decrypt uses the E2E key to decrypt the message. It returns an error in
 	// case of HMAC verification failure or in case of a decryption error
-	// (related to padding).
-	Decrypt(msg format.Message) ([]byte, error)
+	// (related to padding). It generates a residue of the
+	// key used to encrypt the contents.
+	Decrypt(msg format.Message) (decryptedPayload []byte, residue e2eCrypto.KeyResidue, err error)
 
 	// Use sets the key as used. It cannot be used again.
 	Use()
@@ -110,11 +112,13 @@ func (k *cypher) Fingerprint() format.Fingerprint {
 
 // Encrypt uses the E2E key to encrypt the message to its intended recipient. It
 // also properly populates the associated data, including the MAC, fingerprint,
-// and encrypted timestamp.
-func (k *cypher) Encrypt(contents []byte) (ecrContents, mac []byte) {
+// and encrypted timestamp. It generates a residue of the key used to encrypt the contents.
+func (k *cypher) Encrypt(contents []byte) (ecrContents, mac []byte, residue e2eCrypto.KeyResidue) {
 	fp := k.Fingerprint()
 	key := k.generateKey()
 
+	residue = e2eCrypto.NewKeyResidue(key)
+
 	// encrypt the payload
 	ecrContents = e2eCrypto.Crypt(key, fp, contents)
 
@@ -122,25 +126,28 @@ func (k *cypher) Encrypt(contents []byte) (ecrContents, mac []byte) {
 	// Currently, the MAC doesn't include any of the associated data
 	mac = hash.CreateHMAC(ecrContents, key[:])
 
-	return ecrContents, mac
+	return ecrContents, mac, residue
 }
 
 // Decrypt uses the E2E key to decrypt the message. It returns an error in case
 // of HMAC verification failure or in case of a decryption error (related to
-// padding).
-func (k *cypher) Decrypt(msg format.Message) ([]byte, error) {
+// padding). It generates a residue of the key used to encrypt the contents
+func (k *cypher) Decrypt(msg format.Message) (decryptedPayload []byte, residue e2eCrypto.KeyResidue, err error) {
 	fp := k.Fingerprint()
 	key := k.generateKey()
 
 	// Verify the MAC is correct
 	if !hash.VerifyHMAC(msg.GetContents(), msg.GetMac(), key[:]) {
-		return nil, errors.New("HMAC verification failed for E2E message")
+		return nil, e2eCrypto.KeyResidue{}, errors.New("HMAC verification failed for E2E message")
 	}
 
 	// Decrypt the payload
-	decryptedPayload := e2eCrypto.Crypt(key, fp, msg.GetContents())
+	decryptedPayload = e2eCrypto.Crypt(key, fp, msg.GetContents())
+
+	// Construct residue
+	residue = e2eCrypto.NewKeyResidue(key)
 
-	return decryptedPayload, nil
+	return decryptedPayload, residue, nil
 }
 
 // Use sets the key as used. It cannot be used again.
diff --git a/e2e/ratchet/partner/session/cypher_test.go b/e2e/ratchet/partner/session/cypher_test.go
index b6c92de98bc3dcaf1b6e18237df418ad246dbe5b..c46e380a8bb48f129f06301460c9f52dc05849d7 100644
--- a/e2e/ratchet/partner/session/cypher_test.go
+++ b/e2e/ratchet/partner/session/cypher_test.go
@@ -153,7 +153,7 @@ func Test_cypher_EncryptDecrypt(t *testing.T) {
 		msg.SetContents(contents)
 
 		// Encrypt
-		contentsEnc, mac := cy.Encrypt(msg.GetContents())
+		contentsEnc, mac, _ := cy.Encrypt(msg.GetContents())
 
 		// Make the encrypted message
 		ecrMsg := format.NewMessage(grp.GetP().ByteLen())
@@ -162,7 +162,7 @@ func Test_cypher_EncryptDecrypt(t *testing.T) {
 		ecrMsg.SetMac(mac)
 
 		// Decrypt
-		contentsDecr, err := cy.Decrypt(ecrMsg)
+		contentsDecr, _, err := cy.Decrypt(ecrMsg)
 		if err != nil {
 			t.Fatalf("Decrypt error: %+v", err)
 		}
diff --git a/e2e/rekey/exchange.go b/e2e/rekey/exchange.go
index 30345ca1a28cf7b30ea8db9fd6587289e4092aec..cd5f553efa6501c956de7da3601e5fe5a56e4291 100644
--- a/e2e/rekey/exchange.go
+++ b/e2e/rekey/exchange.go
@@ -16,12 +16,10 @@ import (
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/xx_network/primitives/id"
-	"time"
 )
 
 type E2eSender func(mt catalog.MessageType, recipient *id.ID, payload []byte,
-	cmixParams cmix.CMIXParams) (
-	[]id.Round, e2e.MessageID, time.Time, error)
+	cmixParams cmix.CMIXParams) (e2e.SendReport, error)
 
 func Start(switchboard *receive.Switchboard, ratchet *ratchet.Ratchet,
 	sender E2eSender, net cmix.Client, grp *cyclic.Group, params Params) (stoppable.Stoppable, error) {
diff --git a/e2e/rekey/rekey.go b/e2e/rekey/rekey.go
index 960df918f20e3aee3e87a77ac4959668fc5db87c..3d2cdfca774503ea68d6d0bb217c73ffbeba34dc 100644
--- a/e2e/rekey/rekey.go
+++ b/e2e/rekey/rekey.go
@@ -125,7 +125,8 @@ func negotiate(instance *commsNetwork.Instance, grp *cyclic.Group, sendE2E E2eSe
 	params := cmix.GetDefaultCMIXParams()
 	params.DebugTag = "kx.Request"
 
-	rounds, msgID, _, err := sendE2E(param.Trigger, sess.GetPartner(),
+	// fixme: should this use the key residue?
+	sendReport, err := sendE2E(param.Trigger, sess.GetPartner(),
 		payload, params)
 	// If the send fails, returns the error so it can be handled. The caller
 	// should ensure the calling session is in a state where the Rekey will
@@ -137,18 +138,18 @@ func negotiate(instance *commsNetwork.Instance, grp *cyclic.Group, sendE2E E2eSe
 	}
 
 	//create the runner which will handle the result of sending the messages
-	sendResults := make(chan ds.EventReturn, len(rounds))
+	sendResults := make(chan ds.EventReturn, len(sendReport.RoundList))
 
 	//Register the event for all rounds
 	roundEvents := instance.GetRoundEvents()
-	for _, r := range rounds {
+	for _, r := range sendReport.RoundList {
 		roundEvents.AddRoundEventChan(r, sendResults, sendTimeout,
 			states.COMPLETED, states.FAILED)
 	}
 
 	//Wait until the result tracking responds
 	success, numRoundFail, numTimeOut := cmix.TrackResults(sendResults,
-		len(rounds))
+		len(sendReport.RoundList))
 
 	// If a single partition of the Key Negotiation request does not
 	// transmit, the partner cannot read the result. Log the error and set
@@ -157,14 +158,14 @@ func negotiate(instance *commsNetwork.Instance, grp *cyclic.Group, sendE2E E2eSe
 		_ = sess.TrySetNegotiationStatus(session.Unconfirmed)
 		return errors.Errorf("[REKEY] Key Negotiation rekey for %s failed to "+
 			"transmit %v/%v paritions: %v round failures, %v timeouts, msgID: %s",
-			sess, numRoundFail+numTimeOut, len(rounds), numRoundFail,
-			numTimeOut, msgID)
+			sess, numRoundFail+numTimeOut, len(sendReport.RoundList), numRoundFail,
+			numTimeOut, sendReport.MessageId)
 	}
 
 	// otherwise, the transmission is a success and this should be denoted
 	// in the session and the log
 	jww.INFO.Printf("[REKEY] Key Negotiation rekey transmission for %s, msgID %s successful",
-		sess, msgID)
+		sess, sendReport.MessageId)
 	err = sess.TrySetNegotiationStatus(session.Sent)
 	if err != nil {
 		if sess.NegotiationStatus() == session.NewSessionTriggered {
diff --git a/e2e/rekey/trigger.go b/e2e/rekey/trigger.go
index 80d2cd2b62a8a1840e8605aa79bc7d4e501cbb2d..4f62828c669c4c34c6500747fca1af4bb7922aee 100644
--- a/e2e/rekey/trigger.go
+++ b/e2e/rekey/trigger.go
@@ -127,7 +127,8 @@ func handleTrigger(ratchet *ratchet.Ratchet, sender E2eSender,
 	params := cmix.GetDefaultCMIXParams()
 	params.Critical = true
 	//ignore results, the passed sender interface makes it a critical message
-	_, _, _, _ = sender(param.Confirm, request.Sender, payload,
+	// fixme: should this ignore the error as well?
+	_, _ = sender(param.Confirm, request.Sender, payload,
 		params)
 
 	return nil
diff --git a/e2e/rekey/trigger_test.go b/e2e/rekey/trigger_test.go
index 8e9ab6658eb627babb1dd9acacd0150222f1a71c..ba41dff0e1e179c746265260a5328c8312772465 100644
--- a/e2e/rekey/trigger_test.go
+++ b/e2e/rekey/trigger_test.go
@@ -119,7 +119,8 @@ func TestHandleTrigger(t *testing.T) {
 	rekeyParams := GetDefaultParams()
 	stop := stoppable.NewSingle("stoppable")
 	rekeyParams.RoundTimeout = 0 * time.Second
-	err = handleTrigger(r, testSendE2E, &mockNetManager{}, grp, receiveMsg, rekeyParams, stop)
+	err = handleTrigger(r, testSendE2E, &mockNetManager{}, grp, receiveMsg,
+		rekeyParams, stop)
 	if err != nil {
 		t.Errorf("Handle trigger error: %v", err)
 	}
diff --git a/e2e/rekey/utils_test.go b/e2e/rekey/utils_test.go
index 3e1f77f1dcdc80a760498da5ea6b1905b90cec84..4f040f4d71083ca6625165e9c73caef261d6e54b 100644
--- a/e2e/rekey/utils_test.go
+++ b/e2e/rekey/utils_test.go
@@ -8,6 +8,7 @@
 package rekey
 
 import (
+	"gitlab.com/elixxir/crypto/e2e"
 	"math/rand"
 	"testing"
 	"time"
@@ -27,7 +28,6 @@ import (
 	network2 "gitlab.com/elixxir/comms/network"
 	ds "gitlab.com/elixxir/comms/network/dataStructures"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/crypto/hash"
 	"gitlab.com/elixxir/primitives/format"
 	"gitlab.com/xx_network/comms/connect"
@@ -73,7 +73,8 @@ func genSidhKeys() (*sidh.PrivateKey, *sidh.PublicKey, *sidh.PrivateKey, *sidh.P
 }
 
 func testSendE2E(mt catalog.MessageType, recipient *id.ID,
-	payload []byte, cmixParams cmix.CMIXParams) ([]id.Round, e2e.MessageID, time.Time, error) {
+	payload []byte, cmixParams cmix.CMIXParams) (
+	e2e.SendReport, error) {
 	rounds := []id.Round{id.Round(0), id.Round(1), id.Round(2)}
 	alicePartner, err := r.GetPartner(aliceID)
 	if err != nil {
@@ -109,7 +110,9 @@ func testSendE2E(mt catalog.MessageType, recipient *id.ID,
 
 	bobSwitchboard.Speak(confirmMessage)
 
-	return rounds, e2e.MessageID{}, time.Time{}, nil
+	return e2e.SendReport{
+		RoundList: rounds,
+	}, nil
 }
 
 var pub = "-----BEGIN CERTIFICATE-----\nMIIGHTCCBAWgAwIBAgIUOcAn9cpH+hyRH8/UfqtbFDoSxYswDQYJKoZIhvcNAQEL\nBQAwgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJQ2xhcmVt\nb250MRAwDgYDVQQKDAdFbGl4eGlyMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEZMBcG\nA1UEAwwQZ2F0ZXdheS5jbWl4LnJpcDEfMB0GCSqGSIb3DQEJARYQYWRtaW5AZWxp\neHhpci5pbzAeFw0xOTA4MTYwMDQ4MTNaFw0yMDA4MTUwMDQ4MTNaMIGSMQswCQYD\nVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCUNsYXJlbW9udDEQMA4GA1UE\nCgwHRWxpeHhpcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGTAXBgNVBAMMEGdhdGV3\nYXkuY21peC5yaXAxHzAdBgkqhkiG9w0BCQEWEGFkbWluQGVsaXh4aXIuaW8wggIi\nMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7Dkb6VXFn4cdpU0xh6ji0nTDQ\nUyT9DSNW9I3jVwBrWfqMc4ymJuonMZbuqK+cY2l+suS2eugevWZrtzujFPBRFp9O\n14Jl3fFLfvtjZvkrKbUMHDHFehascwzrp3tXNryiRMmCNQV55TfITVCv8CLE0t1i\nbiyOGM9ZWYB2OjXt59j76lPARYww5qwC46vS6+3Cn2Yt9zkcrGeskWEFa2VttHqF\n910TP+DZk2R5C7koAh6wZYK6NQ4S83YQurdHAT51LKGrbGehFKXq6/OAXCU1JLi3\nkW2PovTb6MZuvxEiRmVAONsOcXKu7zWCmFjuZZwfRt2RhnpcSgzfrarmsGM0LZh6\nJY3MGJ9YdPcVGSz+Vs2E4zWbNW+ZQoqlcGeMKgsIiQ670g0xSjYICqldpt79gaET\n9PZsoXKEmKUaj6pq1d4qXDk7s63HRQazwVLGBdJQK8qX41eCdR8VMKbrCaOkzD5z\ngnEu0jBBAwdMtcigkMIk1GRv91j7HmqwryOBHryLi6NWBY3tjb4So9AppDQB41SH\n3SwNenAbNO1CXeUqN0hHX6I1bE7OlbjqI7tXdrTllHAJTyVVjenPel2ApMXp+LVR\ndDbKtwBiuM6+n+z0I7YYerxN1gfvpYgcXm4uye8dfwotZj6H2J/uSALsU2v9UHBz\nprdrLSZk2YpozJb+CQIDAQABo2kwZzAdBgNVHQ4EFgQUDaTvG7SwgRQ3wcYx4l+W\nMcZjX7owHwYDVR0jBBgwFoAUDaTvG7SwgRQ3wcYx4l+WMcZjX7owDwYDVR0TAQH/\nBAUwAwEB/zAUBgNVHREEDTALgglmb28uY28udWswDQYJKoZIhvcNAQELBQADggIB\nADKz0ST0uS57oC4rT9zWhFqVZkEGh1x1XJ28bYtNUhozS8GmnttV9SnJpq0EBCm/\nr6Ub6+Wmf60b85vCN5WDYdoZqGJEBjGGsFzl4jkYEE1eeMfF17xlNUSdt1qLCE8h\nU0glr32uX4a6nsEkvw1vo1Liuyt+y0cOU/w4lgWwCqyweu3VuwjZqDoD+3DShVzX\n8f1p7nfnXKitrVJt9/uE+AtAk2kDnjBFbRxCfO49EX4Cc5rADUVXMXm0itquGBYp\nMbzSgFmsMp40jREfLYRRzijSZj8tw14c2U9z0svvK9vrLCrx9+CZQt7cONGHpr/C\n/GIrP/qvlg0DoLAtjea73WxjSCbdL3Nc0uNX/ymXVHdQ5husMCZbczc9LYdoT2VP\nD+GhkAuZV9g09COtRX4VP09zRdXiiBvweiq3K78ML7fISsY7kmc8KgVH22vcXvMX\nCgGwbrxi6QbQ80rWjGOzW5OxNFvjhvJ3vlbOT6r9cKZGIPY8IdN/zIyQxHiim0Jz\noavr9CPDdQefu9onizsmjsXFridjG/ctsJxcUEqK7R12zvaTxu/CVYZbYEUFjsCe\nq6ZAACiEJGvGeKbb/mSPvGs2P1kS70/cGp+P5kBCKqrm586FB7BcafHmGFrWhT3E\nLOUYkOV/gADT2hVDCrkPosg7Wb6ND9/mhCVVhf4hLGRh\n-----END CERTIFICATE-----\n"
diff --git a/e2e/sendE2E.go b/e2e/sendE2E.go
index 284008a92d92988b56abb6f6e81fd49381be94e0..3a181f3c7f2e508fe7372d7c2d8684919f016de8 100644
--- a/e2e/sendE2E.go
+++ b/e2e/sendE2E.go
@@ -19,11 +19,24 @@ import (
 	"gitlab.com/xx_network/primitives/netTime"
 )
 
+// SendE2E send a message containing the payload to the
+// recipient of the passed message type, per the given
+// parameters - encrypted with end-to-end encryption.
+// Default parameters can be retrieved through
+// GetDefaultParams()
+// If too long, it will chunk a message up into its messages
+// and send each as a separate cmix message. It will return
+// the list of all rounds sent on, a unique ID for the
+// message, and the timestamp sent on.
+// the recipient must already have an e2e relationship,
+// otherwise an error will be returned.
+// Will return an error if the network is not healthy or in
+// the event of a failed send
 func (m *manager) SendE2E(mt catalog.MessageType, recipient *id.ID,
-	payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error) {
+	payload []byte, params Params) (e2e.SendReport, error) {
 
 	if !m.net.IsHealthy() {
-		return nil, e2e.MessageID{}, time.Time{},
+		return e2e.SendReport{},
 			errors.New("cannot sendE2E when network is not healthy")
 	}
 
@@ -35,18 +48,18 @@ func (m *manager) SendE2E(mt catalog.MessageType, recipient *id.ID,
 		params.Critical = false
 	}
 
-	rounds, msgID, t, err := m.sendE2E(mt, recipient, payload, params)
+	sendReport, err := m.sendE2E(mt, recipient, payload, params)
 
 	if handleCritical {
-		m.crit.handle(mt, recipient, payload, rounds, err)
+		m.crit.handle(mt, recipient, payload, sendReport.RoundList, err)
 	}
-	return rounds, msgID, t, err
+	return sendReport, err
 
 }
 
 // sendE2eFn contains a prepared sendE2E operation and sends an E2E message when
 // called, returning the results of the send.
-type sendE2eFn func() ([]id.Round, e2e.MessageID, time.Time, error)
+type sendE2eFn func() (e2e.SendReport, error)
 
 // prepareSendE2E makes a prepared function that does the e2e send.
 // This is so that when doing deletePartner we can prepare the send before
@@ -63,41 +76,46 @@ func (m *manager) prepareSendE2E(mt catalog.MessageType, recipient *id.ID,
 	partitions, internalMsgId, err := m.partitioner.Partition(recipient,
 		mt, ts, payload)
 	if err != nil {
-		return nil, errors.WithMessage(err, "failed to send unsafe message")
+		return nil, errors.WithMessage(err,
+			"failed to send unsafe message")
 	}
 
-	jww.INFO.Printf("E2E sending %d messages to %s", len(partitions), recipient)
+	jww.INFO.Printf("E2E sending %d messages to %s",
+		len(partitions), recipient)
 
-	// When sending E2E messages, we first partition into cMix packets and then
-	// send each partition over cMix
+	// When sending E2E messages, we first partition into cMix
+	// packets and then send each partition over cMix
 	roundIds := make([]id.Round, len(partitions))
 	errCh := make(chan error, len(partitions))
 
-	// The Key manager for the partner (recipient) ensures single use of each
-	// key negotiated for the ratchet
+	// The Key manager for the partner (recipient) ensures single
+	// use of each key negotiated for the ratchet
 	partner, err := m.Ratchet.GetPartner(recipient)
 	if err != nil {
 		return nil, errors.WithMessagef(err,
-			"cannot send E2E message no relationship found with %s", recipient)
+			"cannot send E2E message no relationship found with %s",
+			recipient)
 	}
 
 	msgID := e2e.NewMessageID(
 		partner.SendRelationshipFingerprint(), internalMsgId)
 
 	wg := sync.WaitGroup{}
-
+	var keyResidue e2e.KeyResidue
 	for i, p := range partitions {
 		if mt != catalog.KeyExchangeTrigger {
 			// Check if any rekeys need to happen and trigger them
-			rekeySendFunc := func(mt catalog.MessageType, recipient *id.ID,
+			rekeySendFunc := func(mt catalog.MessageType,
+				recipient *id.ID,
 				payload []byte, cmixParams cmix.CMIXParams) (
-				[]id.Round, e2e.MessageID, time.Time, error) {
+				e2e.SendReport, error) {
 				par := params
 				par.CMIXParams = cmixParams
 				return m.SendE2E(mt, recipient, payload, par)
 			}
-			rekey.CheckKeyExchanges(m.net.GetInstance(), m.grp, rekeySendFunc,
-				m.events, partner, m.rekeyParams, 1*time.Minute)
+			rekey.CheckKeyExchanges(m.net.GetInstance(), m.grp,
+				rekeySendFunc, m.events, partner,
+				m.rekeyParams, 1*time.Minute)
 		}
 
 		var keyGetter func() (session.Cypher, error)
@@ -107,23 +125,29 @@ func (m *manager) prepareSendE2E(mt catalog.MessageType, recipient *id.ID,
 			keyGetter = partner.PopSendCypher
 		}
 
-		// FIXME: remove this wait, it is weird. Why is it here? we cant remember.
+		// FIXME: remove this wait, it is weird. Why is it
+		// here? we cant remember.
 		key, err := waitForKey(
-			keyGetter, params.KeyGetRetryCount, params.KeyGeRetryDelay,
+			keyGetter, params.KeyGetRetryCount,
+			params.KeyGeRetryDelay,
 			params.Stop, recipient, format.DigestContents(p), i)
 		if err != nil {
 			return nil, errors.WithMessagef(err,
 				"Failed to get key for end-to-end encryption")
 		}
 
-		// This does not encrypt for cMix but instead end-to-end encrypts the
-		// cMix message
-		contentsEnc, mac := key.Encrypt(p)
+		// This does not encrypt for cMix but instead
+		// end-to-end encrypts the cMix message
+		contentsEnc, mac, residue := key.Encrypt(p)
+		// Carry the first key residue to the top level
+		if i == 0 {
+			keyResidue = residue
+		}
 
-		jww.INFO.Printf(
-			"E2E sending %d/%d to %s with key fp: %s, msgID: %s (msgDigest %s)",
-			i+i, len(partitions), recipient, key.Fingerprint(), msgID,
-			format.DigestContents(p))
+		jww.INFO.Printf("E2E sending %d/%d to %s with key fp: %s, "+
+			"msgID: %s (msgDigest %s)",
+			i+i, len(partitions), recipient, key.Fingerprint(),
+			msgID, format.DigestContents(p))
 
 		var s message.Service
 		if i == len(partitions)-1 {
@@ -132,16 +156,20 @@ func (m *manager) prepareSendE2E(mt catalog.MessageType, recipient *id.ID,
 			s = partner.MakeService(params.ServiceTag)
 		}
 
-		// We send each partition in its own thread here; some may send in round
-		// X, others in X+1 or X+2, and so on
+		// We send each partition in its own thread here; some
+		// may send in round X, others in X+1 or X+2, and so
+		// on
 		localI := i
 		thisSendFunc := func() {
 			wg.Add(1)
 			go func(i int) {
 				var err error
 				roundIds[i], _, err = m.net.Send(recipient,
-					key.Fingerprint(), s, contentsEnc, mac, params.CMIXParams)
+					key.Fingerprint(), s, contentsEnc, mac,
+					params.CMIXParams)
 				if err != nil {
+					jww.DEBUG.Printf("[E2E] cMix error on "+
+						"Send: %+v", err)
 					errCh <- err
 				}
 				wg.Done()
@@ -150,7 +178,7 @@ func (m *manager) prepareSendE2E(mt catalog.MessageType, recipient *id.ID,
 		sendFuncs = append(sendFuncs, thisSendFunc)
 	}
 
-	sendE2E = func() ([]id.Round, e2e.MessageID, time.Time, error) {
+	sendE2E = func() (e2e.SendReport, error) {
 		for i := range sendFuncs {
 			sendFuncs[i]()
 		}
@@ -161,27 +189,33 @@ func (m *manager) prepareSendE2E(mt catalog.MessageType, recipient *id.ID,
 		if numFail > 0 {
 			jww.INFO.Printf("Failed to E2E send %d/%d to %s",
 				numFail, len(partitions), recipient)
-			return nil, e2e.MessageID{}, time.Time{}, errors.Errorf(
+			return e2e.SendReport{}, errors.Errorf(
 				"Failed to E2E send %v/%v sub payloads: %s",
 				numFail, len(partitions), errRtn)
 		} else {
 			jww.INFO.Printf("Successfully E2E sent %d/%d to %s",
-				len(partitions)-numFail, len(partitions), recipient)
+				len(partitions)-numFail, len(partitions),
+				recipient)
 		}
 
-		jww.INFO.Printf("Successful E2E Send of %d messages to %s with msgID %s",
-			len(partitions), recipient, msgID)
+		jww.INFO.Printf("Successful E2E Send of %d messages to %s "+
+			"with msgID %s", len(partitions), recipient, msgID)
 
-		return roundIds, msgID, ts, nil
+		return e2e.SendReport{
+			RoundList:  roundIds,
+			MessageId:  msgID,
+			SentTime:   ts,
+			KeyResidue: keyResidue,
+		}, nil
 	}
 	return sendE2E, nil
 }
 
 func (m *manager) sendE2E(mt catalog.MessageType, recipient *id.ID,
-	payload []byte, params Params) ([]id.Round, e2e.MessageID, time.Time, error) {
+	payload []byte, params Params) (e2e.SendReport, error) {
 	sendFunc, err := m.prepareSendE2E(mt, recipient, payload, params)
 	if err != nil {
-		return nil, e2e.MessageID{}, time.Time{}, err
+		return e2e.SendReport{}, err
 	}
 	return sendFunc()
 }
diff --git a/e2e/sendE2E_test.go b/e2e/sendE2E_test.go
index d316b90c79a388ac32c405542e49d882089138d1..12f89ce15a2fdf692f8b82ceecddebf7e8bd25f8 100644
--- a/e2e/sendE2E_test.go
+++ b/e2e/sendE2E_test.go
@@ -22,6 +22,7 @@ import (
 	"gitlab.com/elixxir/client/storage/versioned"
 	"gitlab.com/elixxir/crypto/cyclic"
 	dh "gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/elixxir/primitives/format"
@@ -106,7 +107,7 @@ func Test_manager_SendE2E_Smoke(t *testing.T) {
 
 	payload := []byte("My Payload")
 	p := GetDefaultParams()
-	_, _, _, err = m1.SendE2E(catalog.NoType, partnerID, payload, p)
+	_, err = m1.SendE2E(catalog.NoType, partnerID, payload, p)
 	if err != nil {
 		t.Errorf("SendE2E failed: %+v", err)
 	}
@@ -221,11 +222,15 @@ type mockWaitForKeyCypher struct {
 	cypherNum int
 }
 
-func (m *mockWaitForKeyCypher) GetSession() *session.Session           { return nil }
-func (m *mockWaitForKeyCypher) Fingerprint() format.Fingerprint        { return format.Fingerprint{} }
-func (m *mockWaitForKeyCypher) Encrypt([]byte) ([]byte, []byte)        { return nil, nil }
-func (m *mockWaitForKeyCypher) Decrypt(format.Message) ([]byte, error) { return nil, nil }
-func (m *mockWaitForKeyCypher) Use()                                   {}
+func (m *mockWaitForKeyCypher) GetSession() *session.Session    { return nil }
+func (m *mockWaitForKeyCypher) Fingerprint() format.Fingerprint { return format.Fingerprint{} }
+func (m *mockWaitForKeyCypher) Encrypt([]byte) ([]byte, []byte, e2e.KeyResidue) {
+	return nil, nil, e2e.KeyResidue{}
+}
+func (m *mockWaitForKeyCypher) Decrypt(format.Message) ([]byte, e2e.KeyResidue, error) {
+	return nil, e2e.KeyResidue{}, nil
+}
+func (m *mockWaitForKeyCypher) Use() {}
 
 // Tests that getSendErrors returns all the errors on the channel.
 func Test_getSendErrors(t *testing.T) {
diff --git a/e2e/unsafeProcessor.go b/e2e/unsafeProcessor.go
index 3cd173d525244ea2bdb15cd1c28fe4af1847ce58..741ae088b71f185c32a6cd6d53d2ed769199f627 100644
--- a/e2e/unsafeProcessor.go
+++ b/e2e/unsafeProcessor.go
@@ -33,8 +33,9 @@ func (up *UnsafeProcessor) Process(ecrMsg format.Message,
 	}
 
 	//Parse
-	message, done := up.m.partitioner.HandlePartition(sender,
-		ecrMsg.GetContents(), nil)
+	// todo: handle residue here
+	message, _, done := up.m.partitioner.HandlePartition(sender,
+		ecrMsg.GetContents(), nil, e2e.KeyResidue{})
 
 	if done {
 		message.RecipientID = receptionID.Source
diff --git a/fileTransfer/connect/send.go b/fileTransfer/connect/send.go
index 2b00c8ba71cc8817a2764025ae34f8e4bdcc3438..0f0c0df1904aed876f52fa9342a116e79260b8d7 100644
--- a/fileTransfer/connect/send.go
+++ b/fileTransfer/connect/send.go
@@ -45,7 +45,7 @@ func sendNewFileTransferMessage(
 	params.LastServiceTag = catalog.Silent
 	params.DebugTag = initialMessageDebugTag
 
-	_, _, _, err := connectionHandler.SendE2E(
+	_, err := connectionHandler.SendE2E(
 		catalog.NewFileTransfer, transferInfo, params)
 	if err != nil {
 		return errors.Errorf(errNewFtSendE2e, err)
@@ -65,7 +65,7 @@ func sendEndFileTransferMessage(cmix ft.Cmix, connectionHandler connection) {
 				params.LastServiceTag = catalog.EndFT
 				params.DebugTag = lastMessageDebugTag
 
-				_, _, _, err := connectionHandler.SendE2E(
+				_, err := connectionHandler.SendE2E(
 					catalog.EndFileTransfer, nil, params)
 				if err != nil {
 					jww.ERROR.Printf(errEndFtSendE2e, err)
diff --git a/fileTransfer/connect/utils_test.go b/fileTransfer/connect/utils_test.go
index 7d0fb6aeba668c2fcccf89cd881746007a84a101..400b5288f6a148eee8dd82b13f5e1085e0f73327 100644
--- a/fileTransfer/connect/utils_test.go
+++ b/fileTransfer/connect/utils_test.go
@@ -26,7 +26,7 @@ import (
 	"gitlab.com/elixxir/client/xxdk"
 	"gitlab.com/elixxir/comms/network"
 	"gitlab.com/elixxir/crypto/cyclic"
-	e2eCrypto "gitlab.com/elixxir/crypto/e2e"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/elixxir/primitives/format"
@@ -38,7 +38,6 @@ import (
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
 	"gitlab.com/xx_network/primitives/ndf"
-	"gitlab.com/xx_network/primitives/netTime"
 	"sync"
 	"testing"
 	"time"
@@ -268,7 +267,7 @@ func (m *mockConnection) GetPartner() partner.Manager {
 
 // SendE2E adds the message to the e2e handler map.
 func (m *mockConnection) SendE2E(mt catalog.MessageType, payload []byte,
-	_ e2e.Params) ([]id.Round, e2eCrypto.MessageID, time.Time, error) {
+	_ e2e.Params) (cryptoE2e.SendReport, error) {
 	m.handler.Lock()
 	defer m.handler.Unlock()
 
@@ -278,7 +277,7 @@ func (m *mockConnection) SendE2E(mt catalog.MessageType, payload []byte,
 		Sender:      m.myID,
 	})
 
-	return []id.Round{42}, e2eCrypto.MessageID{}, netTime.Now(), nil
+	return cryptoE2e.SendReport{RoundList: []id.Round{42}}, nil
 }
 
 func (m *mockConnection) RegisterListener(mt catalog.MessageType,
diff --git a/fileTransfer/connect/wrapper.go b/fileTransfer/connect/wrapper.go
index 60a3f6641da53aad60d53a29feb533180738556b..0d826daecfa137cfa001b8a482e0b47687593dc3 100644
--- a/fileTransfer/connect/wrapper.go
+++ b/fileTransfer/connect/wrapper.go
@@ -13,9 +13,8 @@ import (
 	"gitlab.com/elixxir/client/e2e/ratchet/partner"
 	"gitlab.com/elixxir/client/e2e/receive"
 	ft "gitlab.com/elixxir/client/fileTransfer"
-	e2eCrypto "gitlab.com/elixxir/crypto/e2e"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	ftCrypto "gitlab.com/elixxir/crypto/fileTransfer"
-	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
@@ -41,7 +40,7 @@ type Wrapper struct {
 type connection interface {
 	GetPartner() partner.Manager
 	SendE2E(mt catalog.MessageType, payload []byte, params e2e.Params) (
-		[]id.Round, e2eCrypto.MessageID, time.Time, error)
+		cryptoE2e.SendReport, error)
 	RegisterListener(messageType catalog.MessageType,
 		newListener receive.Listener) (receive.ListenerID, error)
 }
diff --git a/fileTransfer/e2e/send.go b/fileTransfer/e2e/send.go
index a073d0afe841a04026081b9033e0e1cc5c164062..6f19468dcb07c8b698ad9b574f3de038818fc3e2 100644
--- a/fileTransfer/e2e/send.go
+++ b/fileTransfer/e2e/send.go
@@ -46,7 +46,7 @@ func sendNewFileTransferMessage(
 	params.LastServiceTag = catalog.Silent
 	params.DebugTag = initialMessageDebugTag
 
-	_, _, _, err := e2eHandler.SendE2E(
+	_, err := e2eHandler.SendE2E(
 		catalog.NewFileTransfer, recipient, transferInfo, params)
 	if err != nil {
 		return errors.Errorf(errNewFtSendE2e, err)
@@ -66,7 +66,7 @@ func sendEndFileTransferMessage(recipient *id.ID, cmix ft.Cmix, e2eHandler e2eHa
 				params.LastServiceTag = catalog.EndFT
 				params.DebugTag = lastMessageDebugTag
 
-				_, _, _, err := e2eHandler.SendE2E(
+				_, err := e2eHandler.SendE2E(
 					catalog.EndFileTransfer, recipient, nil, params)
 				if err != nil {
 					jww.ERROR.Printf(errEndFtSendE2e, err)
diff --git a/fileTransfer/e2e/utils_test.go b/fileTransfer/e2e/utils_test.go
index 164d155740c789916f1ff5a96b3bd3c89079c61f..a185a84aafaf6120fd6a9df74cb774291ea36eeb 100644
--- a/fileTransfer/e2e/utils_test.go
+++ b/fileTransfer/e2e/utils_test.go
@@ -28,7 +28,7 @@ import (
 	"gitlab.com/elixxir/client/xxdk"
 	"gitlab.com/elixxir/comms/network"
 	"gitlab.com/elixxir/crypto/cyclic"
-	e "gitlab.com/elixxir/crypto/e2e"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/elixxir/primitives/format"
@@ -40,7 +40,6 @@ import (
 	"gitlab.com/xx_network/primitives/id"
 	"gitlab.com/xx_network/primitives/id/ephemeral"
 	"gitlab.com/xx_network/primitives/ndf"
-	"gitlab.com/xx_network/primitives/netTime"
 	"sync"
 	"time"
 )
@@ -259,7 +258,7 @@ func (m *mockE2e) StartProcesses() (stoppable.Stoppable, error) { panic("impleme
 
 // SendE2E adds the message to the e2e handler map.
 func (m *mockE2e) SendE2E(mt catalog.MessageType, recipient *id.ID,
-	payload []byte, _ e2e.Params) ([]id.Round, e.MessageID, time.Time, error) {
+	payload []byte, _ e2e.Params) (cryptoE2e.SendReport, error) {
 
 	m.handler.listeners[mt].Hear(receive.Message{
 		MessageType: mt,
@@ -268,7 +267,7 @@ func (m *mockE2e) SendE2E(mt catalog.MessageType, recipient *id.ID,
 		RecipientID: recipient,
 	})
 
-	return []id.Round{42}, e.MessageID{}, netTime.Now(), nil
+	return cryptoE2e.SendReport{RoundList: []id.Round{42}}, nil
 }
 
 func (m *mockE2e) RegisterListener(_ *id.ID, mt catalog.MessageType,
diff --git a/fileTransfer/e2e/wrapper.go b/fileTransfer/e2e/wrapper.go
index e27c281c9dfc490d271b3d225b73643b9e75cfa2..de212b0ca6a34b651e2686ee61781e4fb75db0b0 100644
--- a/fileTransfer/e2e/wrapper.go
+++ b/fileTransfer/e2e/wrapper.go
@@ -12,7 +12,7 @@ import (
 	"gitlab.com/elixxir/client/e2e"
 	"gitlab.com/elixxir/client/e2e/receive"
 	ft "gitlab.com/elixxir/client/fileTransfer"
-	e2eCrypto "gitlab.com/elixxir/crypto/e2e"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	ftCrypto "gitlab.com/elixxir/crypto/fileTransfer"
 	"gitlab.com/xx_network/primitives/id"
 	"time"
@@ -39,7 +39,7 @@ type Wrapper struct {
 // for easier testing.
 type e2eHandler interface {
 	SendE2E(mt catalog.MessageType, recipient *id.ID, payload []byte,
-		params e2e.Params) ([]id.Round, e2eCrypto.MessageID, time.Time, error)
+		params e2e.Params) (cryptoE2e.SendReport, error)
 	RegisterListener(senderID *id.ID, messageType catalog.MessageType,
 		newListener receive.Listener) receive.ListenerID
 }
diff --git a/groupChat/e2eManager_test.go b/groupChat/e2eManager_test.go
index 37e4019cb8442cf95c8537cbb412d2d4a5d1e585..1c4a777d149876b5433655aebd3c087d446f3c79 100644
--- a/groupChat/e2eManager_test.go
+++ b/groupChat/e2eManager_test.go
@@ -11,7 +11,7 @@ import (
 	"gitlab.com/elixxir/client/e2e/receive"
 	"gitlab.com/elixxir/client/stoppable"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/e2e"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/xx_network/primitives/id"
 	"sync"
 	"testing"
@@ -65,16 +65,15 @@ func (tnm *testE2eManager) GetE2eMsg(i int) testE2eMessage {
 }
 
 func (tnm *testE2eManager) SendE2E(_ catalog.MessageType, recipient *id.ID,
-	payload []byte, _ clientE2E.Params) ([]id.Round, e2e.MessageID, time.Time,
-	error) {
+	payload []byte, _ clientE2E.Params) (cryptoE2e.SendReport, error) {
 	tnm.Lock()
 	defer tnm.Unlock()
 
 	tnm.errSkip++
 	if tnm.sendErr == 1 {
-		return nil, e2e.MessageID{}, time.Time{}, errors.New("SendE2E error")
+		return cryptoE2e.SendReport{}, errors.New("SendE2E error")
 	} else if tnm.sendErr == 2 && tnm.errSkip%2 == 0 {
-		return nil, e2e.MessageID{}, time.Time{}, errors.New("SendE2E error")
+		return cryptoE2e.SendReport{}, errors.New("SendE2E error")
 	}
 
 	tnm.e2eMessages = append(tnm.e2eMessages, testE2eMessage{
@@ -82,7 +81,7 @@ func (tnm *testE2eManager) SendE2E(_ catalog.MessageType, recipient *id.ID,
 		Payload:   payload,
 	})
 
-	return []id.Round{0, 1, 2, 3}, e2e.MessageID{}, time.Time{}, nil
+	return cryptoE2e.SendReport{RoundList: []id.Round{0, 1, 2, 3}}, nil
 }
 
 func (*testE2eManager) RegisterListener(*id.ID, catalog.MessageType, receive.Listener) receive.ListenerID {
diff --git a/groupChat/interface.go b/groupChat/interface.go
index 0355c90943797cb7ab98d7e02e648ba946f1692b..d670a565c2e2a3aa16ec061980ee3f331acae19b 100644
--- a/groupChat/interface.go
+++ b/groupChat/interface.go
@@ -32,7 +32,7 @@ import (
 	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/client/xxdk"
 	"gitlab.com/elixxir/crypto/cyclic"
-	crypto "gitlab.com/elixxir/crypto/e2e"
+	cryptoE2e "gitlab.com/elixxir/crypto/e2e"
 	"gitlab.com/elixxir/crypto/fastRNG"
 	"gitlab.com/elixxir/crypto/group"
 	"gitlab.com/xx_network/primitives/id"
@@ -126,7 +126,7 @@ type groupCmix interface {
 // needed by GroupChat
 type groupE2eHandler interface {
 	SendE2E(mt catalog.MessageType, recipient *id.ID, payload []byte,
-		params e2e.Params) ([]id.Round, crypto.MessageID, time.Time, error)
+		params e2e.Params) (cryptoE2e.SendReport, error)
 	RegisterListener(senderID *id.ID, messageType catalog.MessageType,
 		newListener receive.Listener) receive.ListenerID
 	AddService(tag string, processor message.Processor) error
diff --git a/groupChat/sendRequests.go b/groupChat/sendRequests.go
index b28985361e09183b6b4d807e10c770b454386bda..ff566063a696effc99444dd024aee9891e627418 100644
--- a/groupChat/sendRequests.go
+++ b/groupChat/sendRequests.go
@@ -120,13 +120,13 @@ func (m *manager) sendRequest(memberID *id.ID, request []byte) ([]id.Round, erro
 	p.LastServiceTag = catalog.GroupRq
 	p.DebugTag = "group.Request"
 
-	rounds, _, _, err := m.getE2eHandler().SendE2E(
+	sendReport, err := m.getE2eHandler().SendE2E(
 		catalog.GroupCreationRequest, memberID, request, p)
 	if err != nil {
 		return nil, errors.Errorf(sendE2eErr, memberID, err)
 	}
 
-	return rounds, nil
+	return sendReport.RoundList, nil
 }
 
 // roundIdMap2List converts the map of round IDs to a list of round IDs.
diff --git a/restlike/connect/receiver.go b/restlike/connect/receiver.go
index d6e87c57489a4235cd0e225fb38c4c87dad4afc3..be76084b075900e9b25741bab62131281191eb6c 100644
--- a/restlike/connect/receiver.go
+++ b/restlike/connect/receiver.go
@@ -55,7 +55,7 @@ func respond(response *restlike.Message, conn connect.Connection) error {
 	}
 
 	// TODO: Parameterize params
-	_, _, _, err = conn.SendE2E(catalog.XxMessage, payload, e2e.GetDefaultParams())
+	_, err = conn.SendE2E(catalog.XxMessage, payload, e2e.GetDefaultParams())
 	if err != nil {
 		return errors.Errorf("unable to send restlike response message: %+v", err)
 	}
diff --git a/restlike/connect/request.go b/restlike/connect/request.go
index 4ea32656a2f3716481153e1955d0435311b61244..0dd35c27f71139ee9a39c3dad0ccc9eb7914532d 100644
--- a/restlike/connect/request.go
+++ b/restlike/connect/request.go
@@ -49,7 +49,8 @@ func (s *Request) Request(method restlike.Method, path restlike.URI,
 	s.Net.RegisterListener(catalog.XxMessage, &response{responseCallback: cb})
 
 	// Transmit the Message
-	_, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams)
+	// fixme: should this use the key residue?
+	_, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams)
 	if err != nil {
 		return nil, err
 	}
@@ -84,6 +85,6 @@ func (s *Request) AsyncRequest(method restlike.Method, path restlike.URI,
 	s.Net.RegisterListener(catalog.XxMessage, &response{responseCallback: cb})
 
 	// Transmit the Message
-	_, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams)
+	_, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams)
 	return err
 }
diff --git a/storage/versioned/kv.go b/storage/versioned/kv.go
index 2b8b7ae2129d5a67fdf576297a4318e229c91ff8..13f05e82c7449282c669453eefbff1a6bb03ad46 100644
--- a/storage/versioned/kv.go
+++ b/storage/versioned/kv.go
@@ -164,3 +164,9 @@ func (v *KV) GetFullKey(key string, version uint64) string {
 func (v *KV) makeKey(key string, version uint64) string {
 	return fmt.Sprintf("%s%s_%d", v.prefix, key, version)
 }
+
+// Exists returns false if the error indicates the element doesn't
+// exist.
+func (v *KV) Exists(err error) bool {
+	return ekv.Exists(err)
+}
diff --git a/ud/mockE2e_test.go b/ud/mockE2e_test.go
index c57e148fe6a63845a480bebb1aa939789b600bfc..bcdf30e75cfb5d3b5e1a4ba97282fcd099879689 100644
--- a/ud/mockE2e_test.go
+++ b/ud/mockE2e_test.go
@@ -109,7 +109,7 @@ func (m mockE2eHandler) StartProcesses() (stoppable.Stoppable, error) {
 	panic("implement me")
 }
 
-func (m mockE2eHandler) SendE2E(mt catalog.MessageType, recipient *id.ID, payload []byte, params e2e.Params) ([]id.Round, cryptoE2e.MessageID, time.Time, error) {
+func (m mockE2eHandler) SendE2E(mt catalog.MessageType, recipient *id.ID, payload []byte, params e2e.Params) (cryptoE2e.SendReport, error) {
 	//TODO implement me
 	panic("implement me")
 }