diff --git a/auth/request.go b/auth/request.go
index 8e9c1c58e206bfb51ca952cbec66227497f593f7..be8f08d23532b67e74575f8eb45368ad66c467d7 100644
--- a/auth/request.go
+++ b/auth/request.go
@@ -33,12 +33,6 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader,
 	storage *storage.Session, net interfaces.NetworkManager) error {
 	/*edge checks generation*/
 
-	// check that messages can be sent over the network
-	if !net.GetHealthTracker().IsHealthy() {
-		return errors.New("Cannot create authenticated message " +
-			"when the network is not healthy")
-	}
-
 	// check that an authenticated channel does not already exists
 	if _, err := storage.E2e().GetPartner(partner.ID); err == nil ||
 		!strings.Contains(err.Error(), e2e.NoPartnerErrorStr) {
@@ -145,47 +139,58 @@ func RequestAuth(partner, me contact.Contact, message string, rng io.Reader,
 	//store the message as a critical message so it will always be sent
 	storage.GetCriticalRawMessages().AddProcessing(cmixMsg, partner.ID)
 
-	jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s",
-		partner.ID, cmixMsg.Digest())
-
-	/*send message*/
-	round, _, err := net.SendCMIX(cmixMsg, partner.ID, params.GetDefaultCMIX())
-	if err != nil {
-		// if the send fails just set it to failed, it will but automatically
-		// retried
-		jww.INFO.Printf("Auth Request with %s (msgDigest: %s) failed "+
-			"to transmit: %+v", partner.ID, cmixMsg.Digest(), err)
-		storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID)
-		return errors.WithMessage(err, "Auth Request Failed to transmit")
-	}
-
-	jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent on round %d",
-		partner.ID, cmixMsg.Digest(), round)
-
-	/*check message delivery*/
-	sendResults := make(chan ds.EventReturn, 1)
-	roundEvents := net.GetInstance().GetRoundEvents()
-
-	roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute,
-		states.COMPLETED, states.FAILED)
+	go func() {
+		jww.INFO.Printf("Requesting Auth with %s, msgDigest: %s",
+			partner.ID, cmixMsg.Digest())
+
+		/*send message*/
+		round, _, err := net.SendCMIX(cmixMsg, partner.ID,
+			params.GetDefaultCMIX())
+		if err != nil {
+			// if the send fails just set it to failed, it will
+			// but automatically retried
+			jww.WARN.Printf("Auth Request with %s (msgDigest: %s)"+
+				" failed to transmit: %+v", partner.ID,
+				cmixMsg.Digest(), err)
+			storage.GetCriticalRawMessages().Failed(cmixMsg,
+				partner.ID)
+		}
 
-	success, numFailed, _ := utility.TrackResults(sendResults, 1)
-	if !success {
-		if numFailed > 0 {
-			jww.INFO.Printf("Auth Request with %s (msgDigest: %s) failed "+
-				"delivery due to round failure, will retry on reconnect",
-				partner.ID, cmixMsg.Digest())
+		jww.INFO.Printf("Auth Request with %s (msgDigest: %s) sent"+
+			" on round %d", partner.ID, cmixMsg.Digest(), round)
+
+		/*check message delivery*/
+		sendResults := make(chan ds.EventReturn, 1)
+		roundEvents := net.GetInstance().GetRoundEvents()
+
+		roundEvents.AddRoundEventChan(round, sendResults, 1*time.Minute,
+			states.COMPLETED, states.FAILED)
+
+		success, numFailed, _ := utility.TrackResults(sendResults, 1)
+		if !success {
+			if numFailed > 0 {
+				jww.WARN.Printf("Auth Request with %s "+
+					"(msgDigest: %s) failed "+
+					"delivery due to round failure, "+
+					"will retry on reconnect",
+					partner.ID, cmixMsg.Digest())
+			} else {
+				jww.WARN.Printf("Auth Request with %s "+
+					"(msgDigest: %s) failed "+
+					"delivery due to timeout, "+
+					"will retry on reconnect",
+					partner.ID, cmixMsg.Digest())
+			}
+			storage.GetCriticalRawMessages().Failed(cmixMsg,
+				partner.ID)
 		} else {
-			jww.INFO.Printf("Auth Request with %s (msgDigest: %s) failed "+
-				"delivery due to timeout, will retry on reconnect",
-				partner.ID, cmixMsg.Digest())
+			jww.INFO.Printf("Auth Request with %s (msgDigest: %s) "+
+				"delivered sucessfully", partner.ID,
+				cmixMsg.Digest())
+			storage.GetCriticalRawMessages().Succeeded(cmixMsg,
+				partner.ID)
 		}
-		storage.GetCriticalRawMessages().Failed(cmixMsg, partner.ID)
-	} else {
-		jww.INFO.Printf("Auth Request with %s (msgDigest: %s) delivered "+
-			"sucesfully", partner.ID, cmixMsg.Digest())
-		storage.GetCriticalRawMessages().Succeeded(cmixMsg, partner.ID)
-	}
+	}()
 
 	return nil
 }
