diff --git a/api/authenticatedChannel.go b/api/authenticatedChannel.go index 88682af983c107345b6415648bec9558f2eb4d6b..24b5a8e4af3c771d59dabc3e01d6ffa1b8125a17 100644 --- a/api/authenticatedChannel.go +++ b/api/authenticatedChannel.go @@ -125,3 +125,17 @@ func (c *Client) MakePrecannedContact(precannedID uint) contact.Contact { Facts: make([]fact.Fact, 0), } } + +// GetRelationshipFingerprint returns a unique 15 character fingerprint for an +// E2E relationship. An error is returned if no relationship with the partner +// is found. +func (c *Client) GetRelationshipFingerprint(partner *id.ID) (string, error) { + m, err := c.storage.E2e().GetPartner(partner) + if err != nil { + return "", errors.Errorf("could not get partner %s: %+v", partner, err) + } else if m == nil { + return "", errors.Errorf("manager for partner %s is nil.", partner) + } + + return m.GetRelationshipFingerprint(), nil +} diff --git a/bindings/authenticatedChannels.go b/bindings/authenticatedChannels.go index 30b12f0a1b3039fba5b18eafeff874cc7a438a60..da1d0d7ea128c15a6e4d407f8a7a88430666fd16 100644 --- a/bindings/authenticatedChannels.go +++ b/bindings/authenticatedChannels.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "gitlab.com/elixxir/crypto/contact" + "gitlab.com/xx_network/primitives/id" ) // Create an insecure e2e relationship with a precanned user @@ -123,3 +124,15 @@ func (c *Client) VerifyOwnership(receivedMarshaled, verifiedMarshaled []byte) (b return c.api.VerifyOwnership(received, verified), nil } + +// GetRelationshipFingerprint returns a unique 15 character fingerprint for an +// E2E relationship. An error is returned if no relationship with the partner +// is found. +func (c *Client) GetRelationshipFingerprint(partnerID []byte) (string, error) { + partner, err := id.Unmarshal(partnerID) + if err != nil { + return "", err + } + + return c.api.GetRelationshipFingerprint(partner) +} diff --git a/storage/e2e/manager.go b/storage/e2e/manager.go index 802e81ebe2329f5bd3714500e0229071eaea3bd3..6d7691b2b8f402c92ea6d4615a175b0b14b4b61e 100644 --- a/storage/e2e/manager.go +++ b/storage/e2e/manager.go @@ -8,6 +8,8 @@ package e2e import ( + "bytes" + "encoding/base64" "fmt" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" @@ -17,6 +19,8 @@ import ( "gitlab.com/elixxir/crypto/cyclic" dh "gitlab.com/elixxir/crypto/diffieHellman" "gitlab.com/xx_network/primitives/id" + "golang.org/x/crypto/blake2b" + "sort" ) const managerPrefix = "Manager{partner:%s}" @@ -198,3 +202,24 @@ func (m *Manager) GetMyOriginPrivateKey() *cyclic.Int { func (m *Manager) GetPartnerOriginPublicKey() *cyclic.Int { return m.originPartnerPubKey.DeepCopy() } + +const relationshipFpLength = 15 + +// GetRelationshipFingerprint returns a unique fingerprint for an E2E +// relationship. The fingerprint is a base 64 encoded hash of of the two +// relationship fingerprints truncated to 15 characters. +func (m *Manager) GetRelationshipFingerprint() string { + // Sort fingerprints + fps := [][]byte{m.receive.fingerprint, m.send.fingerprint} + less := func(i, j int) bool { return bytes.Compare(fps[i], fps[j]) == -1 } + sort.Slice(fps, less) + + // Hash fingerprints + h, _ := blake2b.New256(nil) + for _, fp := range fps { + h.Write(fp) + } + + // Base 64 encode hash and truncate + return base64.StdEncoding.EncodeToString(h.Sum(nil))[:relationshipFpLength] +} diff --git a/storage/e2e/manager_test.go b/storage/e2e/manager_test.go index 907bbff91b561d2267b71371356ae7bf6f332b86..195b0752a603002dcb395507fc0b7d361a0b235a 100644 --- a/storage/e2e/manager_test.go +++ b/storage/e2e/manager_test.go @@ -9,12 +9,14 @@ package e2e import ( "bytes" + "encoding/base64" "fmt" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/ekv" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/netTime" + "golang.org/x/crypto/blake2b" "math/rand" "reflect" "testing" @@ -283,3 +285,65 @@ func managersEqual(expected, received *Manager, t *testing.T) bool { return equal } + +// Unit test of Manager.GetRelationshipFingerprint. +func TestManager_GetRelationshipFingerprint(t *testing.T) { + m, _ := newTestManager(t) + m.receive.fingerprint = []byte{5} + m.send.fingerprint = []byte{10} + h, _ := blake2b.New256(nil) + h.Write(append(m.receive.fingerprint, m.send.fingerprint...)) + expected := base64.StdEncoding.EncodeToString(h.Sum(nil))[:relationshipFpLength] + + fp := m.GetRelationshipFingerprint() + if fp != expected { + t.Errorf("GetRelationshipFingerprint did not return the expected "+ + "fingerprint.\nexpected: %s\nreceived: %s", expected, fp) + } + + // Flip the order and show that the output is the same. + m.receive.fingerprint, m.send.fingerprint = m.send.fingerprint, m.receive.fingerprint + + fp = m.GetRelationshipFingerprint() + if fp != expected { + t.Errorf("GetRelationshipFingerprint did not return the expected "+ + "fingerprint.\nexpected: %s\nreceived: %s", expected, fp) + } +} + +// Tests the consistency of the output of Manager.GetRelationshipFingerprint. +func TestManager_GetRelationshipFingerprint_Consistency(t *testing.T) { + m, _ := newTestManager(t) + prng := rand.New(rand.NewSource(42)) + expectedFps := []string{ + "GmeTCfxGOqRqeID", "gbpJjHd3tIe8BKy", "2/ZdG+WNzODJBiF", + "+V1ySeDLQfQNSkv", "23OMC+rBmCk+gsu", "qHu5MUVs83oMqy8", + "kuXqxsezI0kS9Bc", "SlEhsoZ4BzAMTtr", "yG8m6SPQfV/sbTR", + "j01ZSSm762TH7mj", "SKFDbFvsPcohKPw", "6JB5HK8DHGwS4uX", + "dU3mS1ujduGD+VY", "BDXAy3trbs8P4mu", "I4HoXW45EwWR0oD", + "661YH2l2jfOkHbA", "cSS9ZyTOQKVx67a", "ojfubzDIsMNYc/t", + "2WrEw83Yz6Rhq9I", "TQILxBIUWMiQS2j", "rEqdieDTXJfCQ6I", + } + + for i, expected := range expectedFps { + prng.Read(m.receive.fingerprint) + prng.Read(m.send.fingerprint) + + fp := m.GetRelationshipFingerprint() + if fp != expected { + t.Errorf("GetRelationshipFingerprint did not return the expected "+ + "fingerprint (%d).\nexpected: %s\nreceived: %s", i, expected, fp) + } + + // Flip the order and show that the output is the same. + m.receive.fingerprint, m.send.fingerprint = m.send.fingerprint, m.receive.fingerprint + + fp = m.GetRelationshipFingerprint() + if fp != expected { + t.Errorf("GetRelationshipFingerprint did not return the expected "+ + "fingerprint (%d).\nexpected: %s\nreceived: %s", i, expected, fp) + } + + // fmt.Printf("\"%s\",\n", fp) // Uncomment to reprint expected values + } +}