From f8a3912199841ce94c0fa30e6154148a6e228787 Mon Sep 17 00:00:00 2001
From: Benjamin Wenger <ben@elixxir.ioo>
Date: Thu, 15 Oct 2020 13:43:12 -0700
Subject: [PATCH] finished fact stringification

---
 auth/baseFormat.go             | 85 ++++++++++++++++++++++++++++++++++
 auth/ecrformat.go              | 73 +++++++++++++++++++++++++++++
 auth/requestFormat.go          |  8 ++++
 interfaces/contact/contact.go  | 41 ++++++++++++++++
 interfaces/contact/fact.go     | 36 ++++++--------
 interfaces/contact/factList.go | 10 ++--
 interfaces/contact/type.go     | 31 ++++++++++++-
 7 files changed, 258 insertions(+), 26 deletions(-)
 create mode 100644 auth/baseFormat.go
 create mode 100644 auth/ecrformat.go
 create mode 100644 auth/requestFormat.go

diff --git a/auth/baseFormat.go b/auth/baseFormat.go
new file mode 100644
index 000000000..1e39a57ef
--- /dev/null
+++ b/auth/baseFormat.go
@@ -0,0 +1,85 @@
+package auth
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/crypto/cyclic"
+)
+
+const saltSize = 32
+
+type format struct {
+	data       []byte
+	pubkey     []byte
+	salt       []byte
+	ecrPayload []byte
+}
+
+func newFormat(payloadSize, pubkeySize uint, pubkey *cyclic.Int,
+	salt []byte) format {
+
+	if len(salt) != saltSize {
+		jww.FATAL.Panicf("Salt is wrong size, should be %v, is %v",
+			saltSize, len(salt))
+	}
+
+	if payloadSize < pubkeySize+saltSize {
+		jww.FATAL.Panicf("Size of format is too small, must be big " +
+			"enough to contain public key and salt")
+	}
+
+	f := buildFormat(make([]byte, payloadSize), pubkeySize)
+
+	copy(f.pubkey, pubkey.LeftpadBytes(uint64(pubkeySize)))
+	copy(f.salt, salt)
+
+	return f
+}
+
+func buildFormat(data []byte, pubkeySize uint) format {
+	f := format{
+		data: data,
+	}
+
+	f.pubkey = f.data[:pubkeySize]
+	f.salt = f.data[pubkeySize : pubkeySize+saltSize]
+	f.ecrPayload = f.data[pubkeySize+saltSize:]
+	return f
+}
+
+func unmarshalFormat(b []byte, pubkeySize uint) (format, error) {
+	if uint(len(b)) < pubkeySize+saltSize {
+		return format{}, errors.New("Received format too small")
+	}
+
+	return buildFormat(b, pubkeySize), nil
+}
+
+func (f format) Marshal() []byte {
+	return f.data
+}
+
+func (f format) GetPubKey(grp *cyclic.Group) *cyclic.Int {
+	return grp.NewIntFromBytes(f.pubkey)
+}
+
+func (f format) GetSalt() []byte {
+	return f.salt
+}
+
+func (f format) GetEcrPayload() []byte {
+	return f.ecrPayload
+}
+
+func (f format) GetEcrPayloadLen() int {
+	return len(f.ecrPayload)
+}
+
+func (f format) SetEcrPayload(ecr []byte) {
+	if len(ecr) != len(f.ecrPayload) {
+		jww.FATAL.Panicf("Passed ecr payload incorrect lengh. Expected:"+
+			" %v, Recieved: %v", len(f.ecrPayload), len(ecr))
+	}
+
+	copy(f.ecrPayload, ecr)
+}
diff --git a/auth/ecrformat.go b/auth/ecrformat.go
new file mode 100644
index 000000000..7943c501d
--- /dev/null
+++ b/auth/ecrformat.go
@@ -0,0 +1,73 @@
+package auth
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+)
+
+const ownershipSize = 32
+
+type ecrFormat struct {
+	data      []byte
+	ownership []byte
+	payload   []byte
+}
+
+func newEcrFormat(size uint, ownership []byte) ecrFormat {
+	if size < ownershipSize {
+		jww.FATAL.Panicf("Size too small to hold")
+	}
+
+	if len(ownership) != ownershipSize {
+		jww.FATAL.Panicf("ownership proof is the wrong size")
+	}
+
+	f := buildEcrFormat(make([]byte, size))
+
+	copy(f.ownership, ownership)
+
+	return f
+
+}
+
+func buildEcrFormat(data []byte) ecrFormat {
+	f := ecrFormat{
+		data: data,
+	}
+
+	f.ownership = f.data[:ownershipSize]
+	f.payload = f.data[ownershipSize:]
+	return f
+}
+
+func unmarshalEcrFormat(b []byte) (ecrFormat, error) {
+	if len(b) < ownershipSize {
+		return ecrFormat{}, errors.New("Received ecr format too small")
+	}
+
+	return buildEcrFormat(b), nil
+}
+
+func (f ecrFormat) Marshal() []byte {
+	return f.data
+}
+
+func (f ecrFormat) GetOwnership() []byte {
+	return f.ownership
+}
+
+func (f ecrFormat) GetPayload() []byte {
+	return f.payload
+}
+
+func (f ecrFormat) GetPayloadSize() int {
+	return len(f.payload)
+}
+
+func (f ecrFormat) SetPayload(p []byte) {
+	if len(p) != len(f.payload) {
+		jww.FATAL.Panicf("Payload is the wrong length")
+	}
+
+	copy(f.payload, p)
+}
diff --git a/auth/requestFormat.go b/auth/requestFormat.go
new file mode 100644
index 000000000..de8e5031e
--- /dev/null
+++ b/auth/requestFormat.go
@@ -0,0 +1,8 @@
+package auth
+
+type requestFormat struct {
+	ecrFormat
+	id      []byte
+	facts   []byte
+	message []byte
+}
diff --git a/interfaces/contact/contact.go b/interfaces/contact/contact.go
index f67175fc8..769c7e826 100644
--- a/interfaces/contact/contact.go
+++ b/interfaces/contact/contact.go
@@ -6,8 +6,14 @@ import (
 	"gitlab.com/elixxir/client/interfaces"
 	"gitlab.com/elixxir/crypto/cyclic"
 	"gitlab.com/xx_network/primitives/id"
+	"strings"
+	jww "github.com/spf13/jwalterweatherman"
 )
 