diff --git a/go.mod b/go.mod
index c5417cea74695df068ea3b36604fc59f59c69f67..cdf79beb693cc13fab5e8643a1cee19896c2a18a 100644
--- a/go.mod
+++ b/go.mod
@@ -6,10 +6,12 @@ require (
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
 	github.com/golang/protobuf v1.4.3
 	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
+	github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea // indirect
 	github.com/magiconair/properties v1.8.4 // indirect
 	github.com/mitchellh/mapstructure v1.4.0 // indirect
 	github.com/pelletier/go-toml v1.8.1 // indirect
 	github.com/pkg/errors v0.9.1
+	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/smartystreets/assertions v1.0.1 // indirect
 	github.com/spf13/afero v1.5.1 // indirect
 	github.com/spf13/cast v1.3.1 // indirect
diff --git a/go.sum b/go.sum
index ef9faafd22145cb716e0f0b8d8e189f0fc401b33..32e5ce0af0158c05655282388490d326c05eb272 100644
--- a/go.sum
+++ b/go.sum
@@ -144,6 +144,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea h1:uyJ13zfy6l79CM3HnVhDalIyZ4RJAyVfDrbnfFeJoC4=
+github.com/liyue201/goqr v0.0.0-20200803022322-df443203d4ea/go.mod h1:w4pGU9PkiX2hAWyF0yuHEHmYTQFAd6WHzp6+IY7JVjE=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
@@ -201,6 +203,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
+github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
 github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go
index 498500b039a72da3ba9336e2815d34706bf86dbd..1edf828319c5b0cb14e9bf5698f6a0fba72e94f4 100644
--- a/interfaces/contact/contact.go
+++ b/interfaces/contact/contact.go
@@ -13,17 +13,27 @@ import (
 	"encoding/base64"
 	"encoding/binary"
 	"github.com/pkg/errors"
+	"github.com/skip2/go-qrcode"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/primitives/id"
+	"strings"
 )
 
-const sizeByteLength = 2
-const fingerprintLength = 15
+const (
+	version           = "0"
+	headTag           = "<xxc"
+	footTag           = "xxc>"
+	openVerTag        = "("
+	closeVerTag       = ")"
+	sizeLength        = 2
+	minLength         = (sizeLength * 3) + len(headTag) + len(footTag) + id.ArrIDLen
+	fingerprintLength = 15
+)
 
 // Contact implements the Contact interface defined in interface/contact.go,
 // in go, the structure is meant to be edited directly, the functions are for
-// bindings compatibility
+// bindings compatibility.
 type Contact struct {
 	ID             *id.ID
 	DhPubKey       *cyclic.Int
@@ -31,17 +41,23 @@ type Contact struct {
 	Facts          fact.FactList
 }
 
-// Marshal saves the Contact in a compact byte slice. The byte slice has
-// the following structure (not to scale).
+// Marshal saves the Contact in a compact binary format with base 64 encoding.
+// The data has a header and footer that specify the format version and allow
+// the data to be recognized in a stream of data. The format has the following
+// structure.
 //
-// +----------+----------------+---------+----------+----------+----------------+----------+
-// | DhPubKey | OwnershipProof |  Facts  |    ID    |          |                |          |
-// |   size   |      size      |   size  |          | DhPubKey | OwnershipProof | FactList |
-// |  2 bytes |     2 bytes    | 2 bytes | 33 bytes |          |                |          |
-// +----------+----------------+---------+----------+----------+----------------+----------+
+// +----------------+---------------------------------------------------------------------------------------+--------+
+// |     header     |                                     contact data                                      | footer |
+// +------+---------+----------+----------------+---------+----------+----------+----------------+----------+--------+
+// | Open |         | DhPubKey | OwnershipProof |  Facts  |    ID    |          |                |          | Close  |
+// | Tag  | Version |   size   |      size      |   size  |          | DhPubKey | OwnershipProof | FactList |  Tag   |
+// |      |         |  2 bytes |     2 bytes    | 2 bytes | 33 bytes |          |                |          |        |
+// +------+---------+----------+----------------+---------+----------+----------+----------------+----------+--------+
+// |     string     |                                    base 64 encoded                                    | string |
+// +----------------+---------------------------------------------------------------------------------------+--------+
 func (c Contact) Marshal() []byte {
 	var buff bytes.Buffer
-	b := make([]byte, sizeByteLength)
+	b := make([]byte, sizeLength)
 
 	// Write size of DhPubKey
 	var dhPubKey []byte
@@ -77,30 +93,65 @@ func (c Contact) Marshal() []byte {
 	// Write fact list
 	buff.Write([]byte(factList))
 
-	return buff.Bytes()
+	// Base 64 encode buffer
+	encodedBuff := make([]byte, base64.StdEncoding.EncodedLen(buff.Len()))
+	base64.StdEncoding.Encode(encodedBuff, buff.Bytes())
+
+	// Add header tag, version number, and footer tag
+	encodedBuff = append([]byte(headTag+openVerTag+version+closeVerTag), encodedBuff...)
+	encodedBuff = append(encodedBuff, []byte(footTag)...)
+
+	return encodedBuff
 }
 
 // Unmarshal decodes the byte slice produced by Contact.Marshal into a Contact.
 func Unmarshal(b []byte) (Contact, error) {
-	if len(b) < sizeByteLength*3+id.ArrIDLen {
+	if len(b) < minLength {
 		return Contact{}, errors.Errorf("Length of provided buffer (%d) too "+
 			"short; length must be at least %d.",
-			len(b), sizeByteLength*3+id.ArrIDLen)
+			len(b), minLength)
 	}
 
-	c := Contact{DhPubKey: &cyclic.Int{}}
 	var err error
-	buff := bytes.NewBuffer(b)
+
+	// Get data from between the header and footer tags
+	b, err = getTagContents(b, headTag, footTag)
+	if err != nil {
+		return Contact{}, errors.Errorf("data not found: %+v", err)
+	}
+
+	// Check that the version matches
+	currentVersion, err := getTagContents(b, openVerTag, closeVerTag)
+	if string(currentVersion) != version {
+		return Contact{}, errors.Errorf("found version %s incomptible, "+
+			"requires version %s", string(currentVersion), version)
+	}
+
+	// Strip version number
+	b = b[len(currentVersion)+len(openVerTag)+len(closeVerTag):]
+
+	// Create new decoder
+	decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(b))
+
+	// Create a new buffer from the data found between the open and close tags
+	var buff bytes.Buffer
+	_, err = buff.ReadFrom(decoder)
+	if err != nil {
+		return Contact{}, errors.Errorf("failed to read from decoder: %+v", err)
+	}
 
 	// Get size of each field
-	dhPubKeySize, _ := binary.Varint(buff.Next(sizeByteLength))
-	ownershipProofSize, _ := binary.Varint(buff.Next(sizeByteLength))
-	factsSize, _ := binary.Varint(buff.Next(sizeByteLength))
+	dhPubKeySize, _ := binary.Varint(buff.Next(sizeLength))
+	ownershipProofSize, _ := binary.Varint(buff.Next(sizeLength))
+	factsSize, _ := binary.Varint(buff.Next(sizeLength))
+
+	// Create empty client
+	c := Contact{DhPubKey: &cyclic.Int{}}
 
 	// Get and unmarshal ID
 	c.ID, err = id.Unmarshal(buff.Next(id.ArrIDLen))
 	if err != nil {
-		return Contact{}, errors.Errorf("Failed to unmarshal Contact ID: %+v", err)
+		return Contact{}, errors.Errorf("failed to unmarshal Contact ID: %+v", err)
 	}
 
 	// Handle nil ID
@@ -114,7 +165,7 @@ func Unmarshal(b []byte) (Contact, error) {
 		c.DhPubKey = nil
 	} else {
 		if err = c.DhPubKey.BinaryDecode(buff.Next(int(dhPubKeySize))); err != nil {
-			return Contact{}, errors.Errorf("Failed to binary decode Contact DhPubKey: %+v", err)
+			return Contact{}, errors.Errorf("failed to binary decode Contact DhPubKey: %+v", err)
 		}
 	}
 
@@ -129,7 +180,7 @@ func Unmarshal(b []byte) (Contact, error) {
 	// Get and unstringify fact list
 	c.Facts, _, err = fact.UnstringifyFactList(string(buff.Next(int(factsSize))))
 	if err != nil {
-		return Contact{}, errors.Errorf("Failed to unstringify Contact fact list: %+v", err)
+		return Contact{}, errors.Errorf("failed to unstringify Contact fact list: %+v", err)
 	}
 
 	return c, nil
@@ -151,6 +202,23 @@ func (c Contact) GetFingerprint() string {
 	return base64.StdEncoding.EncodeToString(data)[:fingerprintLength]
 }
 
+// MakeQR generates a QR code PNG of the Contact.
+func (c Contact) MakeQR(size int, level qrcode.RecoveryLevel) ([]byte, error) {
+	qrCode, err := qrcode.Encode(string(c.Marshal()), level, size)
+	if err != nil {
+		return nil, errors.Errorf("failed to encode contact to QR code: %v", err)
+	}
+
+	return qrCode, nil
+}
+
+func (c Contact) String() string {
+	return "ID: " + c.ID.String() +
+		"  DhPubKey: " + c.DhPubKey.Text(10) +
+		"  OwnershipProof: " + base64.StdEncoding.EncodeToString(c.OwnershipProof) +
+		"  Facts: " + c.Facts.Stringify()
+}
+
 // Equal determines if the two contacts have the same values.
 func Equal(a, b Contact) bool {
 	return a.ID.Cmp(b.ID) &&
@@ -158,3 +226,26 @@ func Equal(a, b Contact) bool {
 		bytes.Equal(a.OwnershipProof, b.OwnershipProof) &&
 		a.Facts.Stringify() == b.Facts.Stringify()
 }
+
+// getTagContents returns the bytes between the two tags. An error is returned
+// if one ore more tags cannot be found or closing tag precedes the opening tag.
+func getTagContents(b []byte, openTag, closeTag string) ([]byte, error) {
+	// Search for opening tag
+	openIndex := strings.Index(string(b), openTag)
+	if openIndex < 0 {
+		return nil, errors.New("missing opening tag")
+	}
+
+	// Search for closing tag
+	closeIndex := strings.Index(string(b), closeTag)
+	if closeIndex < 0 {
+		return nil, errors.New("missing closing tag")
+	}
+
+	// Return an error if the closing tag comes first
+	if openIndex > closeIndex {
+		return nil, errors.New("tags in wrong order")
+	}
+
+	return b[openIndex+len(openTag) : closeIndex], nil
+}
diff --git a/interfaces/contact/contact_test.go b/interfaces/contact/contact_test.go
index 2d43f1818986c26f81ad5f389dce028e88ec10cd..578143c15262ae055b0bb9263f94c5a733c909c9 100644
--- a/interfaces/contact/contact_test.go
+++ b/interfaces/contact/contact_test.go
@@ -8,14 +8,19 @@
 package contact
 
 import (
+	"bytes"
 	"crypto"
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
+	"github.com/liyue201/goqr"
+	"github.com/skip2/go-qrcode"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/crypto/large"
 	"gitlab.com/xx_network/primitives/id"
+	"image"
 	"math/rand"
 	"reflect"
 	"strings"
@@ -44,7 +49,7 @@ func TestContact_Marshal_Unmarshal(t *testing.T) {
 
 	if !reflect.DeepEqual(expectedContact, testContact) {
 		t.Errorf("Unmarshaled Contact does not match expected."+
-			"\nexpected: %#v\nreceived: %#v", expectedContact, testContact)
+			"\nexpected: %s\nreceived: %s", expectedContact, testContact)
 	}
 }
 
@@ -61,7 +66,137 @@ func TestContact_Marshal_Unmarshal_Nil(t *testing.T) {
 
 	if !reflect.DeepEqual(expectedContact, testContact) {
 		t.Errorf("Unmarshaled Contact does not match expected."+
-			"\nexpected: %#v\nreceived: %#v", expectedContact, testContact)
+			"\nexpected: %s\nreceived: %s", expectedContact, testContact)
+	}
+}
+
+// Consistency test.
+func TestUnmarshal_Consistency(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	var contacts []Contact
+	expectedContact := []string{
+		"PHh4YygwKUpBQUFBTHdKcjc5a3NaWi9qRk1BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBUWh5dEV1ZnU1Y2JWVU5rTW1SVmRDOVliVEF4TWxGd2RHaGxaM2xtYm5jd04zQldjMDEzVGxsVlZFbHBSazVaVVdGNUswSjNiWGRrV1VORU9XZ3dNMWM0UVhKUlpEbFFhMXBMWlVkUU1uQTFkbWQxVms5a1NUWkNOVFUxVEhaWEwycFVUbmsyYUVRM2J6RnFOazFVTHpSak5pdHdWV0paSzNORk9UQmhja0ZVVDB4eFMwaG1SbFkxZWpaTVNHcG9jMHh5TmpaSlJ6UkxibFZTUTB0UmRUQTRhMFI1Y1ZFd1dtRmxSMGxIUm5CbFN6ZFJlbXA0YzFSNmNtNTJSRFJGYkdKV2VFd3JMMkkwVFVWRGFVZzBVVVJoZWxNeVNWZ3lhM04wWjJaaFFVdEZZMGhJUW5nc1ZUVTFZV2w1VTAxdFoyODBja0pYTkRSR01sZFBSVWRHU21sVlpqazRNRkpDUkhSVVFrWm5TUzl4VDA1WVlUSXZkRW92SzBwa1RISkJlWFl5WVRCR1lWTnpWRmxhTlhwcFYxUm1NMGh1YnpGVVVUTk9iVWhRTVcweE1DOXpTR2gxU2xOU2NUTkpNalZNWkZOR2FXdE5PSEkyTUV4RWVXbGplV2hYUkhoeGMwSnVlbkZpYjNZd1lsVnhlWFJIWjBWQmMxZzNTME5FYjJoa1RXMUVlRE53WlVObk9WTm5iV3BpTldKRFExVkdNR0pxTjFVd1BTeFZjR3RaZVdWdllUUnpUVTloT0dNdlUzTTNWVk5IWlhBMVZYcHhMMUpKTUhOU05UQjVXVWhWZW10R2ExVjVUWGRqT0VveWFtNW5ObE51VVVwTGRreDVlR0ZVYkRkMGVsUnVOM2R3ZFVGbVIwWlVaWFJtTlVOVUt6azJkMmhEU2tneGJERTBjRXR4U3pSeFoxSTRXV015ZWswelVISTFlWGR1YjBwcVFVbGFabUUxY21ONWR6RklabHB2SzBoVWFYbG1TRTlEWTNGSFFWZzFLMGxZVTBSQkx6bENkMkpKSzBWalUwOHdXRlUxTVc5WU0ySjVjRFZwT0ZwT05FOVlZa3RIVTNseVZIZG5QVDA3eHhjPg==",
+		"PHh4YygwKUpBQUFBSkFHOGZaZzl5M2xsdFlBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBVkdrM0tBcFhrYkRWV0pFUlVaVlZDOVpVWEJ6V2xOMVJ6WnVlWEZFYUhFMWVYTXlXV1kyUTNkeGVHc3lMMUpvVlZsS1QwbDNWWGhLYW1aS2JrTjFTREYwT0U0eFpUaHZUbTVIVVdOMFduazVhQ3R4UXl0b2RsSllhMmczUkdaQk5VZEVVRVJZUVRsQ09YaG1WbTFIYTFWQ1RUQkRZV05UZEhGYWNYWllkVzFWYVZSTFNuQmxUbTFzZW0xTUszTlNjVVJxZUZwcFRISldUVGRxTlc1V1UwdGpSMFIzV2swMmExZFhSalJaVHpkalNrZFRNbmMxYzJwWVEySnVXRTlFZFZwUVIyeEtiVlZYZDNOTkwyb3pRemRDTDBwWlVURXJObmh1VVd0WGIyNVJhbmR3TlVWTWR6UjRkM05zVkd4dFJIZFlOMDRyVW1Nd1lqaE1lamhIYWxKelVUQTRVbnAzUWtKaU5sbFhiR0pyWjB4dFp6SlBhSGcwWmpCbFJUUkxOMXA0TkZaclIwVTBWRWg0TlRoblVqZzlMRlYyV1doaWVraHlXRFJrV1c1cVNqazRhSGtyUlVRMU1sVXlaak4wY25CUVlrcEtWbmhxTVdkTWFrazRjamROWVhGa2MwOTNLMVZwZG5adFJXNXdZeTlXWVRoRllsQkRObTlPTmtoSFlXRm5hVGt5YUdzM1EzSm5lbGR6UFRzPXh4Yz4=",
+		"PHh4YygwKUpBQUFBS3dDODU0WnRnb0Z6aG9BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBVmZnL1JqV2xyUEhWUzlrT0doelVsSkZkV3g0Y3pGUU1VNVBVM2xUZEV4SllYbENTVVJSUjB4bWQzZFpObVZ0YUdselVEZDRRbE5yV25keGFEWlRXbFE0U0d0QlpVVmpWMnR1U0RaUGNXVmFaR0pOVVVWYVpqQXhUSGw0UXpkRU1DczVaekl5YkRCQ1VtWmpaR3hMTlRkMk9WSmFWRmR4U0VvNGVqSjRVSFJJTTNKb2RtcG9RazFxUzA1RlJDdElSM1p0T0RCV1NYcDNOVTlZYWpFN3h4Yz4=",
+		"PHh4YygwKUpBQUFBUGdFeU1mS3Z6bCt6QXdBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBVFZpTEJjc3ZrS3NWVVU1WVcxemRuSmFRalV4ZERnd1VVWnJRMnh0WlhrMk1FZHJRVUpHTVZaMk5XTjBRVmQ2Um5keVMzbHNlREYxZG1ZemRIYzJSMFpGTURkdlJHRjZjMlpYVURWRFpWWkViakJGT1UxS2RYWm9URGxZYUVrelIxbERTMGxGZFVaSE5tSlFURXRpUldKMGFVSlpkbGhWV0U1MFlVSXlUM1ZtYUdkNkt6TnlabTFLVG5aVlpWUmtjVXRMUlRkNGJXOVhOeXhWYURCT04xRmhZMVU0Y1dkd05HTkpRMFp1VHk5TlRWVk1XalV4VVZadmJGWjVZMGRaUTBveVZqbG5UMWd3Y1RsbWFqaHJVa01yVDNBMFUxaHdVa2xUV0d4M2FsZHhibm80V1hBMlRtdFZabEJNTVNzeWIwWlhVbkUwWVVoUGFEWjBRbUZUYlZwNE1YQlZWbmxFYm1ScGRrOXFjbGhrUVV4R0wxUnhSRzlGV1VseFdXdDZRVU5HYVROVmQyWTVWbEl2VmpGSlJFaDVSV2cwVlVWSmNXeHdXRkpYZVhWclkwVTlPdz09eHhjPg==",
+		"PHh4YygwKUpBQUFBS3dEUzNZbldLdkN0QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBUzNEMVRMS2FGdllWV041YlhSWGN6UXZORmhPUWtGalptdHFPVTR2ZFdaeWFqaE1kRTl0TWpNd1pGTnZkV3RYVkZCQ1psWnZNSE5wWlhCT1dtUmtaMWxQV2tsbFVWZFVTa3gwTVhKaVJrSnZkbVpETDJWbFFrZ3daMk00U1dGcU4wUXlURTR6UVVKTU9VbzJVekpYZDB0MWFGTldkbU15TjJWUWF6TlpWbmxEVm1SaFUwa3hXRkZPVEROS2NGZHNZMDUyZVZwSU9IQllhVTAxV0hVeWN5OHlUblZIZDNwNVJHVmhhRVF2YlZWUWRIbHRLMWQyTUROaE9FRjFaRlE0TVhGNU9YVjZPV3BIVlZWRVYwZHhTMXBaY20xRE1UZHNUMkYwWVRCYU93PT14eGM+",
+		"PHh4YygwKUpBQUFBS1FIVlN4S1dVd1RWYVlBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBVHJrVnVlazMydUFWV2xqZVhJNGIwZGtaVlV5WlVWdlREbERia3BWT0dKUFZUbDFjVGhhY1ZwSmRYUXZZbEk1VGxGcGJXczBRelpaVGxndlV6Qk5ibll5VlU5QlZFYzBPVmhrWVRKSGJEWmpNRms0TUc5Q2RXRjFOM1pyY2xod2RpODFRVFpWV25CclVHODJOMEpJYTNBNGRIRTJOMFkzWmlzd2NGVlVNSGczVlM5NE1GYzFkRmRhVm1oc1VsZ3hPVlJvTW10Qk4xRTVOakJtU1RSMWVrNUZRMlZRUWxwS1ZucHdWemxFY1U5SVUwVXpTa2N2UzJKSFRYbFVWVkEwVUhORVZUQjJNV3hDZGpNck4ybElhVkY1VUZsRWJuazBVbVlzVlZFNVNXcEpjSEJPVEVsWWVFeERVMGwyTkdaVE1sTnRaM0JJYld4WE0zUjZOVThyZVZGVlRrOU9kRkpRWmxock5HcGhSMlZZVURaRVNWZE9NVzFEWW1aUFptZDZZVlo1YVV0eFdsSnNWV2xSTEZWMlJIVTNTaTlYT0ZOWWRsZFdSV1JPZVRSWmNYUk9NVzl0TmtKT1JHRTFiMjlKVUZkd1pXZ3dlRUo2YTNKdVlXUXpZbGxvZDFkUmVVOWlNRE5wYVRaU2NXRjZhV1F2VEVGcVdFWk1lV1J1YlV4QlZtbDVhV3gxUlhGa01rWXdWR1IxUTA5dlRIaHRObVpSY0ZOVE1WTlRkUzlqY1hjd056aDFVWEJsTXpJeU9FbGFLeTlOVDNwNVdEWmFiWGxUVW5KRFFqRmtURVpsVHpkM1FVbHpTVEZvYjBwa2EwSlFVWFZ4UTNCSk93PT14eGM+",
+		"PHh4YygwKUpBQUFBRFFBNmpaL0toMmZwVmNBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBUldmbFZyVnZLTVhWV012YzA1YVNXVTFaa2RyVFcxRVZFTkxRMjAyYlRsVUx6cz14eGM+",
+		"PHh4YygwKUpBQUFBSWdCdVI0djdFazd5Zk1BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBWDcvZDRoSTVEZUtWWEZZY0hKMlMzcHFOM0ZCWWpFeGJFRkVXV1UxWjB4T1ZIZFNXRXgzVjB0dVpVNHlaVzlGU1RGdVZYZG5MMmxIUzFKdEwyTnhaMk05TEZVNE1GaHhia0U5UFRzPXh4Yz4=",
+		"PHh4YygwKUpBQUFBQUlBcGNGR003NU9TWThBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBUXgzeFpkN2VPMmFPdz09eHhjPg==",
+		"PHh4YygwKUpBQUFBUGdDNkdaSWxBaTIzRUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUQrNlNkZ01lZnhKWHBBU012OHNFWE1zTS9WUzlOV2xrNGRUSlVVU3RSSzFod1ZEaHdaVkZKVW5kU2RDdGthVWxCY2pSclJYRkZNRm8zV214V2FUaDVhREJxYTJjNFdIRnRSa1ZxZWtWSmRWUjNQVDBzVldWeWVqTkVlVXRKVFZkelQweHdRa1JVUTNRNGIzTnZUbWxwUVdwS1kyVTBSRXhaTVRaR01qbEplbVJCTVZwT05HOW9TMVpQTTJ0WWVrZzVSakYxZDBKVVVqaE1WMUV4YjNSR1JYcFFRVU5MZVN0VlUwMW1kVUZ1ZVVoTWIwRkJXbFZhU0dwSFRrUjZVMlpQUTBwU00yZFBlbGd4Wm1NNVV6cz14eGM+",
+	}
+
+	// Generate test contacts
+	for i := 0; i < 10; i++ {
+		contacts = append(contacts, Contact{
+			ID:       id.NewIdFromUInt(prng.Uint64(), id.User, t),
+			DhPubKey: getGroup().NewInt(prng.Int63()),
+			Facts:    fact.FactList{},
+		})
+
+		for j := 0; j < prng.Intn(5); j++ {
+			username := make([]byte, prng.Intn(255))
+			prng.Read(username)
+			newFact, err := fact.NewFact(fact.Username, base64.StdEncoding.EncodeToString(username))
+			if err != nil {
+				t.Errorf("Failed to generate new fact (%d %d): %+v", i, j, err)
+			}
+			contacts[i].Facts = append(contacts[i].Facts, newFact)
+		}
+
+		// fmt.Printf("\"%s\",\n", base64.StdEncoding.EncodeToString(contacts[i].Marshal()))
+	}
+
+	for i, c := range contacts {
+		contactBase64 := base64.StdEncoding.EncodeToString(c.Marshal())
+		if expectedContact[i] != contactBase64 {
+			t.Errorf("Contacts %d do not match.\nexpected: %s\nreceived: %s",
+				i, expectedContact[i], contactBase64)
+		}
+	}
+}
+
+// Error path: length of buffer is too small.
+func TestUnmarshal_LengthError(t *testing.T) {
+	buff := make([]byte, minLength-1)
+
+	_, err := Unmarshal(buff)
+	if err == nil || !strings.Contains(err.Error(), "too short") {
+		t.Errorf("Unmarshal() did not produce the expected error: %+v", err)
+	}
+}
+
+// Error path: the opening tag is missing.
+func TestUnmarshal_OpenTagError(t *testing.T) {
+	buff := Contact{
+		ID:       id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey: getCycInt(256),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+		},
+	}.Marshal()
+
+	buff = []byte(strings.Replace(string(buff), headTag, "", 1))
+
+	_, err := Unmarshal(buff)
+	if err == nil || !strings.Contains(err.Error(), "missing opening tag") {
+		t.Errorf("Unmarshal() did not produce the expected error: %+v", err)
+	}
+}
+
+// Error path: the closing tag is missing.
+func TestUnmarshal_CloseTagError(t *testing.T) {
+	buff := Contact{
+		ID:       id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey: getCycInt(256),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+		},
+	}.Marshal()
+
+	buff = []byte(strings.Replace(string(buff), footTag, "", 1))
+
+	_, err := Unmarshal(buff)
+	if err == nil || !strings.Contains(err.Error(), "missing closing tag") {
+		t.Errorf("Unmarshal() did not produce the expected error: %+v", err)
+	}
+}
+
+// Error path: the version is incorrect.
+func TestUnmarshal_IncorrectVersionError(t *testing.T) {
+	buff := Contact{
+		ID:       id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey: getCycInt(256),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+		},
+	}.Marshal()
+
+	buff = []byte(strings.Replace(string(buff), openVerTag+version+closeVerTag,
+		openVerTag+version+"0"+closeVerTag, 1))
+
+	_, err := Unmarshal(buff)
+	if err == nil || !strings.Contains(err.Error(), "requires version") {
+		t.Errorf("Unmarshal() did not produce the expected error: %+v", err)
+	}
+}
+
+// Error path: the version is missing.
+func TestUnmarshal_MissingVersionError(t *testing.T) {
+	buff := Contact{
+		ID:       id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey: getCycInt(256),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+		},
+	}.Marshal()
+
+	buff = []byte(strings.Replace(string(buff), openVerTag+version+closeVerTag, "", 1))
+
+	_, err := Unmarshal(buff)
+	if err == nil || !strings.Contains(err.Error(), "requires version") {
+		t.Errorf("Unmarshal() did not produce the expected error: %+v", err)
 	}
 }
 
