diff --git a/account/accounts.go b/account/accounts.go index cfb593f84..d6ef097d0 100644 --- a/account/accounts.go +++ b/account/accounts.go @@ -54,7 +54,23 @@ var zeroAddress = types.Address{} type SignParams struct { Data interface{} `json:"data"` Address string `json:"account"` - Password string `json:"password"` + Password string `json:"password,omitempty"` +} + +func (sp *SignParams) Validate(checkPassword bool) error { + if len(sp.Address) != 2*types.AddressLength+2 { + return errors.New("address has to be provided") + } + + if sp.Data == "" { + return errors.New("data has to be provided") + } + + if checkPassword && sp.Password == "" { + return errors.New("password has to be provided") + } + + return nil } type RecoverParams struct { diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 9e44f8b0e..1e51560ce 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -3248,25 +3248,32 @@ func (m *Manager) SaveRequestToJoinAndCommunity(requestToJoin *RequestToJoin, co return community, requestToJoin, nil } -func (m *Manager) CreateRequestToJoin(requester *ecdsa.PublicKey, request *requests.RequestToJoinCommunity) (*Community, *RequestToJoin, error) { - community, err := m.GetByID(request.CommunityID) +func (m *Manager) CheckCommunityForJoining(communityID types.HexBytes) (*Community, error) { + community, err := m.GetByID(communityID) if err != nil { - return nil, nil, err + return nil, err } - - err = community.updateCommunityDescriptionByEvents() - if err != nil { - return nil, nil, err + if community == nil { + return nil, ErrOrgNotFound } // We don't allow requesting access if already joined if community.Joined() { - return nil, nil, ErrAlreadyJoined + return nil, ErrAlreadyJoined } + err = community.updateCommunityDescriptionByEvents() + if err != nil { + return nil, err + } + + return community, nil +} + +func (m *Manager) CreateRequestToJoin(request *requests.RequestToJoinCommunity) *RequestToJoin { clock := uint64(time.Now().Unix()) requestToJoin := &RequestToJoin{ - PublicKey: common.PubkeyToHex(requester), + PublicKey: common.PubkeyToHex(&m.identity.PublicKey), Clock: clock, ENSName: request.ENSName, CommunityID: request.CommunityID, @@ -3277,7 +3284,21 @@ func (m *Manager) CreateRequestToJoin(requester *ecdsa.PublicKey, request *reque requestToJoin.CalculateID() - return community, requestToJoin, nil + addSignature := len(request.Signatures) == len(request.AddressesToReveal) + for i := range request.AddressesToReveal { + revealedAcc := &protobuf.RevealedAccount{ + Address: request.AddressesToReveal[i], + IsAirdropAddress: types.HexToAddress(request.AddressesToReveal[i]) == types.HexToAddress(request.AirdropAddress), + } + + if addSignature { + revealedAcc.Signature = request.Signatures[i] + } + + requestToJoin.RevealedAccounts = append(requestToJoin.RevealedAccounts, revealedAcc) + } + + return requestToJoin } func (m *Manager) SaveRequestToJoin(request *RequestToJoin) error { diff --git a/protocol/communities_events_utils_test.go b/protocol/communities_events_utils_test.go index cf0ae61d3..a30e4e972 100644 --- a/protocol/communities_events_utils_test.go +++ b/protocol/communities_events_utils_test.go @@ -122,19 +122,17 @@ func setUpCommunityAndRoles(base CommunityEventsTestsInterface, role protobuf.Co request := &requests.RequestToJoinCommunity{ CommunityID: community.ID(), AddressesToReveal: []string{eventsSenderAccountAddress}, - Password: accountPassword, AirdropAddress: eventsSenderAccountAddress, } - joinCommunity(suite, community, base.GetControlNode(), base.GetEventSender(), request) + joinCommunity(suite, community, base.GetControlNode(), base.GetEventSender(), request, accountPassword) refreshMessengerResponses(base) request = &requests.RequestToJoinCommunity{ CommunityID: community.ID(), AddressesToReveal: []string{aliceAccountAddress}, - Password: accountPassword, AirdropAddress: aliceAccountAddress, } - joinCommunity(suite, community, base.GetControlNode(), base.GetMember(), request) + joinCommunity(suite, community, base.GetControlNode(), base.GetMember(), request, accountPassword) refreshMessengerResponses(base) // grant permissions to the event sender @@ -426,7 +424,6 @@ func setUpOnRequestCommunityAndRoles(base CommunityEventsTestsInterface, role pr CommunityID: community.ID(), AddressesToReveal: []string{eventsSenderAccountAddress}, ENSName: "eventSender", - Password: accountPassword, AirdropAddress: eventsSenderAccountAddress, } @@ -436,7 +433,6 @@ func setUpOnRequestCommunityAndRoles(base CommunityEventsTestsInterface, role pr CommunityID: community.ID(), AddressesToReveal: []string{aliceAccountAddress}, ENSName: "alice", - Password: accountPassword, AirdropAddress: aliceAccountAddress, } joinOnRequestCommunity(s, community, base.GetControlNode(), base.GetMember(), requestMember) @@ -2040,7 +2036,6 @@ func testJoinedPrivilegedMemberReceiveRequestsToJoin(base CommunityEventsTestsIn CommunityID: community.ID(), AddressesToReveal: []string{bobAccountAddress}, ENSName: "bob", - Password: accountPassword, AirdropAddress: bobAccountAddress, } @@ -2050,7 +2045,6 @@ func testJoinedPrivilegedMemberReceiveRequestsToJoin(base CommunityEventsTestsIn CommunityID: community.ID(), AddressesToReveal: []string{eventsSenderAccountAddress}, ENSName: "newPrivilegedUser", - Password: accountPassword, AirdropAddress: eventsSenderAccountAddress, } @@ -2118,7 +2112,6 @@ func testMemberReceiveRequestsToJoinAfterGettingNewRole(base CommunityEventsTest CommunityID: community.ID(), AddressesToReveal: []string{aliceAccountAddress}, ENSName: "alice", - Password: accountPassword, AirdropAddress: aliceAccountAddress, } @@ -2128,7 +2121,6 @@ func testMemberReceiveRequestsToJoinAfterGettingNewRole(base CommunityEventsTest CommunityID: community.ID(), AddressesToReveal: []string{bobAccountAddress}, ENSName: "bob", - Password: accountPassword, AirdropAddress: bobAccountAddress, } @@ -2138,7 +2130,6 @@ func testMemberReceiveRequestsToJoinAfterGettingNewRole(base CommunityEventsTest CommunityID: community.ID(), AddressesToReveal: []string{eventsSenderAccountAddress}, ENSName: "eventSender", - Password: accountPassword, AirdropAddress: eventsSenderAccountAddress, } diff --git a/protocol/communities_messenger_helpers_test.go b/protocol/communities_messenger_helpers_test.go index 447b87936..ade5cc101 100644 --- a/protocol/communities_messenger_helpers_test.go +++ b/protocol/communities_messenger_helpers_test.go @@ -429,7 +429,30 @@ func advertiseCommunityTo(s *suite.Suite, community *communities.Community, owne s.Require().NoError(err) } -func joinCommunity(s *suite.Suite, community *communities.Community, owner *Messenger, user *Messenger, request *requests.RequestToJoinCommunity) { +func joinCommunity(s *suite.Suite, community *communities.Community, owner *Messenger, user *Messenger, request *requests.RequestToJoinCommunity, password string) { + if password != "" { + signingParams, err := user.GenerateJoiningCommunityRequestsForSigning(common.PubkeyToHex(&user.identity.PublicKey), community.ID(), request.AddressesToReveal) + s.Require().NoError(err) + + for i := range signingParams { + signingParams[i].Password = password + } + signatures, err := user.SignData(signingParams) + s.Require().NoError(err) + + updateAddresses := len(request.AddressesToReveal) == 0 + if updateAddresses { + request.AddressesToReveal = make([]string, len(signingParams)) + } + for i := range signingParams { + request.AddressesToReveal[i] = signingParams[i].Address + request.Signatures = append(request.Signatures, types.FromHex(signatures[i])) + } + if updateAddresses { + request.AirdropAddress = request.AddressesToReveal[0] + } + } + response, err := user.RequestToJoinCommunity(request) s.Require().NoError(err) s.Require().NotNil(response) diff --git a/protocol/communities_messenger_signers_test.go b/protocol/communities_messenger_signers_test.go index 46d8ee1ae..747f82eee 100644 --- a/protocol/communities_messenger_signers_test.go +++ b/protocol/communities_messenger_signers_test.go @@ -95,7 +95,7 @@ func (s *MessengerCommunitiesSignersSuite) advertiseCommunityTo(controlNode *Mes func (s *MessengerCommunitiesSignersSuite) joinCommunity(controlNode *Messenger, community *communities.Community, user *Messenger) { request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} - joinCommunity(&s.Suite, community, controlNode, user, request) + joinCommunity(&s.Suite, community, controlNode, user, request, "") } func (s *MessengerCommunitiesSignersSuite) joinOnRequestCommunity(controlNode *Messenger, community *communities.Community, user *Messenger) { diff --git a/protocol/communities_messenger_test.go b/protocol/communities_messenger_test.go index 1a7cedba7..9ced927e9 100644 --- a/protocol/communities_messenger_test.go +++ b/protocol/communities_messenger_test.go @@ -392,7 +392,7 @@ func (s *MessengerCommunitiesSuite) advertiseCommunityTo(community *communities. func (s *MessengerCommunitiesSuite) joinCommunity(community *communities.Community, owner *Messenger, user *Messenger) { request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} - joinCommunity(&s.Suite, community, owner, user, request) + joinCommunity(&s.Suite, community, owner, user, request, "") } func (s *MessengerCommunitiesSuite) TestCommunityContactCodeAdvertisement() { @@ -3246,7 +3246,7 @@ func (s *MessengerCommunitiesSuite) TestCommunityBanUserRequestToJoin() { request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} // We try to join the org - _, rtj, err := s.alice.communitiesManager.CreateRequestToJoin(&s.alice.identity.PublicKey, request) + rtj := s.alice.communitiesManager.CreateRequestToJoin(request) s.Require().NoError(err) diff --git a/protocol/communities_messenger_token_permissions_test.go b/protocol/communities_messenger_token_permissions_test.go index 6b7f54da7..1eeb7c525 100644 --- a/protocol/communities_messenger_token_permissions_test.go +++ b/protocol/communities_messenger_token_permissions_test.go @@ -196,8 +196,8 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) joinCommunityWithAirdropAddr airdropAddress = addresses[0] } - request := &requests.RequestToJoinCommunity{CommunityID: community.ID(), Password: passwdHash, AddressesToReveal: addresses, AirdropAddress: airdropAddress} - joinCommunity(&s.Suite, community, s.owner, user, request) + request := &requests.RequestToJoinCommunity{CommunityID: community.ID(), AddressesToReveal: addresses, AirdropAddress: airdropAddress} + joinCommunity(&s.Suite, community, s.owner, user, request, passwdHash) } func (s *MessengerCommunitiesTokenPermissionsSuite) advertiseCommunityTo(community *communities.Community, user *Messenger) { @@ -529,8 +529,30 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestEditSharedAddresses() { s.Require().Equal(alicesRevealedAccounts[0].Address, aliceAddress2) s.Require().Equal(true, alicesRevealedAccounts[0].IsAirdropAddress) + request := &requests.EditSharedAddresses{CommunityID: community.ID(), AddressesToReveal: []string{aliceAddress1}, AirdropAddress: aliceAddress1} + + signingParams, err := s.alice.GenerateJoiningCommunityRequestsForSigning(common.PubkeyToHex(&s.alice.identity.PublicKey), community.ID(), request.AddressesToReveal) + s.Require().NoError(err) + passwdHash := types.EncodeHex(crypto.Keccak256([]byte(alicePassword))) - request := &requests.EditSharedAddresses{CommunityID: community.ID(), Password: passwdHash, AddressesToReveal: []string{aliceAddress1}, AirdropAddress: aliceAddress1} + for i := range signingParams { + signingParams[i].Password = passwdHash + } + signatures, err := s.alice.SignData(signingParams) + s.Require().NoError(err) + + updateAddresses := len(request.AddressesToReveal) == 0 + if updateAddresses { + request.AddressesToReveal = make([]string, len(signingParams)) + } + for i := range signingParams { + request.AddressesToReveal[i] = signingParams[i].Address + request.Signatures = append(request.Signatures, types.FromHex(signatures[i])) + } + if updateAddresses { + request.AirdropAddress = request.AddressesToReveal[0] + } + response, err := s.alice.EditSharedAddressesForCommunity(request) s.Require().NoError(err) s.Require().NotNil(response) @@ -670,8 +692,7 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestBecomeMemberPermissions( s.Require().ErrorContains(err, "no messages") // bob tries to join, but he doesn't satisfy so the request isn't sent - passwdHash := types.EncodeHex(crypto.Keccak256([]byte(bobPassword))) - request := &requests.RequestToJoinCommunity{CommunityID: community.ID(), Password: passwdHash, AddressesToReveal: []string{bobAddress}, AirdropAddress: bobAddress} + request := &requests.RequestToJoinCommunity{CommunityID: community.ID(), AddressesToReveal: []string{bobAddress}, AirdropAddress: bobAddress} _, err = s.bob.RequestToJoinCommunity(request) s.Require().Error(err) s.Require().ErrorContains(err, "permission to join not satisfied") diff --git a/protocol/messenger_activity_center_test.go b/protocol/messenger_activity_center_test.go index 92fd23dd2..1410ef9cb 100644 --- a/protocol/messenger_activity_center_test.go +++ b/protocol/messenger_activity_center_test.go @@ -34,7 +34,7 @@ func (s *MessengerActivityCenterMessageSuite) advertiseCommunityTo(community *co func (s *MessengerActivityCenterMessageSuite) joinCommunity(community *communities.Community, owner *Messenger, user *Messenger) { request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} - joinCommunity(&s.Suite, community, owner, user, request) + joinCommunity(&s.Suite, community, owner, user, request, "") } type MessengerActivityCenterMessageSuite struct { diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index 48387ad90..960d6be92 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -982,17 +982,15 @@ func (m *Messenger) SetMutePropertyOnChatsByCategory(request *requests.MuteCateg return nil } -// getAccountsToShare is used to get the wallet accounts to share either when requesting to join a community or when editing -// requestToJoinID can be empty when editing -func (m *Messenger) getAccountsToShare(addressesToReveal []string, airdropAddress string, communityID types.HexBytes, password string, requestToJoinID []byte) (map[gethcommon.Address]*protobuf.RevealedAccount, []gethcommon.Address, error) { +// Generates a single hash for each address that needs to be revealed to a community. +// Each hash needs to be signed. +// The order of retuned hashes corresponds to the order of addresses in addressesToReveal. +func (m *Messenger) generateCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string, isEdit bool) ([]account.SignParams, error) { walletAccounts, err := m.settings.GetActiveAccounts() if err != nil { - return nil, nil, err + return nil, err } - revealedAccounts := make(map[gethcommon.Address]*protobuf.RevealedAccount) - revealedAddresses := make([]gethcommon.Address, 0) - containsAddress := func(addresses []string, targetAddress string) bool { for _, address := range addresses { if types.HexToAddress(address) == types.HexToAddress(targetAddress) { @@ -1002,7 +1000,8 @@ func (m *Messenger) getAccountsToShare(addressesToReveal []string, airdropAddres return false } - for i, walletAccount := range walletAccounts { + msgsToSign := make([]account.SignParams, 0) + for _, walletAccount := range walletAccounts { if walletAccount.Chat || walletAccount.Type == accounts.AccountTypeWatch { continue } @@ -1011,65 +1010,114 @@ func (m *Messenger) getAccountsToShare(addressesToReveal []string, airdropAddres continue } - verifiedAccount, err := m.accountsManager.GetVerifiedWalletAccount(m.settings, walletAccount.Address.Hex(), password) - if err != nil { - return nil, nil, err - } - - messageToSign := types.EncodeHex(crypto.Keccak256(m.IdentityPublicKeyCompressed(), communityID, requestToJoinID)) - signParams := account.SignParams{ - Data: messageToSign, - Address: verifiedAccount.Address.Hex(), - Password: password, - } - signatureBytes, err := m.accountsManager.Sign(signParams, verifiedAccount) - if err != nil { - return nil, nil, err - } - - revealedAddress := gethcommon.HexToAddress(verifiedAccount.Address.Hex()) - revealedAddresses = append(revealedAddresses, revealedAddress) - address := verifiedAccount.Address.Hex() - isAirdropAddress := types.HexToAddress(address) == types.HexToAddress(airdropAddress) - if airdropAddress == "" { - isAirdropAddress = i == 0 - } - revealedAccounts[revealedAddress] = &protobuf.RevealedAccount{ - Address: address, - IsAirdropAddress: isAirdropAddress, - Signature: signatureBytes, - ChainIds: make([]uint64, 0), + requestID := []byte{} + if !isEdit { + requestID = communities.CalculateRequestID(memberPubKey, communityID) } + msgsToSign = append(msgsToSign, account.SignParams{ + Data: types.EncodeHex(crypto.Keccak256(m.IdentityPublicKeyCompressed(), communityID, requestID)), + Address: walletAccount.Address.Hex(), + }) } - return revealedAccounts, revealedAddresses, nil + + return msgsToSign, nil +} + +func (m *Messenger) GenerateJoiningCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) { + if len(communityID) == 0 { + return nil, errors.New("communityID has to be provided") + } + return m.generateCommunityRequestsForSigning(memberPubKey, communityID, addressesToReveal, false) +} + +func (m *Messenger) GenerateEditCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) { + return m.generateCommunityRequestsForSigning(memberPubKey, communityID, addressesToReveal, true) +} + +// Signs the provided messages with the provided accounts and password. +// Provided accounts must not belong to a keypair that is migrated to a keycard. +// Otherwise, the signing will fail, cause such accounts should be signed with a keycard. +func (m *Messenger) SignData(signParams []account.SignParams) ([]string, error) { + signatures := make([]string, len(signParams)) + for i, param := range signParams { + if err := param.Validate(true); err != nil { + return nil, err + } + + account, err := m.settings.GetAccountByAddress(types.HexToAddress(param.Address)) + if err != nil { + return nil, err + } + + if account.Chat || account.Type == accounts.AccountTypeWatch { + return nil, errors.New("cannot join a community using profile chat or watch-only account") + } + + keypair, err := m.settings.GetKeypairByKeyUID(account.KeyUID) + if err != nil { + return nil, err + } + + if keypair.MigratedToKeycard() { + return nil, errors.New("signing a joining community request for accounts migrated to keycard must be done with a keycard") + } + + verifiedAccount, err := m.accountsManager.GetVerifiedWalletAccount(m.settings, param.Address, param.Password) + if err != nil { + return nil, err + } + + signature, err := m.accountsManager.Sign(param, verifiedAccount) + if err != nil { + return nil, err + } + + signatures[i] = types.EncodeHex(signature) + } + + return signatures, nil } func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommunity) (*MessengerResponse, error) { + // TODO: Because of changes that need to be done in tests, calling this function and providing `request` without `AddressesToReveal` + // is not an error, but it should be. logger := m.logger.Named("RequestToJoinCommunity") - if err := request.Validate(); err != nil { + if err := request.Validate(len(request.AddressesToReveal) > 0); err != nil { logger.Debug("request failed to validate", zap.Error(err), zap.Any("request", request)) return nil, err } - if request.Password != "" { - walletAccounts, err := m.settings.GetActiveAccounts() + requestToJoin := m.communitiesManager.CreateRequestToJoin(request) + + if len(request.AddressesToReveal) > 0 { + revealedAddresses := make([]gethcommon.Address, 0) + for _, addr := range request.AddressesToReveal { + revealedAddresses = append(revealedAddresses, gethcommon.HexToAddress(addr)) + } + + permissions, err := m.communitiesManager.CheckPermissionToJoin(request.CommunityID, revealedAddresses) if err != nil { return nil, err } - if len(walletAccounts) > 0 { - _, err := m.accountsManager.GetVerifiedWalletAccount(m.settings, walletAccounts[0].Address.Hex(), request.Password) - if err != nil { - return nil, err + if !permissions.Satisfied { + return nil, errors.New("permission to join not satisfied") + } + + for _, accountAndChainIDs := range permissions.ValidCombinations { + for i := range requestToJoin.RevealedAccounts { + if gethcommon.HexToAddress(requestToJoin.RevealedAccounts[i].Address) == accountAndChainIDs.Address { + requestToJoin.RevealedAccounts[i].ChainIds = accountAndChainIDs.ChainIDs + } } } } - displayName, err := m.settings.DisplayName() + community, err := m.communitiesManager.CheckCommunityForJoining(request.CommunityID) if err != nil { return nil, err } - community, requestToJoin, err := m.communitiesManager.CreateRequestToJoin(&m.identity.PublicKey, request) + displayName, err := m.settings.DisplayName() if err != nil { return nil, err } @@ -1078,40 +1126,8 @@ func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommun Clock: requestToJoin.Clock, EnsName: requestToJoin.ENSName, DisplayName: displayName, - CommunityId: community.ID(), - RevealedAccounts: make([]*protobuf.RevealedAccount, 0), - } - - // find wallet accounts and attach wallet addresses and - // signatures to request - if request.Password != "" { - revealedAccounts, revealedAddresses, err := m.getAccountsToShare( - request.AddressesToReveal, - request.AirdropAddress, - request.CommunityID, - request.Password, - requestToJoin.ID, - ) - if err != nil { - return nil, err - } - - response, err := m.communitiesManager.CheckPermissionToJoin(community.ID(), revealedAddresses) - if err != nil { - return nil, err - } - if !response.Satisfied { - return nil, errors.New("permission to join not satisfied") - } - - for _, accountAndChainIDs := range response.ValidCombinations { - revealedAccounts[accountAndChainIDs.Address].ChainIds = accountAndChainIDs.ChainIDs - } - - for _, revealedAccount := range revealedAccounts { - requestToJoinProto.RevealedAccounts = append(requestToJoinProto.RevealedAccounts, revealedAccount) - requestToJoin.RevealedAccounts = append(requestToJoinProto.RevealedAccounts, revealedAccount) - } + CommunityId: request.CommunityID, + RevealedAccounts: requestToJoin.RevealedAccounts, } community, _, err = m.communitiesManager.SaveRequestToJoinAndCommunity(requestToJoin, community) @@ -1223,31 +1239,26 @@ func (m *Messenger) EditSharedAddressesForCommunity(request *requests.EditShared logger.Debug("request failed to validate", zap.Error(err), zap.Any("request", request)) return nil, err } - // verify wallet password is there - if request.Password == "" { - return nil, errors.New("password is necessary to use this function") - } community, err := m.communitiesManager.GetByID(request.CommunityID) if err != nil { return nil, err } - walletAccounts, err := m.settings.GetActiveAccounts() - if err != nil { - return nil, err - } - if len(walletAccounts) > 0 { - _, err := m.accountsManager.GetVerifiedWalletAccount(m.settings, walletAccounts[0].Address.Hex(), request.Password) - if err != nil { - return nil, err - } - } - if !community.HasMember(m.IdentityPublicKey()) { return nil, errors.New("not part of the community") } + revealedAddresses := make([]gethcommon.Address, 0) + for _, addr := range request.AddressesToReveal { + revealedAddresses = append(revealedAddresses, gethcommon.HexToAddress(addr)) + } + + checkPermissionResponse, err := m.communitiesManager.CheckPermissionToJoin(community.ID(), revealedAddresses) + if err != nil { + return nil, err + } + member := community.GetMember(m.IdentityPublicKey()) requestToEditRevealedAccountsProto := &protobuf.CommunityEditSharedAddresses{ @@ -1255,30 +1266,22 @@ func (m *Messenger) EditSharedAddressesForCommunity(request *requests.EditShared CommunityId: community.ID(), RevealedAccounts: make([]*protobuf.RevealedAccount, 0), } - // find wallet accounts and attach wallet addresses and - // signatures to request - revealedAccounts, revealedAddresses, err := m.getAccountsToShare( - request.AddressesToReveal, - request.AirdropAddress, - request.CommunityID, - request.Password, - []byte{}, - ) - if err != nil { - return nil, err - } - checkPermissionResponse, err := m.communitiesManager.CheckPermissionToJoin(community.ID(), revealedAddresses) - if err != nil { - return nil, err - } + for i := range request.AddressesToReveal { + revealedAcc := &protobuf.RevealedAccount{ + Address: request.AddressesToReveal[i], + IsAirdropAddress: types.HexToAddress(request.AddressesToReveal[i]) == types.HexToAddress(request.AirdropAddress), + Signature: request.Signatures[i], + } - for _, accountAndChainIDs := range checkPermissionResponse.ValidCombinations { - revealedAccounts[accountAndChainIDs.Address].ChainIds = accountAndChainIDs.ChainIDs - } + for _, accountAndChainIDs := range checkPermissionResponse.ValidCombinations { + if accountAndChainIDs.Address == gethcommon.HexToAddress(request.AddressesToReveal[i]) { + revealedAcc.ChainIds = accountAndChainIDs.ChainIDs + break + } + } - for _, revealedAccount := range revealedAccounts { - requestToEditRevealedAccountsProto.RevealedAccounts = append(requestToEditRevealedAccountsProto.RevealedAccounts, revealedAccount) + requestToEditRevealedAccountsProto.RevealedAccounts = append(requestToEditRevealedAccountsProto.RevealedAccounts, revealedAcc) } requestID := communities.CalculateRequestID(common.PubkeyToHex(&m.identity.PublicKey), request.CommunityID) diff --git a/protocol/messenger_delete_message_for_everyone_test.go b/protocol/messenger_delete_message_for_everyone_test.go index 32e78627e..33cd2606c 100644 --- a/protocol/messenger_delete_message_for_everyone_test.go +++ b/protocol/messenger_delete_message_for_everyone_test.go @@ -74,10 +74,10 @@ func (s *MessengerDeleteMessageForEveryoneSuite) TestDeleteMessageForEveryone() request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} advertiseCommunityTo(&s.Suite, community, s.admin, s.moderator) - joinCommunity(&s.Suite, community, s.admin, s.moderator, request) + joinCommunity(&s.Suite, community, s.admin, s.moderator, request, "") advertiseCommunityTo(&s.Suite, community, s.admin, s.bob) - joinCommunity(&s.Suite, community, s.admin, s.bob, request) + joinCommunity(&s.Suite, community, s.admin, s.bob, request, "") response, err := s.admin.AddRoleToMember(&requests.AddRoleToMember{ CommunityID: community.ID(), diff --git a/protocol/messenger_send_images_album_test.go b/protocol/messenger_send_images_album_test.go index 4b1608c27..8a777e1b4 100644 --- a/protocol/messenger_send_images_album_test.go +++ b/protocol/messenger_send_images_album_test.go @@ -69,7 +69,7 @@ func (s *MessengerSendImagesAlbumSuite) advertiseCommunityTo(community *communit func (s *MessengerSendImagesAlbumSuite) joinCommunity(community *communities.Community, user *Messenger) { request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} - joinCommunity(&s.Suite, community, s.m, user, request) + joinCommunity(&s.Suite, community, s.m, user, request, "") } func (s *MessengerSendImagesAlbumSuite) TestAlbumImageMessagesSend() { diff --git a/protocol/requests/edit_shared_addresses.go b/protocol/requests/edit_shared_addresses.go index 06ba9cf8b..861c397ae 100644 --- a/protocol/requests/edit_shared_addresses.go +++ b/protocol/requests/edit_shared_addresses.go @@ -3,6 +3,7 @@ package requests import ( "errors" + "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" ) @@ -10,27 +11,46 @@ var ErrInvalidCommunityID = errors.New("invalid community id") var ErrMissingPassword = errors.New("password is necessary when sending a list of addresses") var ErrMissingSharedAddresses = errors.New("list of shared addresses is needed") var ErrMissingAirdropAddress = errors.New("airdropAddress is needed") +var ErrNoAirdropAddressAmongAddressesToReveal = errors.New("airdropAddress must be in the set of addresses to reveal") +var ErrInvalidSignature = errors.New("invalid signature") type EditSharedAddresses struct { - CommunityID types.HexBytes `json:"communityId"` - Password string `json:"password"` - AddressesToReveal []string `json:"addressesToReveal"` - AirdropAddress string `json:"airdropAddress"` + CommunityID types.HexBytes `json:"communityId"` + AddressesToReveal []string `json:"addressesToReveal"` + Signatures []types.HexBytes `json:"signatures"` // the order of signatures should match the order of addresses + AirdropAddress string `json:"airdropAddress"` } func (j *EditSharedAddresses) Validate() error { if len(j.CommunityID) == 0 { return ErrInvalidCommunityID } - if j.Password == "" { - return ErrMissingPassword - } + if len(j.AddressesToReveal) == 0 { return ErrMissingSharedAddresses } + if j.AirdropAddress == "" { return ErrMissingAirdropAddress } + found := false + for _, address := range j.AddressesToReveal { + if address == j.AirdropAddress { + found = true + break + } + } + + if !found { + return ErrNoAirdropAddressAmongAddressesToReveal + } + + for _, signature := range j.Signatures { + if len(signature) > 0 && len(signature) != crypto.SignatureLength { + return ErrInvalidSignature + } + } + return nil } diff --git a/protocol/requests/request_to_join_community.go b/protocol/requests/request_to_join_community.go index 813c41780..ff3bcae96 100644 --- a/protocol/requests/request_to_join_community.go +++ b/protocol/requests/request_to_join_community.go @@ -3,31 +3,63 @@ package requests import ( "errors" + "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" ) var ErrRequestToJoinCommunityInvalidCommunityID = errors.New("request-to-join-community: invalid community id") +var ErrRequestToJoinCommunityNoAddressesToReveal = errors.New("request-to-join-community: no addresses to reveal") var ErrRequestToJoinCommunityMissingPassword = errors.New("request-to-join-community: password is necessary when sending a list of addresses") var ErrRequestToJoinNoAirdropAddress = errors.New("request-to-join-community: airdropAddress is necessary when sending a list of addresses") +var ErrRequestToJoinNoAirdropAddressAmongAddressesToReveal = errors.New("request-to-join-community: airdropAddress must be in the set of addresses to reveal") +var ErrRequestToJoinCommunityInvalidSignature = errors.New("request-to-join-community: invalid signature") type RequestToJoinCommunity struct { - CommunityID types.HexBytes `json:"communityId"` - ENSName string `json:"ensName"` - Password string `json:"password"` - AddressesToReveal []string `json:"addressesToReveal"` - AirdropAddress string `json:"airdropAddress"` + CommunityID types.HexBytes `json:"communityId"` + ENSName string `json:"ensName"` + AddressesToReveal []string `json:"addressesToReveal"` + Signatures []types.HexBytes `json:"signatures"` // the order of signatures should match the order of addresses + AirdropAddress string `json:"airdropAddress"` } -func (j *RequestToJoinCommunity) Validate() error { +func (j *RequestToJoinCommunity) Validate(full bool) error { + // TODO: A parital validation, in case `full` is set to `false` should check `AddressesToReveal` as well. But because of changes + // that need to be done in tests we cannot do that now. + // Also in the line 61, we should remove `len(signature) > 0` from the condition, but from the same reason we cannot do that now. + if len(j.CommunityID) == 0 { return ErrRequestToJoinCommunityInvalidCommunityID } - if len(j.AddressesToReveal) > 0 && j.Password == "" { - return ErrRequestToJoinCommunityMissingPassword + + if !full { + return nil } - if len(j.AddressesToReveal) > 0 && j.AirdropAddress == "" { + + if len(j.AddressesToReveal) == 0 { + return ErrRequestToJoinCommunityNoAddressesToReveal + } + + if j.AirdropAddress == "" { return ErrRequestToJoinNoAirdropAddress } + found := false + for _, address := range j.AddressesToReveal { + if address == j.AirdropAddress { + found = true + break + } + } + + if !found { + return ErrRequestToJoinNoAirdropAddressAmongAddressesToReveal + } + + for _, signature := range j.Signatures { + if len(signature) > 0 && len(signature) != crypto.SignatureLength { + return ErrRequestToJoinCommunityInvalidSignature + } + } + return nil } diff --git a/services/ext/api.go b/services/ext/api.go index 41ba0e22c..a637971a9 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -9,6 +9,7 @@ import ( "math/big" "time" + "github.com/status-im/status-go/account" "github.com/status-im/status-go/services/browsers" "github.com/status-im/status-go/services/wallet" "github.com/status-im/status-go/services/wallet/bigint" @@ -586,6 +587,27 @@ func (api *PublicAPI) CanceledRequestsToJoinForCommunity(id types.HexBytes) ([]* return api.service.messenger.CanceledRequestsToJoinForCommunity(id) } +// Generates a single hash for each address that needs to be revealed to a community. +// Each hash needs to be signed. +// The order of retuned hashes corresponds to the order of addresses in addressesToReveal. +func (api *PublicAPI) GenerateJoiningCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) { + return api.service.messenger.GenerateJoiningCommunityRequestsForSigning(memberPubKey, communityID, addressesToReveal) +} + +// Generates a single hash for each address that needs to be revealed to a community. +// Each hash needs to be signed. +// The order of retuned hashes corresponds to the order of addresses in addressesToReveal. +func (api *PublicAPI) GenerateEditCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) { + return api.service.messenger.GenerateEditCommunityRequestsForSigning(memberPubKey, communityID, addressesToReveal) +} + +// Signs the provided messages with the provided accounts and password. +// Provided accounts must not belong to a keypair that is migrated to a keycard. +// Otherwise, the signing will fail, cause such accounts should be signed with a keycard. +func (api *PublicAPI) SignData(signParams []account.SignParams) ([]string, error) { + return api.service.messenger.SignData(signParams) +} + // CancelRequestToJoinCommunity accepts a pending request to join a community func (api *PublicAPI) CancelRequestToJoinCommunity(ctx context.Context, request *requests.CancelRequestToJoinCommunity) (*protocol.MessengerResponse, error) { return api.service.messenger.CancelRequestToJoinCommunity(ctx, request)