+const factDelimiter = ","
+const factBreak = ";"
+
+
 // 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
@@ -38,10 +44,22 @@ func (c Contact) GetFactList() interfaces.FactList {
 	return FactList{source: &c}
 }
 
+// json marshals the contact
 func (c Contact) Marshal() ([]byte, error) {
 	return json.Marshal(&c)
 }
 
+// converts facts to a delineated string with an ending character for transfer
+// over the network
+func (c Contact) StringifyFacts() string {
+	stringList := make([]string, len(c.Facts))
+	for index, f := range c.Facts {
+		stringList[index] = f.Stringify()
+	}
+
+	return strings.Join(stringList, factDelimiter) + factBreak
+}
+
 func Unmarshal(b []byte) (Contact, error) {
 	c := Contact{}
 	err := json.Unmarshal(b, &c)
@@ -56,3 +74,26 @@ func Unmarshal(b []byte) (Contact, error) {
 	}
 	return c, nil
 }
+
+// splits the "facts" portion of the payload from the rest and returns them as
+// facts
+func UnstringifyFacts(s string) ([]Fact, string, error) {
+	parts := strings.SplitN(s, factBreak, 1)
+	if len(parts) != 2 {
+		return nil, "", errors.New("Invalid fact string passed")
+	}
+	factStrings := strings.Split(parts[0], factDelimiter)
+
+	var factList []Fact
+	for _, fString := range factStrings {
+		fact, err := UnstringifyFact(fString)
+		if err != nil {
+			jww.WARN.Printf("Fact failed to unstringify, dropped: %s",
+				err)
+		} else {
+			factList = append(factList, fact)
+		}
+
+	}
+	return factList, parts[1], nil
+}
diff --git a/interfaces/contact/fact.go b/interfaces/contact/fact.go
index a5af2ab57..298b80b2a 100644
--- a/interfaces/contact/fact.go
+++ b/interfaces/contact/fact.go
@@ -1,14 +1,18 @@
 package contact
 
