diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go
index c060a74a167e5d6377077ee38ccc5b49284c40c1..7dd6322c39971968d9731374e96792b36dd40677 100644
--- a/interfaces/contact/contact.go
+++ b/interfaces/contact/contact.go
@@ -8,15 +8,15 @@
 package contact
 
 import (
-	"encoding/json"
+	"bytes"
+	"encoding/binary"
 	"github.com/pkg/errors"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/elixxir/primitives/fact"
 	"gitlab.com/xx_network/primitives/id"
 )
 
-const factDelimiter = ","
-const factBreak = ";"
+const sizeByteLength = 2
 
 // Contact implements the Contact interface defined in interface/contact.go,
 // in go, the structure is meant to be edited directly, the functions are for
@@ -28,21 +28,103 @@ 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).
+//
+// +----------+----------------+---------+----------+----------+----------------+----------+
+// | DhPubKey | OwnershipProof |  Facts  |    ID    |          |                |          |
+// |   size   |      size      |   size  |          | DhPubKey | OwnershipProof | FactList |
+// |  2 bytes |     2 bytes    | 2 bytes | 33 bytes |          |                |          |
+// +----------+----------------+---------+----------+----------+----------------+----------+
 func (c Contact) Marshal() ([]byte, error) {
-	return json.Marshal(&c)
+	var buff bytes.Buffer
+	b := make([]byte, sizeByteLength)
+
+	// Write size of DhPubKey
+	var dhPubKey []byte
+	var err error
+	if c.DhPubKey != nil {
+		dhPubKey, err = c.DhPubKey.GobEncode()
+		if err != nil {
+			return nil, errors.Errorf("Failed to gob encode DhPubKey: %+v", err)
+		}
+	}
+	binary.PutVarint(b, int64(len(dhPubKey)))
+	buff.Write(b)
+
+	// Write size of OwnershipProof
+	binary.PutVarint(b, int64(len(c.OwnershipProof)))
+	buff.Write(b)
+
+	// Write length of Facts
+	factList := c.Facts.Stringify()
+	binary.PutVarint(b, int64(len(factList)))
+	buff.Write(b)
+
+	// Write ID
+	if c.ID != nil {
+		buff.Write(c.ID.Marshal())
+	} else {
+		emptyID := make([]byte, id.ArrIDLen)
+		buff.Write(emptyID)
+	}
+
+	// Write DhPubKey
+	buff.Write(dhPubKey)
+
+	// Write OwnershipProof
+	buff.Write(c.OwnershipProof)
+
+	// Write fact list
+	buff.Write([]byte(factList))
+
+	return buff.Bytes(), nil
 }
 
