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) +// } +// }