-import (
-	"github.com/pkg/errors"
-)
-
 type Fact struct {
 	Fact string
 	T    FactType
 }
 
+func NewFact(ft FactType, fact string) (Fact, error) {
+	//todo: filter the fact string
+	return Fact{
+		Fact: fact,
+		T:    ft,
+	}, nil
+}
+
 func (f Fact) Get() string {
 	return f.Fact
 }
@@ -18,25 +22,15 @@ func (f Fact) Type() int {
 }
 
 // marshal is for transmission for UDB, not a part of the fact interface
-func (f Fact) Marshal() []byte {
-	serial := []byte(f.Fact)
-	b := make([]byte, len(serial)+1)
-	b[0] = byte(f.T)
-
-	copy(b[1:len(serial)-1], serial)
-	return b
+func (f Fact) Stringify() string {
+	return f.T.Stringify() + f.Fact
 }
 
-func UnmarshalFact(b []byte) (Fact, error) {
-	t := FactType(b[0])
-	if !t.IsValid() {
-		return Fact{}, errors.Errorf("Fact is not a valid type: %s", t)
+func UnstringifyFact(s string) (Fact, error) {
+	ft, err := UnstringifyFactType(s)
+	if err != nil {
+		return Fact{}, err
 	}
 
-	f := string(b[1:])
-
-	return Fact{
-		Fact: f,
-		T:    t,
-	}, nil
+	return NewFact(ft, s)
 }
diff --git a/interfaces/contact/factList.go b/interfaces/contact/factList.go
index 5c39398fc..bf4cd5015 100644
--- a/interfaces/contact/factList.go
+++ b/interfaces/contact/factList.go
@@ -22,9 +22,11 @@ func (fl FactList) Add(fact string, factType int) error {
 	if !ft.IsValid() {
 		return errors.New("Invalid fact type")
 	}
-	fl.source.Facts = append(fl.source.Facts, Fact{
-		Fact: fact,
-		T:    ft,
-	})
+	f, err := NewFact(ft, fact)
+	if err != nil {
+		return err
+	}
+
+	fl.source.Facts = append(fl.source.Facts, f)
 	return nil
 }
diff --git a/interfaces/contact/type.go b/interfaces/contact/type.go
index 1b9bb8989..cc7263d5f 100644
--- a/interfaces/contact/type.go
+++ b/interfaces/contact/type.go
@@ -1,6 +1,10 @@
 package contact
 
-import "fmt"
+import (
+	"fmt"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+)
 
 type FactType uint8
 
@@ -23,6 +27,31 @@ func (t FactType) String() string {
 	}
 }
 
+func (t FactType) Stringify() string {
+	switch t {
+	case Username:
+		return "U"
+	case Email:
+		return "E"
+	case Phone:
+		return "P"
+	}
+	jww.FATAL.Panicf("Unknown Fact FactType: %d", t)
+	return "error"
+}
+
+func UnstringifyFactType(s string) (FactType, error) {
+	switch s {
+	case "U":
+		return Username, nil
+	case "E":
+		return Email, nil
+	case "P":
+		return Phone, nil
+	}
+	return 3, errors.Errorf("Unknown Fact FactType: %s", s)
+}
+
 func (t FactType) IsValid() bool {
 	return t == Username || t == Email || t == Phone
 }
-- 
GitLab