@@ -149,6 +284,93 @@ func TestContact_GetFingerprint_Consistency(t *testing.T) {
 	}
 }
 
+// Happy path.
+func TestContact_MakeQR(t *testing.T) {
+	c := Contact{
+		ID:       id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey: getCycInt(256),
+		Facts: fact.FactList{
+			{Fact: "myUsername", T: fact.Username},
+			{Fact: "devinputvalidation@elixxir.io", T: fact.Email},
+			{Fact: "6502530000US", T: fact.Phone},
+			{Fact: "6502530001US", T: fact.Phone},
+		},
+	}
+	qrCode, err := c.MakeQR(512, qrcode.Medium)
+	if err != nil {
+		t.Errorf("MakeQR() returned an error: %+v", err)
+	}
+
+	img, _, err := image.Decode(bytes.NewReader(qrCode))
+	if err != nil {
+		t.Fatalf("Failed to decode image: %+v", err)
+	}
+
+	qrCodes, err := goqr.Recognize(img)
+	if err != nil {
+		t.Fatalf("Failed to recognize QR code: %+v", err)
+	}
+
+	var qrBytes []byte
+	for _, qrCode := range qrCodes {
+		qrBytes = append(qrBytes, qrCode.Payload...)
+	}
+
+	if !bytes.Equal(c.Marshal(), qrBytes) {
+		t.Errorf("Generated QR code data does not match expected."+
+			"\nexpected: %+v\nreceived: %+v", c.Marshal(), qrBytes)
+	}
+}
+
+// Consistency test.
+func TestContact_String(t *testing.T) {
+	prng := rand.New(rand.NewSource(42))
+	var contacts []Contact
+	expectedContact := []string{
+		"ID: r79ksZZ/jFMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 6087471365... in GRP: 6SsQ/HAHUn...  OwnershipProof: CcnZND6SugndnVLf15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAg/YdN1vAK0HfT5GSnhj9qeb4LlTnSOgeeeS71v40zcuoQ+6NY+jE/+HOvqVG2PrBPdGqwEzi6ih3xVec+ix44bC6+uiBuCp1EQikLtPJA8qkNGWnhiBhaX  Facts: Uiv79vgwQKIfhANrNLYhfaSy2B9oAoRwccHHnlqLcLcJaW3Sy4SlwXic/BckjJoKOKwVuOBdljhBhSYlH/fNEQQ7UwRYCP6jjV2tv7Sf/iXS6wMr9mtBWkrE2Gec4lk39x56NU0NzZhz9ZtdP7B4biUkatyNuS3UhYpDPK+tCw8onMoVg8arAZ86m6L9G1KsrRoBALF+ygg6IXTJg8d6XgoPUoJo2+WwglBdG4+1NpkaprotPp7T8OiC6+hp17TJ6hriww5rxz9KztRIZ6nlTOr9EjSxHnTJgdTOQWRTIzBzw,UtzTn7wpuAfGFTetf5CT+96whCJH1l14pKqK4qgR8Yc2zM3Pr5ywnoJjAIZfa5rcyw1HfZo+HTiyfHOCcqGAX5+IX;",
+		"ID: 7vJ2X2idUxcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 2332036530... in GRP: 6SsQ/HAHUn...  OwnershipProof: SDA/9OY4I0J2WhPWluUt92D2w0ZeKaDcpGrDoNVwEzvCFXH19UpkMQVRP9hCmxlK4bqfKoOGrnKzZh/oLCrGTb9GFRgk4jBTEmN8mcK4fW3w3V7yg2cZBy1nL2H6oL6G9FeSHsN8DkYM8NcD0H3F9WYaRQEzQJpxK2pmq9e6ZSJMoml42aXOYv6xGoOPFmIutUzuPmdVIpwYPBkzqRZYXhg7twkZLbDmyNcJudc4O5k8aUmZRbCwz+PcLsH8lhDX7rGdCRaidCPCnkQvDjHCyVOWYA==  Facts: U8F+zAEFvphaVuSAuaDY6HHh/R4TgrtnHhWQYThMfHnyBH72IW8xnNOiTPzyy8l1S+fjHVHrX4dYnjJ98hy+ED50=,UlCvsxqp2w7D5SK++YSelz9VrwRs8Lqg3ocZpqCL3aGTsKuDNa/3fIbEURHS/03zSBrUazgUKthmex7OW1hj94OGimZpvPZ+LergUn3Leulxs1P1NOSyStLIayBIDQGLfwwY6emhisP7xBSkZwqh6SZT8HkAeEcWknH6OqeZdbMQEZf01LyxC7D0+9g22l0BR,UfcdlK64b44QTIyjRA/hxr5vNFSM8OTl49cFw,UiejzJnpD0QYzA209RrgZFq5G/xPWprL62QedbfNEBZApZnsutBpAARdVb+XLQFsxcKyspcdbr397cOhhRNO6A2s7H1j+QnlQ59BPTCbr4S/V4SNxmAiiBLhRumzyymxG7YgWL11FzbWgdjrn4YM/t635iTb1Hk3aiihO8ZqFu4dDe0EDMFo7OGx9NtktIlSZYKcU8qgp4cICFnO/MMULZ51QVolVycGYCJ2V9gOX0q9fj8kRC+Op4SXpRISXlwjWqnz8Yp6NkUfPL1+2oFWRq4aHOh6tBaSmZx0=;",
+		"ID: NUaggzr9xQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 2333463841... in GRP: 6SsQ/HAHUn...  OwnershipProof: aVFcg1IDHyEh4UEIqlpXRWyukcFzKa1azmHq6bZS3zMAtMKrWCd22FtoyjLVw3PCZHhYO8SPo2vkjWWcP+FzQQHH5I/Tf7n64/C7Tptt9HUqLpFkzwX1aNLInqTWXXYGDmSHkFkyS7da2xQaL3wv3ngR9IHPCGo+w9izdwAS/SektlsCroUlb3Nu3j5N2FcglXWkiNV0DS9yaVpXDb8mR/KV4jOV7trP9jbhsM8g3moQ/5lD7cpvlr9N2vALnU/Nasvbs/YxlFA1hqimWK5gte5TmrWtGfK6I9ymZ9+mVRNMWUosgGvfpOdW5Fi0i65JDQQuB6NV6Wz7icyr8oGdeU2eEoL9CnJU8bOU9uq8ZqZIut/bR9NQimk4C6YNX/S0Mnv2UOATG49Xda2Gl6c0Y80oBuau7vkrXpv/5A6UZpkPo67BHkp8tq67F7f+0pUT0x7U/w==  Facts: U7Q960fI4uzNECePBZJVzpW9DqOHSE3JG/KbGMyTUP4PsDU0v1lBv3+7iHiQyPYDny4RfQ9IjIpqkIRRKJ9u3xEDBx5j5ok0shfEsJIi/h9LZKaCkeaVbe3Pk77JBQ0421E99eTiNoZ5c/oMhY3WYJt85+DNpXKIqplGVSJC83M9mrT1zqOGrn4OHfSY7uyf1vEl71lRHTcuGKrTdaJugTQ2uaKCD1qXodMQc5K52nd22IcFkMjm9N4oukams4nfywI1xS8nZ5iwFYsopbhKndhdE3bgjqC8Zun0KUktUkrv3KsNO/LkKXt9tvCGfvzDs8l+mZsk=;",
+		"ID: lGGNsAgA744AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 3355697978... in GRP: 6SsQ/HAHUn...  OwnershipProof: d32mRwa3SFelnx0qfw==  Facts: UNuDmAwt8AGm5fGkMmDTCKCm6m9T/qa9YGW93XjDzyTtJ7C8eijfkSIh3/2x+JCUf4VDs58xa+yztemu8rOPuoBvXWUANh7mAs1PBFcvBYqd43Z6gQjWdTCD+IYpGb9yqB/NFyUfloJaiP4lnz0/hKL3qnPzGWPLtsSSSnNzPWI9JTr4zRsGa7Xh7l8V3Df7ehFz1sEDctgiU,USGbDl7Oo40e3k0PkPl6U/KXkCEcEbfnYiAK+JBKhNGe2ZVYvModI5IPF6phRI8xCLk96jOl0B1OPYfZ+ga42GtW89w8iiDFrDi6QQ0wrfKLKDYogIyXHuAy2NehdvSM3QNWTeKISlTt5F8x/RdbsAU0fC1kNaLRRMzwAisvlEjH7gJ8hy6AAGVGR4xjQ80nzgiUd4Ds19X3PUrloJgqUXJGcj7k=;",
+		"ID: Tpniy8GkRuMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 5811638083... in GRP: 6SsQ/HAHUn...  OwnershipProof: +436hH1mygRoZHScSATHnMmtZLZ2Bw9hP9+WSKJW3DwiOkvOiRWUK9lrAHMdrZWDfD+v/xpMwQIeW0K7dXiccKP3faU8JeBIuQuqHrARGizMUEcrKECJa840U6mtBJct5H/GZEahdvtaE8Jdy7pWu/Y1Xhsq+GZUMfdHKpZhgWafEB9aVyy0GiAUFyBexvVbintbSsYQjuBFVTHkOGRH9fTJGdxLvuMp8Ei+/A7kCstKbG4QctBDAFCN1fNbLPwGgdnQAZaEWYyCdG1Zk/AB99k9z/INedKtTv1e5OyrKPK5thkEWP42xLd1rK9gwVQjlbaM71aNOWA4Tr2KTqoq6+xmTlY4cNuAPSgOPmJwo7D+A4vILZyDD+hE0lawteli8zEznxPYUpc7KcqgPpAUqIfiAe4BFutxC8au4sJO  Facts: ;",
+		"ID: 1oKcn8YUNmwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 3236160443... in GRP: 6SsQ/HAHUn...  OwnershipProof: XQ==  Facts: ;",
+		"ID: axTLAX4/+QYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 1614413914... in GRP: 6SsQ/HAHUn...  OwnershipProof: kET7EBq5eBYhI2vBK/rFKCQZxqb4PUfN5vH1mzwAd3fIAAtw4cDIkCK52xNm0x0FAN2fAkPW6rUP0gFhx0hJw94sUaubeM+WWRCILcf1O8cyCxz0hHL2SzZB39Npj3NM2Q5cA3hMWMAcrvqWoVNZPxQqYFWLMoCUCnrl2NArseYXnTlaw8HM3BUfxAXR9ykcOirumjokeGAv1lx7Zq3/Nor7+NgAzkvg7A==  Facts: UvJeHltfAZz4b4IuT+oQigYxDRcFmXIeSJhLevueTpWt1dJvStEJQ8fxvrIP9a5Akudp/q8qRN3ROkECcY8qhZZKBX83ad5hGDxWmCMhmu52G4ZphEv7n3VQk8nl1kw5GEQctlA/8ddAcFCGjwC1Oix/aSZ1w5G/Mv150q6KPqRwakPPsQUNT88axEeSzG7Q3;",
+		"ID: 8Yxj1sakWu8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 2695834924... in GRP: 6SsQ/HAHUn...  OwnershipProof: Bueor1WImHOvaWnYzREROrO+15NoXHHkJqxZCTRVQFHmr49s2K1+1o7QoynfRvFoNfdi7Gblzd7rv5fIMh2GwIbkoLsFSzm0XzjvEssVbacG55mYkHQDXf8fw3602l3g8VoU1TsV5r0CW99pCsBtT1gRrBaUdSVHJPjLwXONzIjQZ4Hb8XXRrP9rumYtjB57FlrXFrVdeB5xXutFT0vVGBr/l1P4/CXNcUZEpPBbRmoJaX5SWlfqGwlMwz25Iso0/4OcsBSEGlDOTTE8MH8BYiDseHK+POQUolVYYQ/bJqXlsK1TPBnPDwAmUb3oBYSSNA/s1mNoxAozwKm71rcB/6QIUFrdiSxbhPWEtcvhSbu665AYmk3Qh8dbOqR+lAVnJ0rFlfiEcy1HMYvBiehrQXsL77Gipdj5QEAhFD+Na90HwAFsKBX2N2oPZfNAvBe/wL77U2aUcA6rUPqzatLnvpfTkZgoNRucWlpBUIRuAWdMsZggTFqg4Dy08scD0td+SsKx7bBgs42ROekFcJKcM7bt7YBzWtDdfeQBUfO6AjOE/pmH/9rtGaMPQPCLrMJX0IRFAY7pB3av3sKkXxw411/Sn1JdfBAwzprPX0ZCYRAt62a1  Facts: UqvuMBBw9/qOBX+rjhWrgFRjjutdNVdi4bW4/33JPJ3qimfZueTPThEradJSQt+A58swj0bEQmT/NYorArSzhdwgxbbxXbjZQZymy9IeZMyKOUJu/Z44h0hwzuEhAE8URf/HBT/Ddh1otZUTFHcUISyMCBvjsDPt271DoznXbWrzRYLbpUSl4j/p1+6Er+7E3r9YD1rg1UqSUYB6UuZh1FUKRbffLpeCmEz/SRdf0xLtCYkRK0b8OXQKaa3Snl3euAFxCSQA71dlvckUYW0EcQzi20c2q7pHDI43wmp8QDzMHRdq1DfCKL2WbkMKlv7fmojrx5wxZB/LI+T1X;",
+		"ID: sij0FdgAF1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 7970589899... in GRP: 6SsQ/HAHUn...  OwnershipProof: FjUO2mXwsCqY6ZgTgKuJ21XgF9J4y0t3ReeI6n3PCDjFLFDZfFinZpfsfzaUFiyJghZ4xPJBP3AfeYY/K6YZyMlfObuNWqZuYBWR8kwWAqaDvxBfHWv91y22LfCbudWqQq4rCl7FmmdH4DD8/VClzFjPOMEYY+09uu1aK0G7bkwY5RC8UcZZz/iose+IdDSUDNw18vsSxh78UwDrSGjtgjl7En7PBZAsmBI95xJx6EcHnQtqaiyDKCDqIxz+zlXoxLMPr4MN  Facts: ;",
+		"ID: Qk08ktHpcTwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD  DhPubKey: 8634899332... in GRP: 6SsQ/HAHUn...  OwnershipProof: C2FxLpJ09wxAzji8zUYo4m9mFEz7A4nSdU9b7RS8tHCjLgjIFbDKheupcpUknOdI0KrwXnoueFNfleBMD9Y1n4WMq8EQR+pd9pAQO7qnK/UJ6Uy+oXMYoDZxyAgw5+5t9axCL+qCA1JXg5ydcWcOuFVreKPSB3ph6BOw/BNTRQJz9lELiUf50YXg2X2J67COYcIwATtZdKohq3QWLP7z7h3UuEdLij8pBdolkZ6ep7EQiuDzA6FlybeM2Ol0JLxzAQnNll+i9YyLkMjai7Uh7ehI/Pjwu9SMF2Acx92JKK6TlZD+B9MUUob7K6YslJw88Xo6OwltKwaSMYl2BmBzQNGVVUxGIxoBskxw8ZYDnpnCfbUGRr/7KLeCpztJCRkbIDeIjz573wi2Y/BfsKqS8ghtePl8pAQF9D4y7YWiIVh765ikj9tGf/uQrx54ZgVXLwA4Xf7jBIj6bjbBvtfTydpCPqhdM10v5l8sC9nJaEizIav6X3DsLuuk+mSgbgkXs4Wn/6MdAQ==  Facts: UrwVSFzs67yCX7w==,UbjwYBN8R5AFUi/hsSHZEa0+E+Bh1xreC7ne1gd5EKq7TVzpT2jFNI4kQNvARtTB+77qyhhm0wcoYRouuoLfaAYcZXKV7J0P81dcBdEpukTouvkq4Q06/,UB4svYp78zmWbHTU5+ClA/W9R/yCwY+xMQDY5JZUK0s8q/MNPwQmmfrt46F9h1mf3Fu1xegZVg8tItYCbeS3Y0I0ewNQXEv/5d5B11w50EAeCWg+CqVSnkZw8Y5Dl0IS/M9tNAfqGeR0hfk8u4q9DittnhjrJ83moTlT6pYalSgms8FksF+vnoPkFwggVcobndU3WOklKgitR29gd4HgveN2F8jNC3K77YoRlenfgkYCRuQRrFARnUY4z+0UPOxaOjz63jGbGEUoldx2u6IhN1DwcpNlgCLUsV4f/re8XrtozQCDeMA==;",
+	}
+
+	// Generate test contacts
+	for i := 0; i < 10; i++ {
+		contacts = append(contacts, Contact{
+			ID:             id.NewIdFromUInt(prng.Uint64(), id.User, t),
+			DhPubKey:       getGroup().NewInt(prng.Int63()),
+			OwnershipProof: make([]byte, prng.Intn(512)),
+			Facts:          fact.FactList{},
+		})
+
+		prng.Read(contacts[i].OwnershipProof)
+
+		for j := 0; j < prng.Intn(5); j++ {
+			username := make([]byte, prng.Intn(255))
+			prng.Read(username)
+			newFact, err := fact.NewFact(fact.Username, base64.StdEncoding.EncodeToString(username))
+			if err != nil {
+				t.Errorf("Failed to generate new fact (%d %d): %+v", i, j, err)
+			}
+			contacts[i].Facts = append(contacts[i].Facts, newFact)
+		}
+
+		fmt.Printf("\"%s\",\n", contacts[i].String())
+	}
+
+	for i, c := range contacts {
+		if expectedContact[i] != c.String() {
+			t.Errorf("Contacts %d do not match.\nexpected: %s\nreceived: %s",
+				i, expectedContact[i], c)
+		}
+	}
+}
+
 // Happy path.
 func TestEqual(t *testing.T) {
 	a := Contact{
@@ -175,42 +397,61 @@ func TestEqual(t *testing.T) {
 
 	if !Equal(a, b) {
 		t.Errorf("Equal reported two equal contacts as different."+
-			"\na: %+v\nb: +%v", a, b)
+			"\na: %s\nb: %s", a, b)
 	}
 
 	if Equal(a, c) {
 		t.Errorf("Equal reported two unequal contacts as the same."+
-			"\na: %+v\nc: +%v", a, c)
+			"\na: %s\nc: %s", a, c)
+	}
+}
+
+// Happy path.
+func Test_getTagContents(t *testing.T) {
+	testData := map[string]string{
+		"test1": "adawdawd" + headTag + "test1" + footTag + "awdwdawd",
+		"test2": "adawdawd" + headTag + "test2" + footTag + "awdwdawd" + headTag + "test2" + footTag + "awdwdawd",
+	}
+
+	for expected, str := range testData {
+		received, err := getTagContents([]byte(str), headTag, footTag)
+		if err != nil {
+			t.Errorf("Failed to get tag contents from string %s", str)
+		}
+
+		if expected != string(received) {
+			t.Errorf("Failed to get the expected contents."+
+				"\nexpected: %s\nreceived: %s", expected, received)
+		}
+	}
+}
+
+// Error path.
+func Test_getTagContents_MissingTagsError(t *testing.T) {
+	testData := []string{
+		"adawdawd" + headTag + "test1" + "awdwdawd",
+		"adawdawd" + footTag + "test2" + headTag + "awdwdawd",
+		"adawdawd" + headTag + "test3" + "awdwdawd" + headTag + "test3" + "awdwdawd",
+	}
+
+	for _, str := range testData {
+		_, err := getTagContents([]byte(str), headTag, footTag)
+		if err == nil {
+			t.Errorf("Retrieved tag contents when tags are missing: %s", str)
+		}
 	}
 }
 
 func getCycInt(size int) *cyclic.Int {
-	var primeString = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E0" +
-		"88A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F" +
-		"14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDE" +
-		"E386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48" +
-		"361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED52907709" +
-		"6966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E8603" +
-		"9B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D22" +
-		"61898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458" +
-		"DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619" +
-		"DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE1" +
-		"17577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A" +
-		"92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150B" +
-		"DA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B" +
-		"2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
-		"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFF" +
-		"FFF"
-
-	buff, err := csprng.GenerateInGroup([]byte(primeString), size, csprng.NewSystemRNG())
+	buff, err := csprng.GenerateInGroup(getGroup().GetPBytes(), size, csprng.NewSystemRNG())
 	if err != nil {
 		panic(err)
 	}
 
-	grp := cyclic.NewGroup(large.NewIntFromString(primeString, 16),
+	cycInt := cyclic.NewGroup(large.NewIntFromBigInt(getGroup().GetP().BigInt()),
 		large.NewInt(2)).NewIntFromBytes(buff)
 
-	return grp
+	return cycInt
 }
 
 func getGroup() *cyclic.Group {
@@ -230,3 +471,72 @@ func getGroup() *cyclic.Group {
 			"84A09EC723742DC35873847AEF49F66E43873", 16),
 		large.NewIntFromString("2", 16))
 }
