From e031d136a12f3a00efd6a4fb306400b7cf5efbb7 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:08:38 -0700 Subject: [PATCH] Include and process destinationPniIdentityKey --- package.json | 2 +- protos/SignalService.proto | 6 +- ts/SignalProtocolStore.ts | 11 +- ts/background.ts | 88 ++++++++---- ts/models/conversations.ts | 6 +- ts/services/profiles.ts | 26 ++-- ts/test-mock/messaging/sendSync_test.ts | 94 ------------- ts/test-mock/pnp/pni_signature_test.ts | 179 +++++++++++++++--------- ts/textsecure/SendMessage.ts | 28 +++- ts/util/verifyStoryListMembers.ts | 4 +- yarn.lock | 8 +- 11 files changed, 238 insertions(+), 214 deletions(-) delete mode 100644 ts/test-mock/messaging/sendSync_test.ts diff --git a/package.json b/package.json index 3ac8421b2..024d2444b 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "@formatjs/intl": "2.6.7", "@indutny/rezip-electron": "1.3.1", "@mixer/parallel-prettier": "2.0.3", - "@signalapp/mock-server": "6.0.0", + "@signalapp/mock-server": "6.1.0", "@storybook/addon-a11y": "7.4.5", "@storybook/addon-actions": "7.4.5", "@storybook/addon-controls": "7.4.5", diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 3859b3ecd..503d19203 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -450,9 +450,11 @@ message Verified { message SyncMessage { message Sent { message UnidentifiedDeliveryStatus { - optional string destination = 1; + optional string destination = 1; optional string destinationServiceId = 3; - optional bool unidentified = 2; + optional bool unidentified = 2; + reserved /* destinationPni */ 4; + optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations } message StoryMessageRecipient { diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index 5fff48642..321b3cec3 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -136,6 +136,11 @@ export type SessionTransactionOptions = Readonly<{ zone?: Zone; }>; +export type SaveIdentityOptions = Readonly<{ + zone?: Zone; + noOverwrite?: boolean; +}>; + export type VerifyAlternateIdentityOptionsType = Readonly<{ aci: AciString; pni: PniString; @@ -2049,7 +2054,7 @@ export class SignalProtocolStore extends EventEmitter { encodedAddress: Address, publicKey: Uint8Array, nonblockingApproval = false, - { zone = GLOBAL_ZONE }: SessionTransactionOptions = {} + { zone = GLOBAL_ZONE, noOverwrite = false }: SaveIdentityOptions = {} ): Promise { if (!this.identityKeys) { throw new Error('saveIdentity: this.identityKeys not yet cached!'); @@ -2100,6 +2105,10 @@ export class SignalProtocolStore extends EventEmitter { return false; } + if (noOverwrite) { + return false; + } + const identityKeyChanged = !constantTimeEqual( identityRecord.publicKey, publicKey diff --git a/ts/background.ts b/ts/background.ts index 7e78cfa38..8f02d1352 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -5,6 +5,7 @@ import { isNumber, throttle, groupBy } from 'lodash'; import { render } from 'react-dom'; import { batch as batchDispatch } from 'react-redux'; import PQueue from 'p-queue'; +import pMap from 'p-map'; import { v4 as generateUuid } from 'uuid'; import * as Registration from './util/registration'; @@ -52,6 +53,7 @@ import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher'; import * as KeyboardLayout from './services/keyboardLayout'; import * as StorageService from './services/storage'; import { usernameIntegrity } from './services/usernameIntegrity'; +import { updateIdentityKey } from './services/profiles'; import { RoutineProfileRefresher } from './routineProfileRefresh'; import { isOlderThan } from './util/timestamp'; import { isValidReactionEmoji } from './reactions/isValidReactionEmoji'; @@ -2536,46 +2538,74 @@ export async function startApp(): Promise { return confirm(); } - function createSentMessage( + async function createSentMessage( data: SentEventData, descriptor: MessageDescriptor ) { const now = Date.now(); const timestamp = data.timestamp || now; + const logId = `createSentMessage(${timestamp})`; const ourId = window.ConversationController.getOurConversationIdOrThrow(); const { unidentifiedStatus = [] } = data; - const sendStateByConversationId: SendStateByConversationId = - unidentifiedStatus.reduce( - ( - result: SendStateByConversationId, - { destinationServiceId, destination, isAllowedToReplyToStory } - ) => { - const conversation = window.ConversationController.get( - destinationServiceId || destination - ); - if (!conversation || conversation.id === ourId) { - return result; - } + const sendStateByConversationId: SendStateByConversationId = { + [ourId]: { + status: SendStatus.Sent, + updatedAt: timestamp, + }, + }; - return { - ...result, - [conversation.id]: { - isAllowedToReplyToStory, - status: SendStatus.Sent, - updatedAt: timestamp, - }, - }; - }, - { - [ourId]: { - status: SendStatus.Sent, - updatedAt: timestamp, - }, - } + for (const { + destinationServiceId, + destination, + isAllowedToReplyToStory, + } of unidentifiedStatus) { + const conversation = window.ConversationController.get( + destinationServiceId || destination ); + if (!conversation || conversation.id === ourId) { + continue; + } + + sendStateByConversationId[conversation.id] = { + isAllowedToReplyToStory, + status: SendStatus.Sent, + updatedAt: timestamp, + }; + } + + await pMap( + unidentifiedStatus, + async ({ destinationServiceId, destinationPniIdentityKey }) => { + if (!Bytes.isNotEmpty(destinationPniIdentityKey)) { + return; + } + + if (!isPniString(destinationServiceId)) { + log.warn( + `${logId}: received an destinationPniIdentityKey for ` + + `an invalid PNI: ${destinationServiceId}` + ); + return; + } + + const changed = await updateIdentityKey( + destinationPniIdentityKey, + destinationServiceId, + { + noOverwrite: true, + } + ); + if (changed) { + log.info( + `${logId}: Updated identity key for ${destinationServiceId}` + ); + } + }, + { concurrency: 10 } + ); let unidentifiedDeliveries: Array = []; if (unidentifiedStatus.length) { @@ -2720,7 +2750,7 @@ export async function startApp(): Promise { }); } - const message = createSentMessage(data, messageDescriptor); + const message = await createSentMessage(data, messageDescriptor); if (data.message.reaction) { strictAssert( diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 63cfd2858..79fe1fa1b 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -2027,9 +2027,12 @@ export class ConversationModel extends window.Backbone incrementSentMessageCount({ dry = false }: { dry?: boolean } = {}): | Partial | undefined { + const needsTitleTransition = + hasNumberTitle(this.attributes) || hasUsernameTitle(this.attributes); const update = { messageCount: (this.get('messageCount') || 0) + 1, sentMessageCount: (this.get('sentMessageCount') || 0) + 1, + ...(needsTitleTransition ? { needsTitleTransition: true } : {}), }; if (dry) { @@ -3719,13 +3722,10 @@ export class ConversationModel extends window.Backbone }; const isEditMessage = Boolean(message.get('editHistory')); - const needsTitleTransition = - hasNumberTitle(this.attributes) || hasUsernameTitle(this.attributes); this.set({ ...draftProperties, ...(enabledProfileSharing ? { profileSharing: true } : {}), - ...(needsTitleTransition ? { needsTitleTransition: true } : {}), ...(dontAddMessage ? {} : this.incrementSentMessageCount({ dry: true })), diff --git a/ts/services/profiles.ts b/ts/services/profiles.ts index fcf7f7047..4512658cd 100644 --- a/ts/services/profiles.ts +++ b/ts/services/profiles.ts @@ -354,7 +354,8 @@ async function doGetProfile(c: ConversationModel): Promise { } if (profile.identityKey) { - await updateIdentityKey(profile.identityKey, serviceId); + const identityKeyBytes = Bytes.fromBase64(profile.identityKey); + await updateIdentityKey(identityKeyBytes, serviceId); } // Update accessKey to prevent race conditions. Since we run asynchronous @@ -596,19 +597,24 @@ async function doGetProfile(c: ConversationModel): Promise { window.Signal.Data.updateConversation(c.attributes); } +export type UpdateIdentityKeyOptionsType = Readonly<{ + noOverwrite?: boolean; +}>; + export async function updateIdentityKey( - identityKey: string, - serviceId: ServiceIdString -): Promise { - if (!identityKey) { - return; + identityKey: Uint8Array, + serviceId: ServiceIdString, + { noOverwrite = false }: UpdateIdentityKeyOptionsType = {} +): Promise { + if (!Bytes.isNotEmpty(identityKey)) { + return false; } - const identityKeyBytes = Bytes.fromBase64(identityKey); const changed = await window.textsecure.storage.protocol.saveIdentity( new Address(serviceId, 1), - identityKeyBytes, - false + identityKey, + false, + { noOverwrite } ); if (changed) { log.info(`updateIdentityKey(${serviceId}): changed`); @@ -619,4 +625,6 @@ export async function updateIdentityKey( new QualifiedAddress(ourAci, new Address(serviceId, 1)) ); } + + return changed; } diff --git a/ts/test-mock/messaging/sendSync_test.ts b/ts/test-mock/messaging/sendSync_test.ts deleted file mode 100644 index 059d21901..000000000 --- a/ts/test-mock/messaging/sendSync_test.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { assert } from 'chai'; -import createDebug from 'debug'; -import Long from 'long'; - -import type { App } from '../playwright'; -import * as durations from '../../util/durations'; -import { Bootstrap } from '../bootstrap'; - -export const debug = createDebug('mock:test:sendSync'); - -describe('sendSync', function (this: Mocha.Suite) { - this.timeout(durations.MINUTE); - - let bootstrap: Bootstrap; - let app: App; - - beforeEach(async () => { - bootstrap = new Bootstrap(); - await bootstrap.init(); - app = await bootstrap.link(); - }); - - afterEach(async function (this: Mocha.Context) { - if (!bootstrap) { - return; - } - - await bootstrap.maybeSaveLogs(this.currentTest, app); - await app.close(); - await bootstrap.teardown(); - }); - - it('creates conversation for sendSync to PNI', async () => { - const { desktop, phone, server } = bootstrap; - - debug('Creating stranger'); - const STRANGER_NAME = 'Mysterious Stranger'; - const stranger = await server.createPrimaryDevice({ - profileName: STRANGER_NAME, - }); - - const timestamp = Date.now(); - const messageText = 'hey there, just reaching out'; - const destinationServiceId = stranger.device.pni; - const destination = stranger.device.number; - const originalDataMessage = { - body: messageText, - timestamp: Long.fromNumber(timestamp), - }; - const content = { - syncMessage: { - sent: { - destinationServiceId, - destination, - timestamp: Long.fromNumber(timestamp), - message: originalDataMessage, - }, - }, - }; - const sendOptions = { - timestamp, - }; - await phone.sendRaw(desktop, content, sendOptions); - - const page = await app.getWindow(); - const leftPane = page.locator('#LeftPane'); - - debug('checking left pane for conversation'); - const strangerName = await leftPane - .locator( - '.module-conversation-list__item--contact-or-conversation .module-contact-name' - ) - .first() - .innerText(); - - assert.equal( - strangerName.slice(-4), - destination?.slice(-4), - 'no profile, just phone number' - ); - - debug('opening conversation'); - await leftPane - .locator('.module-conversation-list__item--contact-or-conversation') - .first() - .click(); - - debug('checking for latest message'); - await page.locator(`.module-message__text >> "${messageText}"`).waitFor(); - }); -}); diff --git a/ts/test-mock/pnp/pni_signature_test.ts b/ts/test-mock/pnp/pni_signature_test.ts index b746f802e..554c3f701 100644 --- a/ts/test-mock/pnp/pni_signature_test.ts +++ b/ts/test-mock/pnp/pni_signature_test.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; +import Long from 'long'; import { Pni } from '@signalapp/libsignal-client'; import { ServiceIdKind, @@ -9,7 +10,6 @@ import { ReceiptType, StorageState, } from '@signalapp/mock-server'; -import type { PrimaryDevice } from '@signalapp/mock-server'; import createDebug from 'debug'; import * as durations from '../../util/durations'; @@ -33,17 +33,12 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { let bootstrap: Bootstrap; let app: App; - let pniContact: PrimaryDevice; beforeEach(async () => { - bootstrap = new Bootstrap(); + bootstrap = new Bootstrap({ contactCount: 0 }); await bootstrap.init(); - const { server, phone } = bootstrap; - - pniContact = await server.createPrimaryDevice({ - profileName: 'ACI Contact', - }); + const { phone } = bootstrap; let state = StorageState.getEmpty(); @@ -52,21 +47,6 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { e164: phone.device.number, }); - state = state.addContact( - pniContact, - { - whitelisted: true, - serviceE164: pniContact.device.number, - identityKey: pniContact.getPublicKey(ServiceIdKind.PNI).serialize(), - givenName: undefined, - familyName: undefined, - }, - ServiceIdKind.PNI - ); - - // Just to make PNI Contact visible in the left pane - state = state.pin(pniContact, ServiceIdKind.PNI); - // Add my story state = state.addRecord({ type: IdentifierType.STORY_DISTRIBUTION_LIST, @@ -140,14 +120,14 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { assert.isTrue(isValid, `Invalid pni signature from ${source}`); }; - debug('sending a message to our PNI'); + debug('Send a message to our PNI'); await stranger.sendText(desktop, 'A message to PNI', { serviceIdKind: ServiceIdKind.PNI, withProfileKey: true, timestamp: bootstrap.getTimestamp(), }); - debug('opening conversation with the stranger'); + debug('Open conversation with the stranger'); await leftPane .locator(`[data-testid="${stranger.toContact().aci}"]`) .click(); @@ -157,7 +137,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { .locator('.module-message-request-actions button >> "Accept"') .click(); - debug('Waiting for a pniSignatureMessage'); + debug('Wait for a pniSignatureMessage'); { const { source, content } = await stranger.waitForMessage(); @@ -171,7 +151,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { await compositionInput.type('first'); await compositionInput.press('Enter'); } - debug('Waiting for the first message with pni signature'); + debug('Wait for the first message with pni signature'); { const { source, content, body, dataMessage } = await stranger.waitForMessage(); @@ -185,7 +165,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { checkPniSignature(content.pniSignatureMessage, 'first message'); const receiptTimestamp = bootstrap.getTimestamp(); - debug('Sending unencrypted receipt', receiptTimestamp); + debug('Send unencrypted receipt', receiptTimestamp); await stranger.sendUnencryptedReceipt(desktop, { messageTimestamp: dataMessage.timestamp?.toNumber() ?? 0, @@ -199,7 +179,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { await compositionInput.type('second'); await compositionInput.press('Enter'); } - debug('Waiting for the second message with pni signature'); + debug('Wait for the second message with pni signature'); { const { source, content, body, dataMessage } = await stranger.waitForMessage(); @@ -213,7 +193,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { checkPniSignature(content.pniSignatureMessage, 'second message'); const receiptTimestamp = bootstrap.getTimestamp(); - debug('Sending encrypted receipt', receiptTimestamp); + debug('Send encrypted receipt', receiptTimestamp); await stranger.sendReceipt(desktop, { type: ReceiptType.Delivery, @@ -233,7 +213,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { await compositionInput.type('third'); await compositionInput.press('Enter'); } - debug('Waiting for the third message without pni signature'); + debug('Wait for the third message without pni signature'); { const { source, content, body } = await stranger.waitForMessage(); @@ -262,55 +242,126 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { }); it('should be received by Desktop and trigger contact merge', async () => { - const { desktop, phone } = bootstrap; + const { desktop, phone, server } = bootstrap; const window = await app.getWindow(); const leftPane = window.locator('#LeftPane'); - debug('opening conversation with the pni contact'); - await leftPane - .locator('.module-conversation-list__item--contact-or-conversation') - .first() - .click(); + debug('Capture storage service state before messaging'); + let state = await phone.expectStorageState('state before messaging'); - debug('Enter a PNI message text'); + debug('Create stranger'); + const STRANGER_NAME = 'Mysterious Stranger'; + const stranger = await server.createPrimaryDevice({ + profileName: STRANGER_NAME, + }); + + debug('Send a PNI sync message'); + const timestamp = bootstrap.getTimestamp(); + const destinationServiceId = stranger.device.pni; + const destination = stranger.device.number; + const destinationPniIdentityKey = await stranger.device.getIdentityKey( + ServiceIdKind.PNI + ); + const originalDataMessage = { + body: 'Hello PNI', + timestamp: Long.fromNumber(timestamp), + }; + const content = { + syncMessage: { + sent: { + destinationServiceId, + destination, + timestamp: Long.fromNumber(timestamp), + message: originalDataMessage, + unidentifiedStatus: [ + { + destinationServiceId, + destination, + destinationPniIdentityKey: destinationPniIdentityKey.serialize(), + }, + ], + }, + }, + }; + const sendOptions = { + timestamp, + }; + await phone.sendRaw(desktop, content, sendOptions); + + debug('Wait for updated storage service state with PNI contact'); { - const compositionInput = await app.waitForEnabledComposer(); + const newState = await phone.waitForStorageState({ + after: state, + }); - await compositionInput.type('Hello PNI'); - await compositionInput.press('Enter'); + const aciRecord = newState.getContact(stranger, ServiceIdKind.ACI); + assert.isUndefined(aciRecord, 'ACI contact must not be created'); + + const pniRecord = newState.getContact(stranger, ServiceIdKind.PNI); + assert.deepEqual( + pniRecord?.identityKey, + destinationPniIdentityKey.serialize(), + 'PNI contact must have correct identity key' + ); + + state = newState; } - debug('Waiting for a PNI message'); - { - const { source, body, serviceIdKind } = await pniContact.waitForMessage(); + debug('Open conversation with the pni contact'); + const contactElem = leftPane.locator( + `[data-testid="${stranger.device.pni}"]` + ); + await contactElem.click(); - assert.strictEqual(source, desktop, 'PNI message has valid source'); - assert.strictEqual(body, 'Hello PNI', 'PNI message has valid body'); - assert.strictEqual( - serviceIdKind, - ServiceIdKind.PNI, - 'PNI message has valid destination' + debug('Verify that left pane shows phone number'); + { + const strangerName = await contactElem + .locator('.module-contact-name') + .first() + .innerText(); + assert.equal( + strangerName.slice(-4), + destination?.slice(-4), + 'no profile, just phone number' ); } - debug('Capture storage service state before merging'); - const state = await phone.expectStorageState('state before merge'); + debug('Verify that we are in MR state'); + const conversationStack = window.locator('.Inbox__conversation-stack'); + await conversationStack + .locator('.module-message-request-actions button >> "Continue"') + .waitFor(); - debug('Enter a draft text without hitting enter'); + debug('Clear message request state on phone'); { - const compositionInput = await app.waitForEnabledComposer(); + const newState = state.updateContact( + stranger, + { + whitelisted: true, + }, + ServiceIdKind.PNI + ); - await compositionInput.type('Draft text'); + await phone.setStorageState(newState, state); + await phone.sendFetchStorage({ + timestamp: bootstrap.getTimestamp(), + }); + state = newState; } + debug('Wait for MR state to disappear'); + await conversationStack + .locator('.module-message-request-actions button >> "Continue"') + .waitFor({ state: 'hidden' }); + debug('Send back the response with profile key and pni signature'); const ourKey = await desktop.popSingleUseKey(); - await pniContact.addSingleUseKey(desktop, ourKey); + await stranger.addSingleUseKey(desktop, ourKey); - await pniContact.sendText(desktop, 'Hello Desktop!', { + await stranger.sendText(desktop, 'Hello Desktop!', { timestamp: bootstrap.getTimestamp(), withPniSignature: true, withProfileKey: true, @@ -318,7 +369,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { debug('Wait for merge to happen'); await leftPane - .locator(`[data-testid="${pniContact.toContact().aci}"]`) + .locator(`[data-testid="${stranger.toContact().aci}"]`) .waitFor(); { @@ -330,9 +381,9 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { await compositionInput.press('Enter'); } - debug('Waiting for a ACI message'); + debug('Wait for a ACI message'); { - const { source, body, serviceIdKind } = await pniContact.waitForMessage(); + const { source, body, serviceIdKind } = await stranger.waitForMessage(); assert.strictEqual(source, desktop, 'ACI message has valid source'); assert.strictEqual(body, 'Hello ACI', 'ACI message has valid body'); @@ -350,8 +401,8 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { after: state, }); - const pniRecord = newState.getContact(pniContact, ServiceIdKind.PNI); - const aciRecord = newState.getContact(pniContact, ServiceIdKind.ACI); + const pniRecord = newState.getContact(stranger, ServiceIdKind.PNI); + const aciRecord = newState.getContact(stranger, ServiceIdKind.ACI); assert.strictEqual( aciRecord, pniRecord, @@ -359,12 +410,12 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { ); assert(aciRecord, 'ACI Contact must be in storage service'); - assert.strictEqual(aciRecord?.aci, pniContact.device.aci); + assert.strictEqual(aciRecord?.aci, stranger.device.aci); assert.strictEqual( aciRecord?.pni && isUntaggedPniString(aciRecord?.pni) && toTaggedPni(aciRecord?.pni), - pniContact.device.pni + stranger.device.pni ); assert.strictEqual(aciRecord?.pniSignatureVerified, true); diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 93a104fe4..eb864a970 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -7,6 +7,7 @@ import { z } from 'zod'; import Long from 'long'; import PQueue from 'p-queue'; +import pMap from 'p-map'; import type { PlaintextContent } from '@signalapp/libsignal-client'; import { Pni, @@ -26,7 +27,11 @@ import type { UploadedAttachmentType, } from '../types/Attachment'; import type { AciString, ServiceIdString } from '../types/ServiceId'; -import { ServiceIdKind, serviceIdSchema } from '../types/ServiceId'; +import { + ServiceIdKind, + serviceIdSchema, + isPniString, +} from '../types/ServiceId'; import type { ChallengeType, GetGroupLogOptionsType, @@ -63,7 +68,7 @@ import type { LinkPreviewImage, LinkPreviewMetadata, } from '../linkPreviews/linkPreviewFetch'; -import { concat, isEmpty, map } from '../util/iterables'; +import { concat, isEmpty } from '../util/iterables'; import type { SendTypesType } from '../util/handleMessageSend'; import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend'; import type { DurationInSeconds } from '../util/durations'; @@ -1267,8 +1272,9 @@ export default class MessageSender { // Though this field has 'unidentified' in the name, it should have entries for each // number we sent to. if (!isEmpty(conversationIdsSentTo)) { - sentMessage.unidentifiedStatus = [ - ...map(conversationIdsSentTo, conversationId => { + sentMessage.unidentifiedStatus = await pMap( + conversationIdsSentTo, + async conversationId => { const status = new Proto.SyncMessage.Sent.UnidentifiedDeliveryStatus(); const conv = window.ConversationController.get(conversationId); @@ -1281,12 +1287,22 @@ export default class MessageSender { if (serviceId) { status.destinationServiceId = serviceId; } + if (isPniString(serviceId)) { + const pniIdentityKey = + await window.textsecure.storage.protocol.loadIdentityKey( + serviceId + ); + if (pniIdentityKey) { + status.destinationPniIdentityKey = pniIdentityKey; + } + } } status.unidentified = conversationIdsWithSealedSender.has(conversationId); return status; - }), - ]; + }, + { concurrency: 10 } + ); } const syncMessage = MessageSender.createSyncMessage(); diff --git a/ts/util/verifyStoryListMembers.ts b/ts/util/verifyStoryListMembers.ts index 98220b38a..2a64e8e74 100644 --- a/ts/util/verifyStoryListMembers.ts +++ b/ts/util/verifyStoryListMembers.ts @@ -5,6 +5,7 @@ import * as log from '../logging/log'; import { isNotNil } from './isNotNil'; import { updateIdentityKey } from '../services/profiles'; import type { ServiceIdString } from '../types/ServiceId'; +import * as Bytes from '../Bytes'; export async function verifyStoryListMembers( serviceIds: Array @@ -49,7 +50,8 @@ export async function verifyStoryListMembers( verifiedServiceIds.delete(serviceId); if (identityKey) { - await updateIdentityKey(identityKey, serviceId); + const identityKeyBytes = Bytes.fromBase64(identityKey); + await updateIdentityKey(identityKeyBytes, serviceId); } else { await window.ConversationController.get(serviceId)?.getProfiles(); } diff --git a/yarn.lock b/yarn.lock index 79abf4d0a..ebaed4d70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4001,10 +4001,10 @@ type-fest "^3.5.0" uuid "^8.3.0" -"@signalapp/mock-server@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-6.0.0.tgz#a67e18b5cb928749c379c219c775a412ad5c181b" - integrity sha512-hzKqCQ8A0xSScn9bztwZnjdizI15wTuEjj/uwmzWylzsPxbcXkOOL+db9O0uTOKbDIl6nJFrsUFqQ8R6LC8TAg== +"@signalapp/mock-server@6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-6.1.0.tgz#5cb26ed475ca74c74800d9abff4c0c7954c59f54" + integrity sha512-SBfN61aRqhtH7hHsdVl3waS1HFI5RCBcQJJ3o5yHXTx1xnXvp1VCjSQ85Vlg57+0IihBPZqqYxyEuNN+5Fnp8g== dependencies: "@signalapp/libsignal-client" "^0.39.2" debug "^4.3.2"