+// Unmarshal decodes the byte slice produced by Contact.Marshal into a Contact.
 func Unmarshal(b []byte) (Contact, error) {
-	c := Contact{}
-	err := json.Unmarshal(b, &c)
+	c := Contact{DhPubKey: &cyclic.Int{}}
+	var err error
+	buf := bytes.NewBuffer(b)
+
+	// Get size (in bytes) of each field
+	dhPubKeySize, _ := binary.Varint(buf.Next(sizeByteLength))
+	ownershipProofSize, _ := binary.Varint(buf.Next(sizeByteLength))
+	factsSize, _ := binary.Varint(buf.Next(sizeByteLength))
+
+	// Get and unmarshal ID
+	c.ID, err = id.Unmarshal(buf.Next(id.ArrIDLen))
 	if err != nil {
-		return c, err
+		return c, errors.Errorf("Failed to unmarshal Contact ID: %+v", err)
+	}
+
+	// Handle nil ID
+	if bytes.Equal(c.ID.Marshal(), make([]byte, id.ArrIDLen)) {
+		c.ID = nil
 	}
-	for i, fact := range c.Facts {
-		if !fact.T.IsValid() {
-			return Contact{}, errors.Errorf("Fact %v/%v has invalid "+
-				"type: %s", i, len(c.Facts), fact.T)
+
+	// Get and decode DhPubKey
+	if dhPubKeySize == 0 {
+		// Handle nil key
+		c.DhPubKey = nil
+	} else {
+		err = c.DhPubKey.GobDecode(buf.Next(int(dhPubKeySize)))
+		if err != nil {
+			return c, errors.Errorf("Failed to gob decode Contact DhPubKey: %+v", err)
 		}
 	}
+
+	// Get OwnershipProof
+	c.OwnershipProof = buf.Next(int(ownershipProofSize))
+	if len(c.OwnershipProof) == 0 {
+		c.OwnershipProof = nil
+	}
+
+	// Get and unstringify fact list
+	c.Facts, _, err = fact.UnstringifyFactList(string(buf.Next(int(factsSize))))
+	if err != nil {
+		return c, errors.Errorf("Failed to unstringify Fact List: %+v", err)
+	}
+
 	return c, nil
 }
diff --git a/interfaces/contact/contact_test.go b/interfaces/contact/contact_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..eb365acf79b9251419fd4fe8a042c31843234620
--- /dev/null
+++ b/interfaces/contact/contact_test.go
@@ -0,0 +1,113 @@
+package contact
+
+import (
+	"encoding/json"
+	"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"
+	"math"
+	"math/rand"
+	"reflect"
+	"testing"
+)
+
+// Tests marshaling and unmarshalling of a common contact.
+func TestContact_Marshal_Unmarshal(t *testing.T) {
+	expectedContact := 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},
+		},
+	}
+
+	buff, err := expectedContact.Marshal()
+	if err != nil {
+		t.Errorf("Marshal() produced an error: %+v", err)
+	}
+
+	testContact, err := Unmarshal(buff)
+	if err != nil {
+		t.Errorf("Unmarshal() produced an error: %+v", err)
+	}
+
+	if !reflect.DeepEqual(expectedContact, testContact) {
+		t.Errorf("Unmarshaled Contact does not match expected."+
+			"\nexpected: %#v\nreceived: %#v", expectedContact, testContact)
+		t.Errorf("DhPubKey."+
+			"\nexpected: %+v\nreceived: %+v", expectedContact.DhPubKey.TextVerbose(10, math.MaxInt64), testContact.DhPubKey.TextVerbose(10, math.MaxInt64))
+	}
+}
+
+// Tests the size of marshaling and JSON marshaling of a Contact with a large
+// amount of data.
+func TestContact_Marshal_Size(t *testing.T) {
+	expectedContact := Contact{
+		ID:             id.NewIdFromUInt(rand.Uint64(), id.User, t),
+		DhPubKey:       getCycInt(512),
+		OwnershipProof: make([]byte, 1024),
+		Facts: fact.FactList{
+			{Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongUsername", T: fact.Username},
+			{Fact: "myVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongEmail@elixxir.io", T: fact.Email},
+			{Fact: "6502530000US", T: fact.Phone},
+		},
+	}
+	rand.Read(expectedContact.OwnershipProof)
+
+	buff, err := expectedContact.Marshal()
+	if err != nil {
+		t.Errorf("Marshal() produced an error: %+v", err)
+	}
+
+	marshalBuff, err := json.Marshal(expectedContact)
+	if err != nil {
+		t.Errorf("Marshal() produced an error: %+v", err)
+	}
+
+	t.Logf("size of buff:        %d", len(buff))
+	t.Logf("size of marshalBuff: %d", len(marshalBuff))
+	t.Logf("ratio: %.2f%%", float32(len(buff))/float32(len(marshalBuff))*100)
+	t.Logf("%s", marshalBuff)
+
+	if len(marshalBuff) < len(buff) {
+		t.Errorf("JSON Contact smaller than marshaled contact."+
+			"\nJSON:    %d\nmarshal: %d", len(marshalBuff), len(buff))
+	}
+}
+
+func getCycInt(size int) *cyclic.Int {
+	var primeString = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
+		"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
+		"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
+		"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
+		"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
+		"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
+		"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
+		"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
+		"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
+		"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
+		"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" +
+		"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" +
+		"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" +
+		"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
+		"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" +
+		"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" +
+		"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" +
+		"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" +
+		"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" +
+		"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" +
+		"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" +
+		"FFFFFFFFFFFFFFFF"
+	buff, err := csprng.GenerateInGroup([]byte(primeString), size, csprng.NewSystemRNG())
+	if err != nil {
+		panic(err)
+	}
+
+	grp := cyclic.NewGroup(large.NewIntFromString(primeString, 16), large.NewInt(2)).NewIntFromBytes(buff)
+	return grp
+}