diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fd67a6b9102a97fda77e9ed716b8e546b8f75fae..ffe11f93f25c157de46f4d033b37faccc4bec62b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,7 +4,7 @@ variables:
   REPO_DIR: gitlab.com/elixxir
   REPO_NAME: crypto
   DOCKER_IMAGE: elixxirlabs/cuda-go:latest
-  MIN_CODE_COVERAGE: "95.5"
+  MIN_CODE_COVERAGE: "95.4"
 
 before_script:
   - go version || echo "Go executable not found."
diff --git a/e2e/singleUse/mac.go b/e2e/singleUse/mac.go
index 649339f16d639629402e69535abf27324ad02b5d..810076b7daee8ce5c59241a408172cb5d1ace963 100644
--- a/e2e/singleUse/mac.go
+++ b/e2e/singleUse/mac.go
@@ -10,31 +10,38 @@ package singleUse
 import (
 	"bytes"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/hash"
 )
 
-const macConstant = "macConstant"
+const macSalt = "singleUseMacSalt"
 
-// MakeMAC generates the MAC for the given base key and encrypted payload.
-func MakeMAC(baseKey *cyclic.Int, encryptedPayload []byte) []byte {
+// MakeMAC generates the MAC used in both the transmission and response CMIX
+// messages.
+func MakeMAC(key []byte, encryptedPayload []byte) []byte {
+	// Create new hash
 	h, err := hash.NewCMixHash()
 	if err != nil {
-		jww.ERROR.Panicf("Failed to create hash: %v", err)
+		jww.ERROR.Panicf("Failed to create new hash for single-use MAC: %v", err)
 	}
 
-	// Hash the key, number, and constant
-	h.Write(baseKey.Bytes())
+	// Hash the key, encrypted payload, and salt
+	h.Write(key)
 	h.Write(encryptedPayload)
-	h.Write([]byte(macConstant))
+	h.Write([]byte(macSalt))
 
-	return h.Sum(nil)
+	// Get hash bytes
+	mac := h.Sum(nil)
+
+	// Set the first bit as zero to ensure everything stays in the group
+	mac[0] &= 0b01111111
+
+	return mac
 }
 
-// VerifyMAC determines if the provided MAC is valid for the given base key and
+// VerifyMAC determines if the provided MAC is valid for the given key and
 // encrypted payload.
-func VerifyMAC(baseKey *cyclic.Int, encryptedPayload, receivedMAC []byte) bool {
-	newMAC := MakeMAC(baseKey, encryptedPayload)
+func VerifyMAC(key []byte, encryptedPayload, receivedMAC []byte) bool {
+	newMAC := MakeMAC(key, encryptedPayload)
 
 	return bytes.Equal(newMAC, receivedMAC)
 }
diff --git a/e2e/singleUse/mac_test.go b/e2e/singleUse/mac_test.go
index 1817a33fd9d2403acd64d47fd9a6f6a51e765352..e1c6a0a006cad59d5d2943fca7bcf20cc59136db 100644
--- a/e2e/singleUse/mac_test.go
+++ b/e2e/singleUse/mac_test.go
@@ -10,114 +10,175 @@ package singleUse
 import (
 	"encoding/base64"
 	"gitlab.com/elixxir/crypto/cyclic"
-	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/xx_network/crypto/large"
 	"math/rand"
 	"testing"
 )
 
 // Tests that the generated MACs do not change.
 func TestMAC_Consistency(t *testing.T) {
-	expectedMACs := []string{
-		"D7vXMT3iX/1tvFPjowoz3w5b3PbECB4EFgkR3hNzyCM=",
-		"ffW26eqwyTHERUPHbmFkUitRj7c+c/PQayfwQPLihNs=",
-		"/lyTEnFbzEEmmMjOQhq6qr0jUYE2j22ERcR4CLH9368=",
-		"vPB8eXP8BXWdxSU4a+Lp1pYVWb98COtWtR0sBngGyTs=",
-		"BuvhuSO01uM+nVQPwXlOwRzr7xchVM1dRoF9h5TC2HU=",
-		"vF5iYaFdhLtgOO5hWNpAF6jJaW4utF1MLGaz/gYb0TY=",
-		"opI2htMJEqZvvEKHAwWnTSqgvaAempY9//rRxNNFHQU=",
-		"ppGzFglPaLzytF5gJRtSFbYoidsShEBoX1cxkAJY3F4=",
-		"pdzZmlz6qdChwo7B5sChTRUpQ5zLvlH5LgndOhDHAt8=",
-		"BHP2yvbUcynr6vM61qt+18YshTsyArJqv0VCq/EI3+o=",
+	expectedMacs := []string{
+		"P5Arek8yIlKVmM4IHJTOycUph0RLeiP0emA0DCx4z90=",
+		"P9LcMFLn+q8/6SOSb9rrYpLyn3X5MMuephBSOtIF4M0=",
+		"UOPiUuATfn9C/KjmFZdA4zTfIykMb0HIgELKLbixrtk=",
+		"UYUHQLjbyDBdZn/MTNQBgrvUQPN0MnVpdYEI0iB/Q68=",
+		"fArOwzcunIaDYoIgtViaoBOkL2/v95Hm+6KRTEE6F9A=",
+		"XMhNPEyxy8q0p+bsjGHPQL5vzA+HyqUoooHXrTBEYF4=",
+		"IDJ4iTo9IcNB/oR5RP45de53SxuWsSSKlYcmGInQtoU=",
+		"Z4QqQfBp0ezUzAU2MHBUR9K0Bdl9Z3WF5Rcy553fb+8=",
+		"HthTNmjJ78iCdjMX+zSyhabsiCEE8QmH/AsDFfH8yUA=",
+		"GnE0KJAwk594cKLt6i2kHAqMJZ8DnXv7XyrByU27F/g=",
 	}
-	grp := getGrp()
 	prng := rand.New(rand.NewSource(42))
 
-	for i, expected := range expectedMACs {
-		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, grp, prng)
-		pubkey := diffieHellman.GeneratePublicKey(privKey, grp)
-		baseKey := diffieHellman.GenerateSessionKey(privKey, pubkey, grp)
-		encryptedPayload := make([]byte, 128)
+	for i, expectedMac := range expectedMacs {
+		key := make([]byte, prng.Intn(255))
+		prng.Read(key)
+		encryptedPayload := make([]byte, prng.Intn(500))
 		prng.Read(encryptedPayload)
-		testMAC := MakeMAC(baseKey, encryptedPayload)
-		testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
+		testMAC := MakeMAC(key, encryptedPayload)
+		testMacBase64 := base64.StdEncoding.EncodeToString(testMAC)
 
-		if expected != testMACBase64 {
-			t.Errorf("MakeMAC() did not return the expected MAC for the given "+
-				"base key and encrypted payload at index %d."+
-				"\nbase key: %s\nexpected: %s\nreceived: %s",
-				i, baseKey.Text(10), expected, testMACBase64)
+		if expectedMac != testMacBase64 {
+			t.Errorf("MakeMAC() did not return the expected MAC (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expectedMac, testMacBase64)
 		}
 	}
 }
 
 // Tests that all generated MACs are unique.
 func TestMAC_Unique(t *testing.T) {
-	grp := getGrp()
+	testRuns := 20
 	prng := rand.New(rand.NewSource(42))
 	MACs := make(map[string]struct {
-		key              *cyclic.Int
+		key              []byte
 		encryptedPayload []byte
-	}, 100)
+	})
 
-	for i := 0; i < 100; i++ {
-		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, grp, prng)
-		pubkey := diffieHellman.GeneratePublicKey(privKey, grp)
-		baseKey := diffieHellman.GenerateSessionKey(privKey, pubkey, grp)
-		encryptedPayload := make([]byte, 128)
+	// Test with same key but differing payloads
+	for i := 0; i < testRuns; i++ {
+		key := make([]byte, prng.Intn(32)+i)
+		prng.Read(key)
+		for j := 0; j < testRuns; j++ {
+			encryptedPayload := make([]byte, prng.Intn(500)+j)
+			prng.Read(encryptedPayload)
+
+			testMAC := MakeMAC(key, encryptedPayload)
+			testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
+
+			if _, exists := MACs[testMACBase64]; exists {
+				t.Errorf("Generated MAC collides with previously generated MAC (%d, %d)."+
+					"\ncurrent MAC:   key: %+v  encryptedPayload: %+v"+
+					"\npreviouse MAC: key: %+v  encryptedPayload: %+v"+
+					"\nMAC:           %s", i, j,
+					key, encryptedPayload, MACs[testMACBase64].key,
+					MACs[testMACBase64].encryptedPayload, testMAC)
+			} else {
+				MACs[testMACBase64] = struct {
+					key              []byte
+					encryptedPayload []byte
+				}{key, encryptedPayload}
+			}
+		}
+	}
+
+	// Test with same payload but differing keys
+	for i := 0; i < testRuns; i++ {
+		encryptedPayload := make([]byte, prng.Intn(500)+i)
 		prng.Read(encryptedPayload)
-		testMAC := MakeMAC(baseKey, encryptedPayload)
-		testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
+		for j := 0; j < testRuns; j++ {
+			key := make([]byte, prng.Intn(32)+j)
+			prng.Read(key)
 
-		if _, exists := MACs[testMACBase64]; exists {
-			t.Errorf("Generated MAC collides with previously generated MAC."+
-				"\ncurrent MAC:   baseKey: %s  encryptedPayload: %s"+
-				"\npreviouse MAC: baseKey: %s  encryptedPayload: %s"+
-				"\nMAC:           %s",
-				baseKey.Text(10),
-				base64.StdEncoding.EncodeToString(encryptedPayload),
-				MACs[testMACBase64].key.Text(10),
-				base64.StdEncoding.EncodeToString(MACs[testMACBase64].encryptedPayload),
-				base64.StdEncoding.EncodeToString(testMAC))
-		} else {
-			MACs[testMACBase64] = struct {
-				key              *cyclic.Int
-				encryptedPayload []byte
-			}{baseKey, encryptedPayload}
+			testMAC := MakeMAC(key, encryptedPayload)
+			testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
+
+			if _, exists := MACs[testMACBase64]; exists {
+				t.Errorf("Generated MAC collides with previously generated MAC (%d, %d)."+
+					"\ncurrent MAC:   key: %+v  encryptedPayload: %+v"+
+					"\npreviouse MAC: key: %+v  encryptedPayload: %+v"+
+					"\nMAC:           %s", i, j,
+					key, encryptedPayload, MACs[testMACBase64].key,
+					MACs[testMACBase64].encryptedPayload, testMAC)
+			} else {
+				MACs[testMACBase64] = struct {
+					key              []byte
+					encryptedPayload []byte
+				}{key, encryptedPayload}
+			}
 		}
 	}
 }
 
+// Happy path.
 func TestVerifyMAC(t *testing.T) {
 	expectedMACs := []string{
-		"D7vXMT3iX/1tvFPjowoz3w5b3PbECB4EFgkR3hNzyCM=",
-		"ffW26eqwyTHERUPHbmFkUitRj7c+c/PQayfwQPLihNs=",
-		"/lyTEnFbzEEmmMjOQhq6qr0jUYE2j22ERcR4CLH9368=",
-		"vPB8eXP8BXWdxSU4a+Lp1pYVWb98COtWtR0sBngGyTs=",
-		"BuvhuSO01uM+nVQPwXlOwRzr7xchVM1dRoF9h5TC2HU=",
-		"vF5iYaFdhLtgOO5hWNpAF6jJaW4utF1MLGaz/gYb0TY=",
-		"opI2htMJEqZvvEKHAwWnTSqgvaAempY9//rRxNNFHQU=",
-		"ppGzFglPaLzytF5gJRtSFbYoidsShEBoX1cxkAJY3F4=",
-		"pdzZmlz6qdChwo7B5sChTRUpQ5zLvlH5LgndOhDHAt8=",
-		"BHP2yvbUcynr6vM61qt+18YshTsyArJqv0VCq/EI3+o=",
+		"P5Arek8yIlKVmM4IHJTOycUph0RLeiP0emA0DCx4z90=",
+		"P9LcMFLn+q8/6SOSb9rrYpLyn3X5MMuephBSOtIF4M0=",
+		"UOPiUuATfn9C/KjmFZdA4zTfIykMb0HIgELKLbixrtk=",
+		"UYUHQLjbyDBdZn/MTNQBgrvUQPN0MnVpdYEI0iB/Q68=",
+		"fArOwzcunIaDYoIgtViaoBOkL2/v95Hm+6KRTEE6F9A=",
+		"XMhNPEyxy8q0p+bsjGHPQL5vzA+HyqUoooHXrTBEYF4=",
+		"IDJ4iTo9IcNB/oR5RP45de53SxuWsSSKlYcmGInQtoU=",
+		"Z4QqQfBp0ezUzAU2MHBUR9K0Bdl9Z3WF5Rcy553fb+8=",
+		"HthTNmjJ78iCdjMX+zSyhabsiCEE8QmH/AsDFfH8yUA=",
+		"GnE0KJAwk594cKLt6i2kHAqMJZ8DnXv7XyrByU27F/g=",
 	}
-	grp := getGrp()
 	prng := rand.New(rand.NewSource(42))
 
 	for i, expected := range expectedMACs {
-		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, grp, prng)
-		pubkey := diffieHellman.GeneratePublicKey(privKey, grp)
-		baseKey := diffieHellman.GenerateSessionKey(privKey, pubkey, grp)
-		encryptedPayload := make([]byte, 128)
+		key := make([]byte, prng.Intn(255))
+		prng.Read(key)
+		encryptedPayload := make([]byte, prng.Intn(500))
 		prng.Read(encryptedPayload)
-		testMAC := MakeMAC(baseKey, encryptedPayload)
-		testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
 
+		testMAC := MakeMAC(key, encryptedPayload)
+		testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
 		receivedMac, _ := base64.StdEncoding.DecodeString(expected)
 
-		if !VerifyMAC(baseKey, encryptedPayload, receivedMac) {
+		if !VerifyMAC(key, encryptedPayload, receivedMac) {
 			t.Errorf("VerifyMAC() failed for a correct MAC (%d)."+
-				"\nbase key: %s\nexpected: %s\nreceived: %s",
-				i, baseKey.Text(10), expected, testMACBase64)
+				"\nkey: %+v\nexpected: %s\nreceived: %s",
+				i, key, expected, testMACBase64)
 		}
 	}
 }
+
+// Error path: tests that bad MACs are not verified.
+func TestVerifyMAC_InvalidMacError(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+
+	for i := 0; i < 100; i++ {
+		key := make([]byte, prng.Intn(255))
+		prng.Read(key)
+		encryptedPayload := make([]byte, prng.Intn(500))
+		prng.Read(encryptedPayload)
+		expectedMac := make([]byte, prng.Intn(255))
+		prng.Read(expectedMac)
+
+		testMAC := MakeMAC(key, encryptedPayload)
+		testMACBase64 := base64.StdEncoding.EncodeToString(testMAC)
+		expectedMACBase64 := base64.StdEncoding.EncodeToString(expectedMac)
+
+		if VerifyMAC(key, encryptedPayload, expectedMac) {
+			t.Errorf("VerifyMAC() verified invalid MAC (%d)."+
+				"\nkey: %+v\nexpected: %s\nreceived: %s",
+				i, key, expectedMACBase64, testMACBase64)
+		}
+	}
+}
+
+// getGrp returns a cyclic Group for testing.
+func getGrp() *cyclic.Group {
+	primeString := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" +
+		"A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1" +
+		"4374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDE" +
+		"E386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA4" +
+		"8361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077" +
+		"096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E8" +
+		"6039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE51" +
+		"5D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"
+	p := large.NewIntFromString(primeString, 16)
+	g := large.NewInt(2)
+	return cyclic.NewGroup(p, g)
+}
diff --git a/e2e/singleUse/recipientID.go b/e2e/singleUse/recipientID.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2ec0af3e8ba4767364a193e120f6d82232e9fe3
--- /dev/null
+++ b/e2e/singleUse/recipientID.go
@@ -0,0 +1,41 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package singleUse
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/hash"
+	"gitlab.com/xx_network/primitives/id"
+)
+
+// NewRecipientID generates the recipient ID for a single-use sender. The ID is
+// generated from the hash of the unencrypted transmission payload. The
+// unencryptedPayload must contain a nonce to prevent collision on the same
+// message being sent multiple times.
+func NewRecipientID(pubKey *cyclic.Int, unencryptedPayload []byte) *id.ID {
+	// Create new hash
+	h, err := hash.NewCMixHash()
+	if err != nil {
+		jww.ERROR.Panicf("Failed to create new hash for single-use "+
+			"communication recipient ID: %v", err)
+	}
+
+	// Hash the public key and unencrypted payload
+	h.Write(pubKey.Bytes())
+	h.Write(unencryptedPayload)
+
+	// Get hash bytes
+	rid := &id.ID{}
+	copy(rid[:], h.Sum(nil))
+
+	// Set the ID type to user
+	rid.SetType(id.User)
+
+	return rid
+}
diff --git a/e2e/singleUse/recipientID_test.go b/e2e/singleUse/recipientID_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e03a1dfc9608779027b265b1141b9ecc8768c9e
--- /dev/null
+++ b/e2e/singleUse/recipientID_test.go
@@ -0,0 +1,107 @@
+package singleUse
+
+import (
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/xx_network/primitives/id"
+	"math/rand"
+	"testing"
+)
+
+// Tests that the generated IDs do not change.
+func TestNewRecipientID_Consistency(t *testing.T) {
+	expectedRIDs := []string{
+		"8weTLQUZT0NXuJL6bt9BbksEt0LLywnvvHbMIO7Is0YD",
+		"olmvbVz3Jgu/lh60yicuTWg15neBB2i/zcd9JZVz9/8D",
+		"JWJAWiCARp+uLYg70YYCYB/1bsTH7Gr3M4mi2ACYhxMD",
+		"onYyGcAjVcwr5rwqQweVxYYnYpgWWjGhiNA/CxnxmsYD",
+		"nIQ/WrgPyl07IPStrcCHTe1+rWrEAxQiFtwzA+K1IMED",
+		"tF+cgN69Xk7fAhHSzuD2diYmMpaaCsWCr1A7+Th1WUkD",
+		"7UW7O94ZB94RJM5Ku4n6/BVREOO4+m7dQgqzzmpo0xgD",
+		"ePDyChmF5Gxr/Ny/9AWgGlPMdqymJ3ivv7bg0dMDH64D",
+		"f7RPMI/NHERKj6MW+bbl0bupBgnIvnOj5Xhum/ItTn8D",
+		"MqXY49FVJoxeuRdQAwYfswf9Li3ZnCZ9fig+EbTl7BkD",
+	}
+	prng := rand.New(rand.NewSource(42))
+
+	for i, expectedRID := range expectedRIDs {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		unencryptedPayload := make([]byte, prng.Intn(500))
+		prng.Read(unencryptedPayload)
+
+		testRID := NewRecipientID(pubKey, unencryptedPayload)
+
+		if expectedRID != testRID.String() {
+			t.Errorf("NewRecipientID() did not return the expected ID (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expectedRID, testRID)
+		}
+
+		if testRID.GetType() != id.User {
+			t.Errorf("NewRecipientID() did not return expected ID type (%d)."+
+				"\nexpected: %s\nreceived: %s", i, id.User, testRID.GetType())
+		}
+	}
+}
+
+// Tests that all generated IDs are unique.
+func TestNewRecipientID_Unique(t *testing.T) {
+	testRuns := 20
+	prng := rand.New(rand.NewSource(42))
+	IDs := make(map[*id.ID]struct {
+		pubKey           *cyclic.Int
+		encryptedPayload []byte
+	})
+
+	// Test with same public key but differing payloads
+	for i := 0; i < testRuns; i++ {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength+i, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		for j := 0; j < testRuns; j++ {
+			unencryptedPayload := make([]byte, prng.Intn(500)+j)
+			prng.Read(unencryptedPayload)
+
+			testID := NewRecipientID(pubKey, unencryptedPayload)
+
+			if _, exists := IDs[testID]; exists {
+				t.Errorf("Generated ID collides with previously generated ID (%d, %d)."+
+					"\ncurrent ID:   key: %s  unencryptedPayload: %+v"+
+					"\npreviouse ID: key: %s  unencryptedPayload: %+v"+
+					"\nID:           %s", i, j,
+					pubKey.Text(10), unencryptedPayload, IDs[testID].pubKey.Text(10),
+					IDs[testID].encryptedPayload, testID)
+			} else {
+				IDs[testID] = struct {
+					pubKey           *cyclic.Int
+					encryptedPayload []byte
+				}{pubKey, unencryptedPayload}
+			}
+		}
+	}
+
+	// Test with same payload but differing public key
+	for i := 0; i < testRuns; i++ {
+		unencryptedPayload := make([]byte, prng.Intn(500)+i)
+		prng.Read(unencryptedPayload)
+		for j := 0; j < testRuns; j++ {
+			privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength+j, getGrp(), prng)
+			pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+
+			testID := NewRecipientID(pubKey, unencryptedPayload)
+
+			if _, exists := IDs[testID]; exists {
+				t.Errorf("Generated ID collides with previously generated ID (%d, %d)."+
+					"\ncurrent ID:   key: %s  unencryptedPayload: %+v"+
+					"\npreviouse ID: key: %s  unencryptedPayload: %+v"+
+					"\nID:           %s", i, j,
+					pubKey.Text(10), unencryptedPayload, IDs[testID].pubKey.Text(10),
+					IDs[testID].encryptedPayload, testID)
+			} else {
+				IDs[testID] = struct {
+					pubKey           *cyclic.Int
+					encryptedPayload []byte
+				}{pubKey, unencryptedPayload}
+			}
+		}
+	}
+}
diff --git a/e2e/singleUse/responseFingerprint.go b/e2e/singleUse/responseFingerprint.go
index 5669053ca0a8310803ae40916541c79473efae97..37f77266f9773d8c29cd3f76d73c1b47b6ecb53d 100644
--- a/e2e/singleUse/responseFingerprint.go
+++ b/e2e/singleUse/responseFingerprint.go
@@ -15,30 +15,32 @@ import (
 	"gitlab.com/elixxir/primitives/format"
 )
 
-const responseFPConstant = "responseFPConstant"
+const responseFpSalt = "singleUseResponseFingerprintSalt"
 
-func ResponseFingerprint(pubKey *cyclic.Int, keyNum uint64) format.Fingerprint {
-	// Create fingerprint
-	fp := format.Fingerprint{}
-	copy(fp[:], makeHash(pubKey, keyNum, responseFPConstant))
-
-	return fp
-}
-
-func makeHash(pubKey *cyclic.Int, keyNum uint64, constant string) []byte {
+// NewResponseFingerprint generates the fingerprint for the response message for
+// the given key number.
+func NewResponseFingerprint(dhKey *cyclic.Int, keyNum uint64) format.Fingerprint {
+	// Create new hash
 	h, err := hash.NewCMixHash()
 	if err != nil {
-		jww.ERROR.Panicf("Failed to create hash: %v", err)
+		jww.ERROR.Panicf("Failed to create new hash for single-use response "+
+			"fingerprint: %v", err)
 	}
 
-	// Convert the key number to bytes
-	buff := make([]byte, binary.MaxVarintLen64)
-	binary.BigEndian.PutUint64(buff, keyNum)
+	keyNumBytes := make([]byte, binary.MaxVarintLen64)
+	binary.BigEndian.PutUint64(keyNumBytes, keyNum)
 
-	// Hash the key, number, and constant
-	h.Write(pubKey.Bytes())
-	h.Write(buff)
-	h.Write([]byte(constant))
+	// Hash the DH key, key number, and salt
+	h.Write(dhKey.Bytes())
+	h.Write(keyNumBytes)
+	h.Write([]byte(responseFpSalt))
 
-	return h.Sum(nil)
+	// Get hash bytes
+	fp := format.Fingerprint{}
+	copy(fp[:], h.Sum(nil))
+
+	// Set the first bit as zero to ensure everything stays in the group
+	fp[0] &= 0b01111111
+
+	return fp
 }
diff --git a/e2e/singleUse/responseFingerprint_test.go b/e2e/singleUse/responseFingerprint_test.go
index bfdb6f2b4cb6c6d02b1c669f6ba4ec5d122256f1..7db2f95d0705d8142c012d6e1bb9b2933168ab25 100644
--- a/e2e/singleUse/responseFingerprint_test.go
+++ b/e2e/singleUse/responseFingerprint_test.go
@@ -11,79 +11,99 @@ import (
 	"encoding/base64"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
+	"gitlab.com/elixxir/primitives/format"
 	"math/rand"
 	"testing"
 )
 
 // Tests that the generated fingerprints do not change.
-func TestResponseFingerprint(t *testing.T) {
-	expectedFP := "H+GySzAGq3y6o1JA0Mqxn68GO1uSzCEmpVVbHR2sJc8="
-	grp := getGrp()
-	prng := rand.New(rand.NewSource(42))
-
-	pubKey := diffieHellman.GeneratePublicKey(diffieHellman.GeneratePrivateKey(
-		diffieHellman.DefaultPrivateKeyLength, grp, prng), grp)
-	testFP := ResponseFingerprint(pubKey, 0)
-	testFPBase64 := base64.StdEncoding.EncodeToString(testFP[:])
-
-	if expectedFP != testFPBase64 {
-		t.Errorf("ResponseFingerprint() did not return the expected "+
-			"fingerprint for public key %s."+
-			"\nexpected: %s\nreceived: %s",
-			pubKey.Text(10), expectedFP, testFPBase64)
+func TestNewResponseFingerprint_Consistency(t *testing.T) {
+	expectedFPs := []string{
+		"OrMBolBbKdrbOYgzErZekpk4EZkoJ+rnM1NDq86aPVs=",
+		"KO7Ri01TBy45IQY2P21R4BAe6GAYQu86tP5JYIlkkeU=",
+		"KgSF7wk/VBx506dgR2DygKoCXLCErjxWHc6WoGqe5eQ=",
+		"OXP6eKBx/pvU6WEZW9vQPq8IcaYHBzLow8zUDtXaJGQ=",
+		"c/E2DCT3iCnOVDV2kBTe2BwbxsiR9dH6ImB8oPS3nFo=",
+		"NS/WmbI3gdwBY0qfoNt+OKavi+TFYy0GKcAXtV0gIuo=",
+		"fuJQu0DiFIImUYYILEtw3Jw5iFs9z/L+32RujfTVoRo=",
+		"LvMcRLC2XTPaV5PuRJ2RTJrQTy6L0I+FwboSLiRYnfM=",
+		"ZUpBiFwcfM/Geae5nyoDVvb3fgkIbZD1HlfTfN19PYI=",
+		"SW7r5m5FuOY/hkEr/d4ze+0RJa9iXf3xRzvyUtyL6yY=",
 	}
-}
-
-// Tests that the generated fingerprints do not change.
-func Test_makeHash_Consistency(t *testing.T) {
-	expectedHashes := []string{
-		"R7UL7by0fP2bXRBlzUOrUKo+7pRATF4r6bUD8/H+PBc=",
-		"CQsWbxgdjYOIJsdfTk/w3549Ltp7TJlSUquK94O8rAM=",
-		"5+pY4khsqHh0bEXZvYC2DLEJbxq4g5bL19JsllOcfso=",
-		"FW9gcwJAl90cLcsnxdnHLcIuJO6NpXqkVWFfbXqWXmA=",
-		"peyTwZZUKPwHB5J1UptEbL+TUEjvg4ZBVceE/J1Q3pQ=",
-		"f8YCUyFXznRHQ1gzuBJrSQegE7wC1JYb8MPTbtJ+qj0=",
-		"g6pHsrATAkSl7QMn9Re01nuc5VF88yvyWvPCHKm3upk=",
-		"S0WrlEhrMIDvJ68hhY6c0Mk5FabZqoAoUdxaBrDXdhg=",
-		"2mHHoyjQExs314tLMg+6pt1cGE5j4vckWZa18pPjZmQ=",
-		"2YMO0HeFF6dCd3cKhNQAXw76zaQ573fw0TM/3PLmUGI=",
-	}
-	grp := getGrp()
 	prng := rand.New(rand.NewSource(42))
 
-	for i, expectedHash := range expectedHashes {
-		pubKey := diffieHellman.GeneratePublicKey(diffieHellman.GeneratePrivateKey(
-			diffieHellman.DefaultPrivateKeyLength, grp, prng), grp)
-		testHash := makeHash(pubKey, uint64(i), "constant")
-		testHashBase64 := base64.StdEncoding.EncodeToString(testHash)
+	for i, expectedFP := range expectedFPs {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+
+		testFP := NewResponseFingerprint(dhKey, uint64(i))
+		testFpBase64 := base64.StdEncoding.EncodeToString(testFP[:])
 
-		if expectedHash != testHashBase64 {
-			t.Errorf("makeHash() did not return the expected hash for public "+
-				"key %s at index %d.\nexpected: %s\nreceived: %s",
-				pubKey.Text(10), i, expectedHash, testHashBase64)
+		if expectedFP != testFpBase64 {
+			t.Errorf("NewResponseFingerprint() did not return the expected "+
+				"fingerprint (%d).\nexpected: %s\nreceived: %s",
+				i, expectedFP, testFpBase64)
 		}
 	}
 }
 
 // Tests that all generated fingerprints are unique.
-func Test_makeHash_Unique(t *testing.T) {
-	grp := getGrp()
+func TestNewResponseFingerprint_Unique(t *testing.T) {
+	testRuns := 20
 	prng := rand.New(rand.NewSource(42))
-	hashes := make(map[string]*cyclic.Int, 100)
+	FPs := make(map[format.Fingerprint]struct {
+		dhKey  *cyclic.Int
+		keyNum uint64
+	})
+
+	// Test with same DH key but differing key numbers
+	for i := 0; i < testRuns; i++ {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength+i, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+		for j := 0; j < testRuns; j++ {
+			testFP := NewResponseFingerprint(dhKey, uint64(j))
 
-	for i := 0; i < 100; i++ {
-		pubKey := diffieHellman.GeneratePublicKey(diffieHellman.GeneratePrivateKey(
-			diffieHellman.DefaultPrivateKeyLength, grp, prng), grp)
-		testHash := makeHash(pubKey, uint64(i), "constant")
+			if _, exists := FPs[testFP]; exists {
+				t.Errorf("Generated fingerprint collides with previously "+
+					"generated fingerprint (%d, %d)."+
+					"\ncurrent FP:   dhKey: %s  keyNum: %d"+
+					"\npreviouse FP: dhKey: %s  keyNum: %d"+
+					"\nFP:           %s", i, j,
+					dhKey.Text(10), j, FPs[testFP].dhKey.Text(10),
+					FPs[testFP].keyNum, testFP)
+			} else {
+				FPs[testFP] = struct {
+					dhKey  *cyclic.Int
+					keyNum uint64
+				}{dhKey, uint64(j)}
+			}
+		}
+	}
 
-		hashBase64 := base64.StdEncoding.EncodeToString(testHash)
+	// Test with same key numbers but differing DH keys
+	for i := 0; i < testRuns; i++ {
+		for j := 0; j < testRuns; j++ {
+			privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength+j, getGrp(), prng)
+			pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+			dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+			testFP := NewResponseFingerprint(dhKey, uint64(i))
 
-		if hashes[hashBase64] != nil {
-			t.Errorf("Generated hash from key %s collides with "+
-				"previously generated hash from key %s.\nfingerprint: %s",
-				pubKey.Text(10), hashes[hashBase64].Text(10), hashBase64)
-		} else {
-			hashes[hashBase64] = pubKey
+			if _, exists := FPs[testFP]; exists {
+				t.Errorf("Generated fingerprint collides with previously "+
+					"generated fingerprint (%d, %d)."+
+					"\ncurrent FP:   dhKey: %s  keyNum: %d"+
+					"\npreviouse FP: dhKey: %s  keyNum: %d"+
+					"\nFP:           %s", i, j,
+					dhKey.Text(10), i, FPs[testFP].dhKey.Text(10),
+					FPs[testFP].keyNum, testFP)
+			} else {
+				FPs[testFP] = struct {
+					dhKey  *cyclic.Int
+					keyNum uint64
+				}{dhKey, uint64(i)}
+			}
 		}
 	}
 }
diff --git a/e2e/singleUse/responseKey.go b/e2e/singleUse/responseKey.go
index a506757b7f45788e282a3053bba49bffd91e3479..b5ada4327670845424156f6659599b92da4c1e35 100644
--- a/e2e/singleUse/responseKey.go
+++ b/e2e/singleUse/responseKey.go
@@ -8,11 +8,32 @@
 package singleUse
 
 import (
+	"encoding/binary"
+	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/hash"
 )
 
-const responseKeyConstant = "responseKeyConstant"
+const responseKeySalt = "singleUseResponseKeySalt"
 
-func ResponseKey(pubKey *cyclic.Int, keyNum uint64) []byte {
-	return makeHash(pubKey, keyNum, responseKeyConstant)
+// NewResponseKey generates the key for the response message that corresponds
+// with the given key number.
+func NewResponseKey(dhKey *cyclic.Int, keyNum uint64) []byte {
+	// Create new hash
+	h, err := hash.NewCMixHash()
+	if err != nil {
+		jww.ERROR.Panicf("Failed to create new hash for single-use response "+
+			"key: %v", err)
+	}
+
+	keyNumBytes := make([]byte, binary.MaxVarintLen64)
+	binary.BigEndian.PutUint64(keyNumBytes, keyNum)
+
+	// Hash the DH key, key number, and salt
+	h.Write(dhKey.Bytes())
+	h.Write(keyNumBytes)
+	h.Write([]byte(responseKeySalt))
+
+	// Get hash bytes
+	return h.Sum(nil)
 }
diff --git a/e2e/singleUse/responseKey_test.go b/e2e/singleUse/responseKey_test.go
index 424eee618d697621a03a18ac07b099ee58e61d77..5a981f979f5bd5f28bf84bea91333f62d45feca8 100644
--- a/e2e/singleUse/responseKey_test.go
+++ b/e2e/singleUse/responseKey_test.go
@@ -9,25 +9,101 @@ package singleUse
 
 import (
 	"encoding/base64"
+	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
 	"math/rand"
 	"testing"
 )
 
 // Tests that the generated key does not change.
-func TestResponseKey(t *testing.T) {
-	expectedKey := "6H61xTtJZYhmT8Q2cm3ZXj7J95yHO5lQN/s1cRu/j40="
-	grp := getGrp()
+func TestNewResponseKey_Consistency(t *testing.T) {
+	expectedKeys := []string{
+		"gMv3mjIgCpTgqx/1d0+k3jYlfkPTM2DtOGYzUKGHwK4=",
+		"k5E9tfudE5W8APDNvZ9XfHuxuivsefcP3IhOBACh7Mg=",
+		"iqQrsHI7bmJGxqHC6bUqXgt03VeGSHHSRBGZ0RrEvP8=",
+		"+taXBcQ+4IB00gvkjQXia6Y6BJJ05vYaO2Ou+8yTd+M=",
+		"PiaH5qrVUdRCKwyf2Kic/M8rqm+q4UNvoVmFFI51k8Y=",
+		"jpKUMoteRvQVJnDsTi4pjw0XL5P4U5rhWaK+0RaKVVw=",
+		"2zx20KGB9xBObQ8cwF3FzpCwApiV8Du/FL2+Ykmh/ck=",
+		"hVYAGYQtd3x0FgXuavULI8saisTjs4rJamebd5aNzQ8=",
+		"zN+Cl7h5KvFGcGPLUVZ0Qjiws5LNUXXhuDUUw6osSfU=",
+		"MVzZoMffLm7Bi7XAuOZxYFxAzeNqxUAG6kZCSCKJuDE=",
+	}
 	prng := rand.New(rand.NewSource(42))
 
-	pubKey := diffieHellman.GeneratePublicKey(diffieHellman.GeneratePrivateKey(
-		diffieHellman.DefaultPrivateKeyLength, grp, prng), grp)
-	testKey := ResponseKey(pubKey, 0)
-	testKeyBase64 := base64.StdEncoding.EncodeToString(testKey)
+	for i, expectedKey := range expectedKeys {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+
+		testKey := NewResponseKey(dhKey, uint64(i))
+		testKeyBase64 := base64.StdEncoding.EncodeToString(testKey)
+
+		if expectedKey != testKeyBase64 {
+			t.Errorf("NewResponseKey() did not return the expected key (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expectedKey, testKeyBase64)
+		}
+	}
+}
+
+// Tests that all generated keys are unique.
+func TestNewResponseKey_Unique(t *testing.T) {
+	testRuns := 20
+	prng := rand.New(rand.NewSource(42))
+	keys := make(map[string]struct {
+		dhKey  *cyclic.Int
+		keyNum uint64
+	})
+
+	// Test with same DH key but differing key numbers
+	for i := 0; i < testRuns; i++ {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength+i, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+		for j := 0; j < testRuns; j++ {
+			testKey := NewResponseKey(dhKey, uint64(j))
+			testKeyBase64 := base64.StdEncoding.EncodeToString(testKey)
+
+			if _, exists := keys[testKeyBase64]; exists {
+				t.Errorf("Generated key collides with previously generated "+
+					"key (%d, %d)."+
+					"\ncurrent key:   dhKey: %s  keyNum: %d"+
+					"\npreviouse key: dhKey: %s  keyNum: %d"+
+					"\nkey:           %s", i, j,
+					dhKey.Text(10), j, keys[testKeyBase64].dhKey.Text(10),
+					keys[testKeyBase64].keyNum, testKeyBase64)
+			} else {
+				keys[testKeyBase64] = struct {
+					dhKey  *cyclic.Int
+					keyNum uint64
+				}{dhKey, uint64(j)}
+			}
+		}
+	}
+
+	// Test with same key number but differing DH keys
+	for i := 0; i < testRuns; i++ {
+		for j := 0; j < testRuns; j++ {
+			privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength+j, getGrp(), prng)
+			pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+			dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+			testKey := NewResponseKey(dhKey, uint64(i))
+			testKeyBase64 := base64.StdEncoding.EncodeToString(testKey)
 
-	if expectedKey != testKeyBase64 {
-		t.Errorf("ResponseKey() did not return the expected key for public "+
-			"key %s.\nexpected: %s\nreceived: %s",
-			pubKey.Text(10), expectedKey, testKeyBase64)
+			if _, exists := keys[testKeyBase64]; exists {
+				t.Errorf("Generated key collides with previously generated "+
+					"key (%d, %d)."+
+					"\ncurrent key:   dhKey: %s  keyNum: %d"+
+					"\npreviouse key: dhKey: %s  keyNum: %d"+
+					"\nkey:           %s", i, j,
+					dhKey.Text(10), i, keys[testKeyBase64].dhKey.Text(10),
+					keys[testKeyBase64].keyNum, testKeyBase64)
+			} else {
+				keys[testKeyBase64] = struct {
+					dhKey  *cyclic.Int
+					keyNum uint64
+				}{dhKey, uint64(i)}
+			}
+		}
 	}
 }
diff --git a/e2e/singleUse/tagFingerprint.go b/e2e/singleUse/tagFingerprint.go
new file mode 100644
index 0000000000000000000000000000000000000000..ac45059738f8baaf6348a4ea6bf973dcee3144dc
--- /dev/null
+++ b/e2e/singleUse/tagFingerprint.go
@@ -0,0 +1,55 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package singleUse
+
+import (
+	"encoding/base64"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/hash"
+)
+
+const tagFpSalt = "singleUseTagFingerprintSalt"
+const TagFpSize = 16
+
+type TagFP [TagFpSize]byte
+
+// NewTagFP generates the tag fingerprint used to identify the module that the
+// transmission message belongs to. The tag can be anything, but should be long
+// enough so that it is unique.
+func NewTagFP(tag string) TagFP {
+	// Create new hash
+	h, err := hash.NewCMixHash()
+	if err != nil {
+		jww.ERROR.Panicf("Failed to create new hash for single-use "+
+			"communication tag fingerprint: %v", err)
+	}
+
+	// Hash tag and salt
+	h.Write([]byte(tag))
+	h.Write([]byte(tagFpSalt))
+
+	// Get hash bytes
+	return UnmarshalTagFP(h.Sum(nil))
+}
+
+// UnmarshalTagFP generates a new TagFP from the specified bytes.
+func UnmarshalTagFP(b []byte) TagFP {
+	var tagFp TagFP
+	copy(tagFp[:], b[:])
+	return tagFp
+}
+
+// Bytes returns the tag fingerprint as a byte slice.
+func (fp TagFP) Bytes() []byte {
+	return fp[:]
+}
+
+// String returns the base64 string encoding of the tag fingerprint.
+func (fp TagFP) String() string {
+	return base64.StdEncoding.EncodeToString(fp[:])
+}
diff --git a/e2e/singleUse/tagFingerprint_test.go b/e2e/singleUse/tagFingerprint_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e6289b239d91b89707d18cb5024ddd661c3f87c
--- /dev/null
+++ b/e2e/singleUse/tagFingerprint_test.go
@@ -0,0 +1,120 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package singleUse
+
+import (
+	"bytes"
+	"encoding/base64"
+	"math/rand"
+	"testing"
+)
+
+// Tests that the generated tag fingerprint does not change.
+func TestNewTagFP_Consistency(t *testing.T) {
+	expectedFPs := []string{
+		"m0cTfRUOrBSMCcsuDFubEA==",
+		"n2wr2FhimigxeaKMQwoOww==",
+		"gQFbF80cFWmRFJCKTcWAtg==",
+		"6B330SPGBjmFIoBwkdkqhg==",
+		"a+AuHpTbVLxbO/wNS6QPAg==",
+		"Npgjn6oPpzAgDe2gb3VPHQ==",
+		"vg0K64iRRVFdNs181ToEtg==",
+		"w9LCSCCo76301pzLKGpvVQ==",
+		"/7ZRLcScR6xlAkjPLHy3rg==",
+		"yV37d2Zs0X+OJp9AlRWhjQ==",
+	}
+	prng := rand.New(rand.NewSource(42))
+
+	for i, expectedFP := range expectedFPs {
+		tag := make([]byte, prng.Intn(255))
+		prng.Read(tag)
+
+		testFP := NewTagFP(string(tag))
+
+		if expectedFP != testFP.String() {
+			t.Errorf("NewTagFP() did not return the expected fingerprint (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expectedFP, testFP)
+		}
+	}
+}
+
+// Tests that all generated tag fingerprints are unique.
+func TestNewTagFP_Unique(t *testing.T) {
+	testRuns := 20
+	prng := rand.New(rand.NewSource(42))
+	FPs := make(map[TagFP]string)
+
+	// Test with differing tag strings
+	for i := 0; i < testRuns; i++ {
+		tag := make([]byte, prng.Intn(255)+i)
+		prng.Read(tag)
+		testTagFP := NewTagFP(string(tag))
+
+		if _, exists := FPs[testTagFP]; exists {
+			t.Errorf("Generated tag fingerprint collides with previously "+
+				"generated tag fingerprint (%d)."+
+				"\ncurrent tag:   %s\npreviouse tag: %s\nFP:           %s",
+				i, tag, FPs[testTagFP], testTagFP)
+		} else {
+			FPs[testTagFP] = string(tag)
+		}
+	}
+}
+
+// Happy path.
+func TestUnmarshalTagFP(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	fpBytes := make([]byte, TagFpSize)
+	prng.Read(fpBytes)
+
+	fp := UnmarshalTagFP(fpBytes)
+	if !bytes.Equal(fpBytes, fp[:]) {
+		t.Errorf("UnmarshalTagFP() failed to copy the correct bytes into the tag "+
+			"fingerprint.\nexpected: %+v\nreceived: %+v", fpBytes, fp)
+	}
+
+	// Ensure that the data is copied
+	fpBytes[2]++
+	if fp[2] == fpBytes[2] {
+		t.Errorf("UnmarshalTagFP() failed to create a copy of the data.")
+	}
+}
+
+// Happy path.
+func TestTagFP_Bytes(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	fpBytes := make([]byte, TagFpSize)
+	prng.Read(fpBytes)
+
+	fp := UnmarshalTagFP(fpBytes)
+	testFpBytes := fp.Bytes()
+	if !bytes.Equal(fpBytes, testFpBytes) {
+		t.Errorf("Bytes() failed to return the expected bytes."+
+			"\nexpected: %+v\nreceived: %+v", fpBytes, testFpBytes)
+	}
+
+	// Ensure that the data is copied
+	testFpBytes[2]++
+	if fp[2] == testFpBytes[2] {
+		t.Errorf("Bytes() failed to create a copy of the data.")
+	}
+}
+
+// Happy path.
+func TestTagFP_String(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	fpBytes := make([]byte, TagFpSize)
+	prng.Read(fpBytes)
+	fp := UnmarshalTagFP(fpBytes)
+
+	expectedString := base64.StdEncoding.EncodeToString(fpBytes)
+	if expectedString != fp.String() {
+		t.Errorf("String() failed to return the expected string."+
+			"\nexpected: %s\nreceived: %s", expectedString, fp.String())
+	}
+}
diff --git a/e2e/singleUse/transmitFingerprint.go b/e2e/singleUse/transmitFingerprint.go
index 1c4aaa8ece5db4b08811cd9b4b1f866d4230b8b5..3969e42d469844ef118dde46acd008991336cab8 100644
--- a/e2e/singleUse/transmitFingerprint.go
+++ b/e2e/singleUse/transmitFingerprint.go
@@ -14,23 +14,28 @@ import (
 	"gitlab.com/elixxir/primitives/format"
 )
 
-const transmitFPConstant = "transmitFPConstant"
+const transmitFpSalt = "singleUseTransmitFingerprintSalt"
 
-func TransmitFingerprint(pubKey *cyclic.Int) format.Fingerprint {
+// NewTransmitFingerprint generates the fingerprint used for the transmission
+// message.
+func NewTransmitFingerprint(pubKey *cyclic.Int) format.Fingerprint {
 	// Create new hash
 	h, err := hash.NewCMixHash()
 	if err != nil {
-		jww.ERROR.Panicf("Failed to create hash: %v", err)
+		jww.ERROR.Panicf("Failed to create new hash for single-use "+
+			"transmission fingerprint: %v", err)
 	}
 
-	// Hash the key and constant
+	// Hash the public key and salt
 	h.Write(pubKey.Bytes())
-	h.Write([]byte(transmitFPConstant))
-	keyHash := h.Sum(nil)
+	h.Write([]byte(transmitFpSalt))
 
-	// Create fingerprint
+	// Get hash bytes
 	fp := format.Fingerprint{}
-	copy(fp[:], keyHash)
+	copy(fp[:], h.Sum(nil))
+
+	// Set the first bit as zero to ensure everything stays in the group
+	fp[0] &= 0b01111111
 
 	return fp
 }
diff --git a/e2e/singleUse/transmitFingerprint_test.go b/e2e/singleUse/transmitFingerprint_test.go
index a0d8787bbc1de700ac865de5a64fd4aa903b8cba..3d4676ea339cd556a3f5a2780f4e3c6682375db2 100644
--- a/e2e/singleUse/transmitFingerprint_test.go
+++ b/e2e/singleUse/transmitFingerprint_test.go
@@ -12,75 +12,59 @@ import (
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/crypto/diffieHellman"
 	"gitlab.com/elixxir/primitives/format"
-	"gitlab.com/xx_network/crypto/large"
 	"math/rand"
 	"testing"
 )
 
 // Tests that the generated fingerprints do not change.
-func TestTransmitFingerprint_Consistency(t *testing.T) {
+func TestNewTransmitFingerprint_Consistency(t *testing.T) {
 	expectedFPs := []string{
-		"8xef7y86YmR6+qVBaVghPYbEd0j7seLUxR1v1dxqiNo=",
-		"LJ1HQ8zZPY3z+6UkKZMSDN2WymMmwuWW3GCjtAGfUlc=",
-		"Fc8CN69uMxT1zBAr3Ed/AOy6Py3XDb3i7LngRsax0K8=",
-		"4mvA+dxgIy/UxRVIvzFgGtEuMoWI8RodF7JMFIO5xcI=",
-		"3YJPhLeI2+RawlSMUTEwTP1iJrFAa5zboCf5fjc/k3o=",
-		"mEOS0pDuNYS5hpNHeJ2IhzlZFf9J5oHRvFpovNwWS7c=",
-		"i3k7dv1Nch1oTT4aOxbBDifmf7+mT34ErFFaWW5LDb8=",
-		"ck/wkZ3lv0bcQuW+Z2dw37m+Re0URPw4UsWBPAWBEtw=",
-		"k/YRXaE2dplVPZSiEY+7C0e6GlDeR3jLrIPRWgeL3cI=",
-		"qkT2Hfz8A7jNxOhDYG266b/hvWcQtAx0ay67Az80bAY=",
+		"Esl9VY4LNOmXqmmEpZfu0Koo/++Zqx/vDSqBFFpdvjI=",
+		"ASz49SEnYLLW7KkVLbHJiYOAlkkY1r/AJBHaR2s1UDk=",
+		"ZxN1S6SBESGAB+2LpKQv18rXqVtwANe19AK2dXckpiM=",
+		"CNZUU0N7gmkjg+/rCZZaZz3NNfLA8zQychvFuhdBa6s=",
+		"blMhqREthbMo9HInS4gq85k4oLOq4L5WlT+U1yFGfYQ=",
+		"b//RiQzsN1BG6pYufzZFmRyrkHP5f4+GaYIutfUZlNw=",
+		"VRbcFvAMFf8SvYLGf2aL9jlenwNG45BzZbuhFbrOZ9Y=",
+		"TiQPO2mHY4Qv/Kr+jqMDiokIWoE6HSn5exlwQw7iK+Y=",
+		"NG21WIBMqCTB4zZAb6CWvQ+B0080zh5e4BQ9Xzh52cI=",
+		"TrA7y8S2NayhqYWPyZDs5q+rC+mlo/CXBt3P6bsmGwQ=",
 	}
-	grp := getGrp()
 	prng := rand.New(rand.NewSource(42))
 
-	for i, expected := range expectedFPs {
-		pubKey := diffieHellman.GeneratePublicKey(diffieHellman.GeneratePrivateKey(
-			diffieHellman.DefaultPrivateKeyLength, grp, prng), grp)
-		testFP := TransmitFingerprint(pubKey)
-		testFPBase64 := base64.StdEncoding.EncodeToString(testFP[:])
+	for i, expectedFP := range expectedFPs {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
 
-		if expected != testFPBase64 {
-			t.Errorf("TransmitFingerprint() did not return the expected "+
-				"fingerprint for public key %s at index %d."+
-				"\nexpected: %s\nreceived: %s",
-				pubKey.Text(10), i, expected, testFPBase64)
+		testFP := NewTransmitFingerprint(pubKey)
+		testFpBase64 := base64.StdEncoding.EncodeToString(testFP[:])
+
+		if expectedFP != testFpBase64 {
+			t.Errorf("NewTransmitFingerprint() did not return the expected "+
+				"fingerprint (%d).\nexpected: %s\nreceived: %s",
+				i, expectedFP, testFpBase64)
 		}
 	}
 }
 
 // Tests that all generated fingerprints are unique.
-func TestTransmitFingerprint_Unique(t *testing.T) {
-	grp := getGrp()
+func TestNewTransmitFingerprint_Unique(t *testing.T) {
+	testRuns := 20
 	prng := rand.New(rand.NewSource(42))
-	FPs := make(map[format.Fingerprint]*cyclic.Int, 100)
+	FPs := make(map[format.Fingerprint]*cyclic.Int)
 
-	for i := 0; i < 100; i++ {
-		pubKey := diffieHellman.GeneratePublicKey(diffieHellman.GeneratePrivateKey(
-			diffieHellman.DefaultPrivateKeyLength, grp, prng), grp)
-		testFP := TransmitFingerprint(pubKey)
+	for i := 0; i < testRuns; i++ {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		testFP := NewTransmitFingerprint(pubKey)
 
 		if FPs[testFP] != nil {
 			t.Errorf("Generated fingerprint from key %s collides with "+
 				"previously generated fingerprint from key %s."+
 				"\nfingerprint: %s", pubKey.Text(10), FPs[testFP].Text(10),
-				base64.StdEncoding.EncodeToString(testFP[:]))
+				testFP)
 		} else {
 			FPs[testFP] = pubKey
 		}
 	}
 }
-
-func getGrp() *cyclic.Group {
-	primeString := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088" +
-		"A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1" +
-		"4374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDE" +
-		"E386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA4" +
-		"8361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077" +
-		"096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E8" +
-		"6039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE51" +
-		"5D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"
-	p := large.NewIntFromString(primeString, 16)
-	g := large.NewInt(2)
-	return cyclic.NewGroup(p, g)
-}
diff --git a/e2e/singleUse/transmitKey.go b/e2e/singleUse/transmitKey.go
new file mode 100644
index 0000000000000000000000000000000000000000..038b75efe6a8dcfca8473ee4691c24072fe6c03f
--- /dev/null
+++ b/e2e/singleUse/transmitKey.go
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package singleUse
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/hash"
+)
+
+const transmitKeySalt = "singleUseTransmitKeySalt"
+
+// NewTransmitKey generates the key used for the transmission message.
+func NewTransmitKey(dhKey *cyclic.Int) []byte {
+	// Create new hash
+	h, err := hash.NewCMixHash()
+	if err != nil {
+		jww.ERROR.Panicf("Failed to create new hash for single-use "+
+			"communication transmission key: %v", err)
+	}
+
+	// Hash the DH key and salt
+	h.Write(dhKey.Bytes())
+	h.Write([]byte(transmitKeySalt))
+
+	// Get hash bytes
+	return h.Sum(nil)
+}
diff --git a/e2e/singleUse/transmitKey_test.go b/e2e/singleUse/transmitKey_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..52f182a8ae8f1923447d5d7b2557649518396a38
--- /dev/null
+++ b/e2e/singleUse/transmitKey_test.go
@@ -0,0 +1,70 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+package singleUse
+
+import (
+	"encoding/base64"
+	"gitlab.com/elixxir/crypto/cyclic"
+	"gitlab.com/elixxir/crypto/diffieHellman"
+	"math/rand"
+	"testing"
+)
+
+// Tests that the generated keys do not change.
+func TestNewTransmitKey_Consistency(t *testing.T) {
+	expectedKeys := []string{
+		"4/q8XevjJmz2ectxCr/NseWS9cSzVD/SUW36m/gk5iA=",
+		"zdF5z3ouPBE1LX8HGuKmlif9QXoURoB8SYvJ/hmrD78=",
+		"kWfO8yo6AXWsfWlo5hZGFei/MdU6kjQO/Q5+tW8CH6A=",
+		"fFnJJTooPyT489Z6DDmXviKvO3ANO/go+SK9bsw47J8=",
+		"ea0K4bAH6j6svv62OF0/xck/V6fsHAzGqK3+gYshfsY=",
+		"L8YsjbsdUcOjYTthWDXDDD0GwizPxCMtkuYOSdNEvxw=",
+		"zcJiH/vhbj9fGwp1FIIqdTwU9zwgLC1vv13NKjpznUc=",
+		"9Tg08QAHOcPR9HmPmKOTkMetLdGYF16VJ0NT62o93aQ=",
+		"8ky3KxQGlf6+6W/XZd8hV++sfMFdiTDI+XGMJXGck/A=",
+		"Jzz96sW79uil/WCnY+8wgH5fDstCbKGs+H7Fd5+olW8=",
+	}
+	prng := rand.New(rand.NewSource(42))
+
+	for i, expectedKey := range expectedKeys {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+
+		testKey := NewTransmitKey(dhKey)
+		testKeyBase64 := base64.StdEncoding.EncodeToString(testKey)
+
+		if expectedKey != testKeyBase64 {
+			t.Errorf("NewTransmitKey() did not return the expected key (%d)."+
+				"\nexpected: %s\nreceived: %s", i, expectedKey, testKeyBase64)
+		}
+	}
+}
+
+// Tests that all generated keys are unique.
+func TestNewTransmitKey_Unique(t *testing.T) {
+	testRuns := 20
+	prng := rand.New(rand.NewSource(42))
+	keys := make(map[string]*cyclic.Int, 100)
+
+	for i := 0; i < testRuns; i++ {
+		privKey := diffieHellman.GeneratePrivateKey(diffieHellman.DefaultPrivateKeyLength, getGrp(), prng)
+		pubKey := diffieHellman.GeneratePublicKey(privKey, getGrp())
+		dhKey := diffieHellman.GenerateSessionKey(privKey, pubKey, getGrp())
+
+		testKey := base64.StdEncoding.EncodeToString(NewTransmitKey(dhKey))
+
+		if keys[testKey] != nil {
+			t.Errorf("Generated fingerprint from key %s collides with "+
+				"previously generated fingerprint from key %s.\nfingerprint: %s",
+				dhKey.Text(10), keys[testKey].Text(10), testKey)
+		} else {
+			keys[testKey] = dhKey
+		}
+	}
+}
diff --git a/go.mod b/go.mod
index bb774233bce42170f1692be61beb864d1fdf4448..ff4e99973af78257ea3681fa0337865e760874fd 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,4 @@ require (
 	gitlab.com/xx_network/crypto v0.0.5-0.20210107183440-804e0f8b7d22
 	gitlab.com/xx_network/primitives v0.0.4-0.20210106014326-691ebfca3b07
 	golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
-	golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect
-	golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 // indirect
-	honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect
 )
diff --git a/go.sum b/go.sum
index 0099d5257aa53f19746f2a260b51d546ab7f4908..9a576c91f188c7f0e3ef3111066bb7521e38553d 100644
--- a/go.sum
+++ b/go.sum
@@ -79,6 +79,8 @@ gitlab.com/elixxir/primitives v0.0.3-0.20210106001742-69578b457afa h1:qjuFd1G1v8
 gitlab.com/elixxir/primitives v0.0.3-0.20210106001742-69578b457afa/go.mod h1:RigMLE2qLcqgRcPFUHRmA1ChSRVf5AuvKLFYxrJr6is=
 gitlab.com/elixxir/primitives v0.0.3-0.20210107183456-9cf6fe2de1e5 h1:50HbCJWirpX2Q+NNhIHcs0M9f45H1UJ/7LNMu81Bnn0=
 gitlab.com/elixxir/primitives v0.0.3-0.20210107183456-9cf6fe2de1e5/go.mod h1:Ph6isHUDVjmRUd9DioyKpd8W9J52gKBiDeue4DCygXA=
+gitlab.com/elixxir/primitives v0.0.3-0.20210115234332-ea62d7ec1b4b h1:YzrIpXBU/DvBZEvzGQc0h7CFZ3XLbmNl6TdpJeqTvJM=
+gitlab.com/elixxir/primitives v0.0.3-0.20210115234332-ea62d7ec1b4b/go.mod h1:Ph6isHUDVjmRUd9DioyKpd8W9J52gKBiDeue4DCygXA=
 gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
 gitlab.com/xx_network/comms v0.0.4-0.20201130190834-365ddae56e7b/go.mod h1:YViGbRj7FjJYoaO4NpALGEd9dK/l8uUT000FEBbUTL8=
 gitlab.com/xx_network/crypto v0.0.3 h1:MDQX71yKLLGC9cgvp9gzJ5b4bShPeYuoqk9tFsCjqIE=