Skip to content
Snippets Groups Projects
Commit 8eaf2bce authored by Jono Wenger's avatar Jono Wenger
Browse files

XX-2971 / Contact compression

parent 8272ed16
No related branches found
No related tags found
No related merge requests found
......@@ -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
}
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
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment