+ for (var attempts = 0; attempts < 5; attempts++) {
+ try {
+ finishChangeNumberInternal(newNumber, verificationCode, pin);
+ break;
+ } catch (MismatchedDevicesException e) {
+ logger.debug("Change number failed with mismatched devices, retrying.");
+ try {
+ dependencies.getMessageSender().handleChangeNumberMismatchDevices(e.getMismatchedDevices());
+ } catch (UntrustedIdentityException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+ }
+ }
+
+ private void finishChangeNumberInternal(
+ String newNumber, String verificationCode, String pin
+ ) throws IncorrectPinException, PinLockedException, IOException {
+ final var pniIdentity = KeyUtils.generateIdentityKeyPair();
+ final var encryptedDeviceMessages = new ArrayList<OutgoingPushMessage>();
+ final var devicePniSignedPreKeys = new HashMap<Integer, SignedPreKeyEntity>();
+ final var devicePniLastResortKyberPreKeys = new HashMap<Integer, KyberPreKeyEntity>();
+ final var pniRegistrationIds = new HashMap<Integer, Integer>();
+
+ final var selfDeviceId = account.getDeviceId();
+ SyncMessage.PniChangeNumber selfChangeNumber = null;
+
+ final var deviceIds = new ArrayList<Integer>();
+ deviceIds.add(SignalServiceAddress.DEFAULT_DEVICE_ID);
+ final var aci = account.getAci();
+ final var accountDataStore = account.getSignalServiceDataStore().aci();
+ final var subDeviceSessions = accountDataStore.getSubDeviceSessions(aci.toString())
+ .stream()
+ .filter(deviceId -> accountDataStore.containsSession(new SignalProtocolAddress(aci.toString(),
+ deviceId)))
+ .toList();
+ deviceIds.addAll(subDeviceSessions);
+
+ final var messageSender = dependencies.getMessageSender();
+ for (final var deviceId : deviceIds) {
+ // Signed Prekey
+ final var signedPreKeyRecord = KeyUtils.generateSignedPreKeyRecord(KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID),
+ pniIdentity.getPrivateKey());
+ final var signedPreKeyEntity = new SignedPreKeyEntity(signedPreKeyRecord.getId(),
+ signedPreKeyRecord.getKeyPair().getPublicKey(),
+ signedPreKeyRecord.getSignature());
+ devicePniSignedPreKeys.put(deviceId, signedPreKeyEntity);
+
+ // Last-resort kyber prekey
+ final var lastResortKyberPreKeyRecord = KeyUtils.generateKyberPreKeyRecord(KeyUtils.getRandomInt(
+ PREKEY_MAXIMUM_ID), pniIdentity.getPrivateKey());
+ final var kyberPreKeyEntity = new KyberPreKeyEntity(lastResortKyberPreKeyRecord.getId(),
+ lastResortKyberPreKeyRecord.getKeyPair().getPublicKey(),
+ lastResortKyberPreKeyRecord.getSignature());
+ devicePniLastResortKyberPreKeys.put(deviceId, kyberPreKeyEntity);
+
+ // Registration Id
+ var pniRegistrationId = -1;
+ while (pniRegistrationId < 0 || pniRegistrationIds.containsValue(pniRegistrationId)) {
+ pniRegistrationId = KeyHelper.generateRegistrationId(false);
+ }
+ pniRegistrationIds.put(deviceId, pniRegistrationId);
+
+ // Device Message
+ final var pniChangeNumber = new SyncMessage.PniChangeNumber.Builder().identityKeyPair(ByteString.of(
+ pniIdentity.serialize()))
+ .signedPreKey(ByteString.of(signedPreKeyRecord.serialize()))
+ .lastResortKyberPreKey(ByteString.of(lastResortKyberPreKeyRecord.serialize()))
+ .registrationId(pniRegistrationId)
+ .newE164(newNumber)
+ .build();
+
+ if (deviceId == selfDeviceId) {
+ selfChangeNumber = pniChangeNumber;
+ } else {
+ try {
+ final var message = messageSender.getEncryptedSyncPniInitializeDeviceMessage(deviceId,
+ pniChangeNumber);
+ encryptedDeviceMessages.add(message);
+ } catch (UntrustedIdentityException | IOException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ final var sessionId = account.getSessionId(newNumber);