github.com/status-im/status-go@v1.1.0/protocol/v1/membership_update_message_test.go (about)

     1  package protocol
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/golang/protobuf/proto"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/status-im/status-go/eth-node/crypto"
    11  	"github.com/status-im/status-go/protocol/protobuf"
    12  )
    13  
    14  var (
    15  	testMembershipUpdateMessageStruct = MembershipUpdateMessage{
    16  		ChatID: "chat-id",
    17  		Events: []MembershipUpdateEvent{
    18  			{
    19  				Type:       protobuf.MembershipUpdateEvent_CHAT_CREATED,
    20  				Name:       "thathata",
    21  				ChatID:     "chat-id",
    22  				ClockValue: 156897373998501,
    23  			},
    24  			{
    25  				Type:       protobuf.MembershipUpdateEvent_MEMBERS_ADDED,
    26  				Members:    []string{"0x04aebe2bb01a988abe7d978662f21de7760486119876c680e5a559e38e086a2df6dad41c4e4d9079c03db3bced6cb70fca76afc5650e50ea19b81572046a813534"},
    27  				ChatID:     "chat-id",
    28  				ClockValue: 156897373998502,
    29  			},
    30  		},
    31  	}
    32  )
    33  
    34  func TestSignMembershipUpdate(t *testing.T) {
    35  	key, err := crypto.HexToECDSA("838fbdd1b670209a258b90af25653a018bc582c44c56e6290a973eebbeb15732")
    36  	require.NoError(t, err)
    37  	event := &testMembershipUpdateMessageStruct.Events[0]
    38  	err = event.Sign(key)
    39  	require.NoError(t, err)
    40  
    41  	encodedEvent, err := proto.Marshal(event.ToProtobuf())
    42  	require.NoError(t, err)
    43  
    44  	var signatureMaterial []byte
    45  	signatureMaterial = append(signatureMaterial, []byte(testMembershipUpdateMessageStruct.ChatID)...)
    46  	signatureMaterial = crypto.Keccak256(append(signatureMaterial, encodedEvent...))
    47  	expected, err := crypto.Sign(signatureMaterial, key)
    48  	require.NoError(t, err)
    49  	require.Equal(t, encodedEvent, event.RawPayload)
    50  	require.Equal(t, expected, event.Signature)
    51  
    52  	// Sign the other event
    53  	err = testMembershipUpdateMessageStruct.Events[1].Sign(key)
    54  	require.NoError(t, err)
    55  
    56  	// Encode message
    57  	encodedMessage, err := testMembershipUpdateMessageStruct.ToProtobuf()
    58  	require.NoError(t, err)
    59  	// Verify it
    60  	verifiedMessage, err := MembershipUpdateMessageFromProtobuf(encodedMessage)
    61  	require.NoError(t, err)
    62  	require.Equal(t, verifiedMessage, &testMembershipUpdateMessageStruct)
    63  }
    64  
    65  func TestGroupCreator(t *testing.T) {
    66  	key, err := crypto.GenerateKey()
    67  	require.NoError(t, err)
    68  	g, err := NewGroupWithCreator("abc", "#fa6565", 20, key)
    69  	require.NoError(t, err)
    70  	creator, err := g.Creator()
    71  	require.NoError(t, err)
    72  	require.Equal(t, publicKeyToString(&key.PublicKey), creator)
    73  }
    74  
    75  func TestGroupProcessEvent(t *testing.T) {
    76  	createGroup := func(admins, members, joined []string, name string, color string, image string) Group {
    77  		return Group{
    78  			name:    name,
    79  			color:   color,
    80  			image:   []byte(image),
    81  			admins:  newStringSetFromSlice(admins),
    82  			members: newStringSetFromSlice(members),
    83  		}
    84  	}
    85  
    86  	const emptyName = ""
    87  	const emptyColor = ""
    88  	const emptyImage = ""
    89  
    90  	testCases := []struct {
    91  		Name   string
    92  		Group  Group
    93  		Result Group
    94  		From   string
    95  		Event  MembershipUpdateEvent
    96  	}{
    97  		{
    98  			Name:   "chat-created event",
    99  			Group:  createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
   100  			Result: createGroup([]string{"0xabc"}, []string{"0xabc"}, []string{"0xabc"}, "some-name", "#7cda00", emptyImage),
   101  			From:   "0xabc",
   102  			Event:  NewChatCreatedEvent("some-name", "#7cda00", 0),
   103  		},
   104  		{
   105  			Name:   "name-changed event",
   106  			Group:  createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
   107  			Result: createGroup(nil, nil, nil, "some-name", emptyColor, emptyImage),
   108  			From:   "0xabc",
   109  			Event:  NewNameChangedEvent("some-name", 0),
   110  		},
   111  		{
   112  			Name:   "color-changed event",
   113  			Group:  createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
   114  			Result: createGroup(nil, nil, nil, emptyName, "#7cda00", emptyImage),
   115  			From:   "0xabc",
   116  			Event:  NewColorChangedEvent("#7cda00", 0),
   117  		},
   118  		{
   119  			Name:   "image-changed event",
   120  			Group:  createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
   121  			Result: createGroup(nil, nil, nil, emptyName, emptyColor, "123"),
   122  			From:   "0xabc",
   123  			Event:  NewImageChangedEvent([]byte("123"), 0),
   124  		},
   125  		{
   126  			Name:   "admins-added event",
   127  			Group:  createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
   128  			Result: createGroup([]string{"0xabc", "0x123"}, nil, nil, emptyName, emptyColor, emptyImage),
   129  			From:   "0xabc",
   130  			Event:  NewAdminsAddedEvent([]string{"0xabc", "0x123"}, 0),
   131  		},
   132  		{
   133  			Name:   "admin-removed event",
   134  			Group:  createGroup([]string{"0xabc", "0xdef"}, nil, nil, emptyName, emptyColor, emptyImage),
   135  			Result: createGroup([]string{"0xdef"}, nil, nil, emptyName, emptyColor, emptyImage),
   136  			From:   "0xabc",
   137  			Event:  NewAdminRemovedEvent("0xabc", 0),
   138  		},
   139  		{
   140  			Name:   "members-added event",
   141  			Group:  createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
   142  			Result: createGroup(nil, []string{"0xabc", "0xdef"}, nil, emptyName, emptyColor, emptyImage),
   143  			From:   "0xabc",
   144  			Event:  NewMembersAddedEvent([]string{"0xabc", "0xdef"}, 0),
   145  		},
   146  		{
   147  			Name:   "member-removed event",
   148  			Group:  createGroup(nil, []string{"0xabc", "0xdef"}, []string{"0xdef", "0xabc"}, emptyName, emptyColor, emptyImage),
   149  			Result: createGroup(nil, []string{"0xdef"}, []string{"0xdef"}, emptyName, emptyColor, emptyImage),
   150  			From:   "0xabc",
   151  			Event:  NewMemberRemovedEvent("0xabc", 0),
   152  		},
   153  		{
   154  			Name:   "member-joined event",
   155  			Group:  createGroup(nil, []string{"0xabc", "0xdef"}, []string{"0xabc"}, emptyName, emptyColor, emptyImage),
   156  			Result: createGroup(nil, []string{"0xabc", "0xdef"}, []string{"0xabc", "0xdef"}, emptyName, emptyColor, emptyImage),
   157  			From:   "0xdef",
   158  			Event:  NewMemberJoinedEvent(0),
   159  		},
   160  	}
   161  
   162  	for _, tc := range testCases {
   163  		t.Run(tc.Name, func(t *testing.T) {
   164  			g := tc.Group
   165  			tc.Event.From = tc.From
   166  			g.processEvent(tc.Event)
   167  			require.EqualValues(t, tc.Result, g)
   168  		})
   169  	}
   170  }
   171  
   172  func TestGroupValidateEvent(t *testing.T) {
   173  	createGroup := func(admins, members []string) Group {
   174  		return Group{
   175  			admins:  newStringSetFromSlice(admins),
   176  			members: newStringSetFromSlice(members),
   177  		}
   178  	}
   179  	testCases := []struct {
   180  		Name   string
   181  		From   string
   182  		Group  Group
   183  		Event  MembershipUpdateEvent
   184  		Result bool
   185  	}{
   186  		{
   187  			Name:   "chat-created with empty admins and members",
   188  			Group:  createGroup(nil, nil),
   189  			From:   "0xabc",
   190  			Event:  NewChatCreatedEvent("test", "#fa6565", 0),
   191  			Result: true,
   192  		},
   193  		{
   194  			Name:   "chat-created with existing admins",
   195  			Group:  createGroup([]string{"0xabc"}, nil),
   196  			From:   "0xabc",
   197  			Event:  NewChatCreatedEvent("test", "#fa6565", 0),
   198  			Result: false,
   199  		},
   200  		{
   201  			Name:   "chat-created with existing members",
   202  			Group:  createGroup(nil, []string{"0xabc"}),
   203  			From:   "0xabc",
   204  			Event:  NewChatCreatedEvent("test", "#fa6565", 0),
   205  			Result: false,
   206  		},
   207  		{
   208  			Name:   "name-changed allowed because from is admin",
   209  			From:   "0xabc",
   210  			Group:  createGroup([]string{"0xabc"}, nil),
   211  			Event:  NewNameChangedEvent("new-name", 0),
   212  			Result: true,
   213  		},
   214  		{
   215  			Name:   "name-changed allowed because from is member",
   216  			From:   "0x123",
   217  			Group:  createGroup([]string{"0xabc"}, []string{"0x123"}),
   218  			Event:  NewNameChangedEvent("new-name", 0),
   219  			Result: true,
   220  		},
   221  		{
   222  			Name:   "color-changed allowed because from is admin",
   223  			From:   "0xabc",
   224  			Group:  createGroup([]string{"0xabc"}, nil),
   225  			Event:  NewColorChangedEvent("#7cda00", 0),
   226  			Result: true,
   227  		},
   228  		{
   229  			Name:   "color-changed allowed because from is member",
   230  			From:   "0x123",
   231  			Group:  createGroup([]string{"0xabc"}, []string{"0x123"}),
   232  			Event:  NewColorChangedEvent("#7cda00", 0),
   233  			Result: true,
   234  		},
   235  		{
   236  			Name:   "image-changed allowed because from is admin",
   237  			From:   "0xabc",
   238  			Group:  createGroup([]string{"0xabc"}, nil),
   239  			Event:  NewImageChangedEvent([]byte{1, 2, 3}, 0),
   240  			Result: true,
   241  		},
   242  		{
   243  			Name:   "image-changed not allowed for non-admins",
   244  			From:   "0xabc",
   245  			Group:  createGroup(nil, nil),
   246  			Event:  NewImageChangedEvent([]byte{1, 2, 3}, 0),
   247  			Result: false,
   248  		},
   249  		{
   250  			Name:   "members-added allowed because from is admin",
   251  			From:   "0xabc",
   252  			Group:  createGroup([]string{"0xabc"}, nil),
   253  			Event:  NewMembersAddedEvent([]string{"0x123"}, 0),
   254  			Result: true,
   255  		},
   256  		{
   257  			Name:   "members-added not allowed because from is member",
   258  			From:   "0x123",
   259  			Group:  createGroup([]string{"0xabc"}, []string{"0x123"}),
   260  			Event:  NewMembersAddedEvent([]string{"0x123"}, 0),
   261  			Result: true,
   262  		},
   263  		{
   264  			Name:   "member-removed allowed because removing themselves",
   265  			From:   "0xabc",
   266  			Group:  createGroup(nil, nil),
   267  			Event:  NewMemberRemovedEvent("0xabc", 0),
   268  			Result: true,
   269  		},
   270  		{
   271  			Name:   "member-removed allowed because from is admin",
   272  			From:   "0xabc",
   273  			Group:  createGroup([]string{"0xabc"}, []string{"0x123"}),
   274  			Event:  NewMemberRemovedEvent("0x123", 0),
   275  			Result: true,
   276  		},
   277  		{
   278  			Name:   "member-removed not allowed for non-admins",
   279  			From:   "0x123",
   280  			Group:  createGroup([]string{"0xabc"}, []string{"0x123", "0x456"}),
   281  			Event:  NewMemberRemovedEvent("0x456", 0),
   282  			Result: false,
   283  		},
   284  		{
   285  			Name:   "member-joined must be in members",
   286  			From:   "0xabc",
   287  			Group:  createGroup(nil, []string{"0xabc"}),
   288  			Event:  NewMemberJoinedEvent(0),
   289  			Result: true,
   290  		},
   291  		{
   292  			Name:   "member-joined not valid because not in members",
   293  			From:   "0xabc",
   294  			Group:  createGroup(nil, nil),
   295  			Event:  NewMemberJoinedEvent(0),
   296  			Result: false,
   297  		},
   298  		{
   299  			Name:   "member-joined not valid because from differs from the event",
   300  			From:   "0xdef",
   301  			Group:  createGroup(nil, nil),
   302  			Event:  NewMemberJoinedEvent(0),
   303  			Result: false,
   304  		},
   305  		{
   306  			Name:   "admins-added allowed because originating from other admin",
   307  			From:   "0xabc",
   308  			Group:  createGroup([]string{"0xabc", "0x123"}, []string{"0xdef", "0xghi"}),
   309  			Event:  NewAdminsAddedEvent([]string{"0xdef"}, 0),
   310  			Result: true,
   311  		},
   312  		{
   313  			Name:   "admins-added not allowed because not from admin",
   314  			From:   "0xabc",
   315  			Group:  createGroup([]string{"0x123"}, []string{"0xdef", "0xghi"}),
   316  			Event:  NewAdminsAddedEvent([]string{"0xdef"}, 0),
   317  			Result: false,
   318  		},
   319  		{
   320  			Name:   "admins-added not allowed because not in members",
   321  			From:   "0xabc",
   322  			Group:  createGroup([]string{"0xabc", "0x123"}, []string{"0xghi"}),
   323  			Event:  NewAdminsAddedEvent([]string{"0xdef"}, 0),
   324  			Result: false,
   325  		},
   326  		{
   327  			Name:   "admin-removed allowed because is admin and removes themselves",
   328  			From:   "0xabc",
   329  			Group:  createGroup([]string{"0xabc"}, nil),
   330  			Event:  NewAdminRemovedEvent("0xabc", 0),
   331  			Result: true,
   332  		},
   333  		{
   334  			Name:   "admin-removed not allowed because not themselves",
   335  			From:   "0xabc",
   336  			Group:  createGroup([]string{"0xabc", "0xdef"}, nil),
   337  			Event:  NewAdminRemovedEvent("0xdef", 0),
   338  			Result: false,
   339  		},
   340  		{
   341  			Name:   "admin-removed not allowed because not admin",
   342  			From:   "0xdef",
   343  			Group:  createGroup([]string{"0xabc"}, nil),
   344  			Event:  NewAdminRemovedEvent("0xabc", 0),
   345  			Result: false,
   346  		},
   347  	}
   348  
   349  	for _, tc := range testCases {
   350  		t.Run(tc.Name, func(t *testing.T) {
   351  			tc.Event.From = tc.From
   352  			result := tc.Group.validateEvent(tc.Event)
   353  			assert.Equal(t, tc.Result, result)
   354  		})
   355  	}
   356  }
   357  
   358  func TestMembershipUpdateEventEqual(t *testing.T) {
   359  	u1 := MembershipUpdateEvent{
   360  		Type:       protobuf.MembershipUpdateEvent_CHAT_CREATED,
   361  		ClockValue: 1,
   362  		Members:    []string{"0xabc"},
   363  		Name:       "abc",
   364  		Signature:  []byte("signature"),
   365  	}
   366  	require.True(t, u1.Equal(u1))
   367  
   368  	// Verify equality breaking.
   369  	u2 := u1
   370  	u2.Signature = []byte("different-signature")
   371  	require.False(t, u1.Equal(u2))
   372  }
   373  
   374  func TestAbridgedEventsNameChanged(t *testing.T) {
   375  	var clock uint64 = 0
   376  	creator, err := crypto.GenerateKey()
   377  	require.NoError(t, err)
   378  	creatorID := publicKeyToString(&creator.PublicKey)
   379  
   380  	g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
   381  	require.NoError(t, err)
   382  	clock++
   383  
   384  	// Full events is only a single one
   385  	require.Len(t, g.Events(), 1)
   386  	// same as abridged
   387  	require.Len(t, g.AbridgedEvents(), 1)
   388  
   389  	// We change name of the chat
   390  	nameChangedEvent1 := NewNameChangedEvent("name-1", clock)
   391  	nameChangedEvent1.From = creatorID
   392  	nameChangedEvent1.ChatID = g.chatID
   393  	err = g.ProcessEvent(nameChangedEvent1)
   394  	require.NoError(t, err)
   395  	clock++
   396  
   397  	// We change name of the chat again
   398  	nameChangedEvent2 := NewNameChangedEvent("name-2", clock)
   399  	nameChangedEvent2.From = creatorID
   400  	nameChangedEvent2.ChatID = g.chatID
   401  	err = g.ProcessEvent(nameChangedEvent2)
   402  	require.NoError(t, err)
   403  
   404  	// Full events is 3 events
   405  	require.Len(t, g.Events(), 3)
   406  	// While abridged should exclude the first name-1 event
   407  	require.Len(t, g.AbridgedEvents(), 2)
   408  	require.Equal(t, g.AbridgedEvents()[1].Name, "name-2")
   409  }
   410  
   411  func TestAbridgedEventsMembers(t *testing.T) {
   412  	var clock uint64 = 0
   413  	creator, err := crypto.GenerateKey()
   414  	require.NoError(t, err)
   415  	creatorID := publicKeyToString(&creator.PublicKey)
   416  
   417  	member1, err := crypto.GenerateKey()
   418  	require.NoError(t, err)
   419  	member1ID := publicKeyToString(&member1.PublicKey)
   420  
   421  	member2, err := crypto.GenerateKey()
   422  	require.NoError(t, err)
   423  	member2ID := publicKeyToString(&member2.PublicKey)
   424  
   425  	member3, err := crypto.GenerateKey()
   426  	require.NoError(t, err)
   427  	member3ID := publicKeyToString(&member3.PublicKey)
   428  
   429  	member4, err := crypto.GenerateKey()
   430  	require.NoError(t, err)
   431  	member4ID := publicKeyToString(&member4.PublicKey)
   432  
   433  	g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
   434  	require.NoError(t, err)
   435  	clock++
   436  
   437  	// Full events is only a single one
   438  	require.Len(t, g.Events(), 1)
   439  	// same as abridged
   440  	require.Len(t, g.AbridgedEvents(), 1)
   441  
   442  	// Add three new members
   443  	event := NewMembersAddedEvent([]string{member1ID, member2ID, member3ID}, clock)
   444  	event.From = creatorID
   445  	event.ChatID = g.chatID
   446  	err = g.ProcessEvent(event)
   447  	require.NoError(t, err)
   448  	clock++
   449  
   450  	require.Len(t, g.Events(), 2)
   451  	// All the events are relevant here, so it should be the same
   452  	require.Len(t, g.AbridgedEvents(), 2)
   453  
   454  	// We remove one of the users
   455  	event = NewMemberRemovedEvent(member3ID, clock)
   456  	event.From = creatorID
   457  	event.ChatID = g.chatID
   458  	err = g.ProcessEvent(event)
   459  	require.NoError(t, err)
   460  	clock++
   461  
   462  	require.Len(t, g.Events(), 3)
   463  	// All the events are relevant here, so it should be the same
   464  	require.Len(t, g.AbridgedEvents(), 3)
   465  
   466  	// Add a new member
   467  	event = NewMembersAddedEvent([]string{member4ID}, clock)
   468  	event.From = creatorID
   469  	event.ChatID = g.chatID
   470  	err = g.ProcessEvent(event)
   471  	require.NoError(t, err)
   472  	clock++
   473  
   474  	require.Len(t, g.Events(), 4)
   475  	// All the events are relevant here, so it should be the same
   476  	require.Len(t, g.AbridgedEvents(), 4)
   477  
   478  	// We remove the member just added
   479  	event = NewMemberRemovedEvent(member4ID, clock)
   480  	event.From = creatorID
   481  	event.ChatID = g.chatID
   482  	err = g.ProcessEvent(event)
   483  	require.NoError(t, err)
   484  
   485  	require.Len(t, g.Events(), 5)
   486  	// The previous two events, should be removed, because they have no impact
   487  	// on the chat history
   488  	abridgedEvents := g.AbridgedEvents()
   489  	require.Len(t, abridgedEvents, 3)
   490  
   491  	require.Equal(t, uint64(0), abridgedEvents[0].ClockValue)
   492  	require.Equal(t, uint64(1), abridgedEvents[1].ClockValue)
   493  	require.Equal(t, uint64(2), abridgedEvents[2].ClockValue)
   494  }
   495  
   496  func TestAbridgedEventsAdmins(t *testing.T) {
   497  	var clock uint64 = 0
   498  	creator, err := crypto.GenerateKey()
   499  	require.NoError(t, err)
   500  	creatorID := publicKeyToString(&creator.PublicKey)
   501  
   502  	member1, err := crypto.GenerateKey()
   503  	require.NoError(t, err)
   504  	member1ID := publicKeyToString(&member1.PublicKey)
   505  
   506  	member2, err := crypto.GenerateKey()
   507  	require.NoError(t, err)
   508  	member2ID := publicKeyToString(&member2.PublicKey)
   509  
   510  	member3, err := crypto.GenerateKey()
   511  	require.NoError(t, err)
   512  	member3ID := publicKeyToString(&member3.PublicKey)
   513  
   514  	g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
   515  	require.NoError(t, err)
   516  	clock++
   517  
   518  	// Full events is only a single one
   519  	require.Len(t, g.Events(), 1)
   520  	// same as abridged
   521  	require.Len(t, g.AbridgedEvents(), 1)
   522  
   523  	// Add three new members
   524  	event := NewMembersAddedEvent([]string{member1ID, member2ID, member3ID}, clock)
   525  	event.From = creatorID
   526  	event.ChatID = g.chatID
   527  	err = g.ProcessEvent(event)
   528  	require.NoError(t, err)
   529  	clock++
   530  
   531  	require.Len(t, g.Events(), 2)
   532  	// All the events are relevant here, so it should be the same
   533  	require.Len(t, g.AbridgedEvents(), 2)
   534  
   535  	// Make two of them admins
   536  	event = NewAdminsAddedEvent([]string{member1ID, member2ID}, clock)
   537  	event.From = creatorID
   538  	event.ChatID = g.chatID
   539  	err = g.ProcessEvent(event)
   540  	require.NoError(t, err)
   541  
   542  	require.Len(t, g.Events(), 3)
   543  	// All the events are relevant here, so it should be the same
   544  	require.Len(t, g.AbridgedEvents(), 3)
   545  }
   546  
   547  func TestWasEverMember(t *testing.T) {
   548  	key, err := crypto.GenerateKey()
   549  	require.NoError(t, err)
   550  	g, err := NewGroupWithCreator("abc", "#fa6565", 20, key)
   551  	require.NoError(t, err)
   552  
   553  	wasMember, err := g.WasEverMember(publicKeyToString(&key.PublicKey))
   554  	require.NoError(t, err)
   555  	require.True(t, wasMember)
   556  
   557  	key2, err := crypto.GenerateKey()
   558  	require.NoError(t, err)
   559  
   560  	wasMember, err = g.WasEverMember(publicKeyToString(&key2.PublicKey))
   561  	require.NoError(t, err)
   562  	require.False(t, wasMember)
   563  
   564  	// Add a new member
   565  	event := NewMembersAddedEvent([]string{publicKeyToString(&key2.PublicKey)}, 21)
   566  	event.From = publicKeyToString(&key.PublicKey)
   567  	event.ChatID = g.chatID
   568  	err = g.ProcessEvent(event)
   569  	require.NoError(t, err)
   570  
   571  	wasMember, err = g.WasEverMember(publicKeyToString(&key2.PublicKey))
   572  	require.NoError(t, err)
   573  	require.True(t, wasMember)
   574  }