+
+// func Test_GenerateContact(t *testing.T) {
+// 	intString := "408f6ed2c7fddc4224df972a305dc7ce974ebf821266cee696cb206d21a3" +
+// 		"1d7c30fbc2d724fb7b16030adb486ac9d89b8b230a3f479f636a0f24fd0465d224608" +
+// 		"cb0a67e5e6682ab14c006330556d10e54447b81acfbd7012a762a95a1c04dd4beb76d" +
+// 		"9f94e712f309ca49b9c566a7545e2c8dea85abd40626a176d371950ccab5442bf5954" +
+// 		"f0f9136d788b1c938e4f4f29927a931e0dc97033ae5d6a8fc9adfbd774aea6230e1d6" +
+// 		"c064c1a995f033d026b050fd955fb1e791d15dd98ee6ff244a5f25c81f753bb82d18c" +
+// 		"e071ce5d79646f306d013d2a86555a0847134173fbf3a9b1eec15934d0af3d0405cac" +
+// 		"fb6425e7d83b20551230f535f87a4ac92c79e615c29571deeeff0d7b7298e1c03b02e" +
+// 		"1bc6e2c56ebea2ec1bffd200358ee52bd330853194632fd5229f08dbcc409b76edb0c" +
+// 		"9c6ed70914aea1be2f0baefff4b4b5578fb1f03b8c49f91498cc4dedf7d51c5c89f9e" +
+// 		"c31d50924ffa972c4e78d3df7649963adfb96cf267f28af15b42a6697635f9c9dc49c" +
+// 		"0ad4b4d45265e8c672643f01b5617a5c35fe24ca1fc92954"
+// 	example := Contact{
+// 		ID:       id.NewIdFromString("MyContactID", id.User, t),
+// 		DhPubKey: getGroup().NewIntFromString(intString, 16),
+// 		Facts: fact.FactList{
+// 			{Fact: "myUsername", T: fact.Username},
+// 			{Fact: "devinputvalidation@elixxir.io", T: fact.Email},
+// 			{Fact: "6502530000US", T: fact.Phone},
+// 		},
+// 	}
+//
+// 	exampleBase64 := base64.StdEncoding.EncodeToString(example.Marshal())
+// 	fmt.Printf("%s\n", example.Marshal())
+// 	fmt.Printf("%s\n", exampleBase64)
+//
+// 	err := utils.WriteFile("testContact.bin", example.Marshal(), utils.FilePerms, utils.DirPerms)
+// 	if err != nil {
+// 		t.Errorf("Failed to save contact file: %+v", err)
+// 	}
+//
+// 	qrCode, err := example.MakeQR(512, qrcode.Medium)
+// 	if err != nil {
+// 		t.Errorf("Failed to generate QR code: %+v", err)
+// 	}
+//
+// 	err = utils.WriteFile("testContactQR.png", qrCode, utils.FilePerms, utils.DirPerms)
+// 	if err != nil {
+// 		t.Errorf("Failed to save contact file: %+v", err)
+// 	}
+//
+// 	path := "newContact.bin"
+//
+// 	if !utils.FileExists(path) {
+// 		return
+// 	}
+//
+// 	newContactData, err := utils.ReadFile(path)
+// 	if err != nil {
+// 		t.Fatalf("Failed to read contact file: %+v", err)
+// 	}
+//
+// 	if !bytes.Equal(example.Marshal(), newContactData) {
+// 		t.Errorf("Contact base64 do not match.\nexpected: %s\nreceived: %s",
+// 			example.Marshal(), newContactData)
+// 	}
+//
+// 	newContact, err := Unmarshal(newContactData)
+// 	if err != nil {
+// 		t.Errorf("Failed to unmarshal contact: %+v", err)
+// 	}
+//
+// 	if !Equal(example, newContact) {
+// 		t.Errorf("Contact files do not match.\nexpected: %s\nreceived: %s",
+// 			example, newContact)
+// 	}
+// }