From 6424c6bc99ea9b5faf50f54e0297056767a9b705 Mon Sep 17 00:00:00 2001 From: Nicholas Tinsley Date: Tue, 30 Jul 2024 19:54:48 +0200 Subject: [PATCH] Fixes for restoring a backup after completing registration. --- .../securesms/PassphraseRequiredActivity.java | 3 +- .../newdevice/NewDeviceTransferFragment.java | 2 + .../LocalRegistrationMetadataSerializer.kt | 17 ++++++ .../keyvalue/RegistrationValues.java | 26 ++++++++ .../registration/RegistrationUtil.java | 1 + .../data/LocalRegistrationMetadataUtil.kt | 61 +++++++++++++++++++ .../data/RegistrationRepository.kt | 32 +++++----- .../registration/ui/RegistrationViewModel.kt | 7 ++- .../ui/restore/RemoteRestoreActivity.kt | 2 + .../RestoreLocalBackupFragment.kt | 1 + .../RestoreLocalBackupViewModel.kt | 29 +++++++-- app/src/main/protowire/Database.proto | 20 ++++++ 12 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/keyvalue/LocalRegistrationMetadataSerializer.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/registration/data/LocalRegistrationMetadataUtil.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index 7cc7ebe936..afe98f5a0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -187,7 +187,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements } private boolean userCanTransferOrRestore() { - return !SignalStore.registration().isRegistrationComplete() && RemoteConfig.restoreAfterRegistration() && !SignalStore.registration().hasSkippedTransferOrRestore(); + return !SignalStore.registration().isRegistrationComplete() && RemoteConfig.restoreAfterRegistration() && !SignalStore.registration().hasSkippedTransferOrRestore() && !SignalStore.registration().hasCompletedRestore(); } private boolean userMustCreateSignalPin() { @@ -241,6 +241,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements private Intent getTransferOrRestoreIntent() { Intent intent = RestoreActivity.getIntentForTransferOrRestore(this); + intent.putExtra(NEXT_INTENT_EXTRA, MainActivity.clearTop(this)); return getRoutedIntent(intent, getIntent()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/newdevice/NewDeviceTransferFragment.java b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/newdevice/NewDeviceTransferFragment.java index 045d44b6dd..6feb785390 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/newdevice/NewDeviceTransferFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/newdevice/NewDeviceTransferFragment.java @@ -13,6 +13,7 @@ import org.greenrobot.eventbus.ThreadMode; import org.signal.devicetransfer.DeviceToDeviceTransferService; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.devicetransfer.DeviceTransferFragment; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.navigation.SafeNavigation; /** @@ -61,6 +62,7 @@ public final class NewDeviceTransferFragment extends DeviceTransferFragment { case SUCCESS: transferFinished = true; DeviceToDeviceTransferService.stop(requireContext()); + SignalStore.registration().markRestoreCompleted(); navigateToTransferComplete(); break; case FAILURE_VERSION_DOWNGRADE: diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/LocalRegistrationMetadataSerializer.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/LocalRegistrationMetadataSerializer.kt new file mode 100644 index 0000000000..9106bc20cc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/LocalRegistrationMetadataSerializer.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.keyvalue + +import org.signal.core.util.ByteSerializer +import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata + +/** + * Serialize [LocalRegistrationMetadata] + */ +object LocalRegistrationMetadataSerializer : ByteSerializer { + override fun serialize(data: LocalRegistrationMetadata): ByteArray = data.encode() + override fun deserialize(data: ByteArray): LocalRegistrationMetadata = LocalRegistrationMetadata.ADAPTER.decode(data) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java index 0e116e10c9..7f64139092 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/RegistrationValues.java @@ -4,6 +4,8 @@ import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata; + import java.util.Collections; import java.util.List; @@ -15,6 +17,8 @@ public final class RegistrationValues extends SignalStoreValues { private static final String SESSION_E164 = "registration.session_e164"; private static final String SESSION_ID = "registration.session_id"; private static final String SKIPPED_TRANSFER_OR_RESTORE = "registration.has_skipped_transfer_or_restore"; + private static final String LOCAL_REGISTRATION_DATA = "registration.local_registration_data"; + private static final String RESTORE_COMPLETED = "registration.backup_restore_completed"; RegistrationValues(@NonNull KeyValueStore store) { super(store); @@ -54,6 +58,20 @@ public final class RegistrationValues extends SignalStoreValues { return getStore().getBoolean(REGISTRATION_COMPLETE, true); } + + public void setLocalRegistrationMetadata(LocalRegistrationMetadata data) { + putObject(LOCAL_REGISTRATION_DATA, data, LocalRegistrationMetadataSerializer.INSTANCE); + } + + @Nullable + public LocalRegistrationMetadata getLocalRegistrationMetadata() { + return getObject(LOCAL_REGISTRATION_DATA, null, LocalRegistrationMetadataSerializer.INSTANCE); + } + + public void clearLocalRegistrationMetadata() { + remove(LOCAL_REGISTRATION_DATA); + } + public boolean hasUploadedProfile() { return getBoolean(HAS_UPLOADED_PROFILE, true); } @@ -95,4 +113,12 @@ public final class RegistrationValues extends SignalStoreValues { public String getSessionE164() { return getString(SESSION_E164, null); } + + public boolean hasCompletedRestore() { + return getBoolean(RESTORE_COMPLETED, false); + } + + public void markRestoreCompleted() { + putBoolean(RESTORE_COMPLETED, true); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java index 18879409b8..a519e3094b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationUtil.java @@ -28,6 +28,7 @@ public final class RegistrationUtil { { Log.i(TAG, "Marking registration completed.", new Throwable()); SignalStore.registration().setRegistrationComplete(); + SignalStore.registration().clearLocalRegistrationMetadata(); if (SignalStore.phoneNumberPrivacy().getPhoneNumberDiscoverabilityMode() == PhoneNumberDiscoverabilityMode.UNDECIDED) { Log.w(TAG, "Phone number discoverability mode is still UNDECIDED. Setting to DISCOVERABLE."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/data/LocalRegistrationMetadataUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/data/LocalRegistrationMetadataUtil.kt new file mode 100644 index 0000000000..216f6a44cf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/data/LocalRegistrationMetadataUtil.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.registration.data + +import okio.ByteString.Companion.toByteString +import org.signal.libsignal.protocol.IdentityKey +import org.signal.libsignal.protocol.state.KyberPreKeyRecord +import org.signal.libsignal.protocol.state.SignedPreKeyRecord +import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata +import org.thoughtcrime.securesms.registration.RegistrationData +import org.whispersystems.signalservice.api.account.PreKeyCollection + +/** + * Takes the two sources of registration data ([RegistrationData], [RegistrationRepository.AccountRegistrationResult]) + * and combines them into a proto-backed class [LocalRegistrationMetadata] so they can be serialized & stored. + */ +object LocalRegistrationMetadataUtil { + fun createLocalRegistrationMetadata(registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean): LocalRegistrationMetadata { + return LocalRegistrationMetadata.Builder().apply { + aciIdentityKey = remoteResult.aciPreKeyCollection.identityKey.serialize().toByteString() + aciSignedPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString() + aciLastRestoreKyberPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString() + pniIdentityKey = remoteResult.pniPreKeyCollection.identityKey.serialize().toByteString() + pniSignedPreKey = remoteResult.pniPreKeyCollection.signedPreKey.serialize().toByteString() + pniLastRestoreKyberPreKey = remoteResult.pniPreKeyCollection.signedPreKey.serialize().toByteString() + aci = remoteResult.uuid + pni = remoteResult.pni + hasPin = remoteResult.storageCapable + remoteResult.pin?.let { + pin = it + } + remoteResult.masterKey?.serialize()?.toByteString()?.let { + masterKey = it + } + e164 = registrationData.e164 + fcmEnabled = registrationData.isFcm + profileKey = registrationData.profileKey.serialize().toByteString() + servicePassword = registrationData.password + this.reglockEnabled = reglockEnabled + }.build() + } + + fun LocalRegistrationMetadata.getAciPreKeyCollection(): PreKeyCollection { + return PreKeyCollection( + IdentityKey(aciIdentityKey.toByteArray()), + SignedPreKeyRecord(aciSignedPreKey.toByteArray()), + KyberPreKeyRecord(aciLastRestoreKyberPreKey.toByteArray()) + ) + } + + fun LocalRegistrationMetadata.getPniPreKeyCollection(): PreKeyCollection { + return PreKeyCollection( + IdentityKey(pniIdentityKey.toByteArray()), + SignedPreKeyRecord(pniSignedPreKey.toByteArray()), + KyberPreKeyRecord(pniLastRestoreKyberPreKey.toByteArray()) + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/data/RegistrationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/data/RegistrationRepository.kt index 14a9cedbc9..e98681b0f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/data/RegistrationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/data/RegistrationRepository.kt @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl import org.thoughtcrime.securesms.database.IdentityTable import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.gcm.FcmUtil import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob @@ -45,6 +46,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.registration.PushChallengeRequest import org.thoughtcrime.securesms.registration.RegistrationData import org.thoughtcrime.securesms.registration.VerifyAccountRepository +import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil.getAciPreKeyCollection +import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil.getPniPreKeyCollection import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult @@ -159,14 +162,14 @@ object RegistrationRepository { * Takes a server response from a successful registration and persists the relevant data. */ @JvmStatic - suspend fun registerAccountLocally(context: Context, registrationData: RegistrationData, response: AccountRegistrationResult, reglockEnabled: Boolean) = + suspend fun registerAccountLocally(context: Context, data: LocalRegistrationMetadata) = withContext(Dispatchers.IO) { Log.v(TAG, "registerAccountLocally()") - val aciPreKeyCollection: PreKeyCollection = response.aciPreKeyCollection - val pniPreKeyCollection: PreKeyCollection = response.pniPreKeyCollection - val aci: ACI = ACI.parseOrThrow(response.uuid) - val pni: PNI = PNI.parseOrThrow(response.pni) - val hasPin: Boolean = response.storageCapable + val aciPreKeyCollection = data.getAciPreKeyCollection() + val pniPreKeyCollection = data.getPniPreKeyCollection() + val aci: ACI = ACI.parseOrThrow(data.aci) + val pni: PNI = PNI.parseOrThrow(data.pni) + val hasPin: Boolean = data.hasPin SignalStore.account.setAci(aci) SignalStore.account.setPni(pni) @@ -187,30 +190,31 @@ object RegistrationRepository { storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection) val recipientTable = SignalDatabase.recipients - val selfId = Recipient.trustedPush(aci, pni, registrationData.e164).id + val selfId = Recipient.trustedPush(aci, pni, data.e164).id recipientTable.setProfileSharing(selfId, true) recipientTable.markRegisteredOrThrow(selfId, aci) - recipientTable.linkIdsForSelf(aci, pni, registrationData.e164) - recipientTable.setProfileKey(selfId, registrationData.profileKey) + recipientTable.linkIdsForSelf(aci, pni, data.e164) + recipientTable.setProfileKey(selfId, ProfileKey(data.profileKey.toByteArray())) AppDependencies.recipientCache.clearSelf() - SignalStore.account.setE164(registrationData.e164) - SignalStore.account.fcmToken = registrationData.fcmToken - SignalStore.account.fcmEnabled = registrationData.isFcm + SignalStore.account.setE164(data.e164) + SignalStore.account.fcmToken = data.fcmToken + SignalStore.account.fcmEnabled = data.fcmEnabled val now = System.currentTimeMillis() saveOwnIdentityKey(selfId, aci, aciProtocolStore, now) saveOwnIdentityKey(selfId, pni, pniProtocolStore, now) - SignalStore.account.setServicePassword(registrationData.password) + SignalStore.account.setServicePassword(data.servicePassword) SignalStore.account.setRegistered(true) TextSecurePreferences.setPromptedPushRegistration(context, true) TextSecurePreferences.setUnauthorizedReceived(context, false) NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID) - SvrRepository.onRegistrationComplete(response.masterKey, response.pin, hasPin, reglockEnabled) + val masterKey = if (data.masterKey != null) MasterKey(data.masterKey.toByteArray()) else null + SvrRepository.onRegistrationComplete(masterKey, data.pin, hasPin, data.reglockEnabled) AppDependencies.resetNetwork() AppDependencies.incomingMessageObserver diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt index ca6afcb51c..0809d39161 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/RegistrationViewModel.kt @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.pin.SvrRepository import org.thoughtcrime.securesms.pin.SvrWrongPinException import org.thoughtcrime.securesms.registration.RegistrationData import org.thoughtcrime.securesms.registration.RegistrationUtil +import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil import org.thoughtcrime.securesms.registration.data.RegistrationRepository import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult import org.thoughtcrime.securesms.registration.data.network.Challenge @@ -815,7 +816,11 @@ class RegistrationViewModel : ViewModel() { private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean) { Log.v(TAG, "onSuccessfulRegistration()") - RegistrationRepository.registerAccountLocally(context, registrationData, remoteResult, reglockEnabled) + val metadata = LocalRegistrationMetadataUtil.createLocalRegistrationMetadata(registrationData, remoteResult, reglockEnabled) + if (RemoteConfig.restoreAfterRegistration) { + SignalStore.registration.localRegistrationMetadata = metadata + } + RegistrationRepository.registerAccountLocally(context, metadata) if (reglockEnabled) { SignalStore.onboarding.clearAll() diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/RemoteRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/RemoteRestoreActivity.kt index e1be1f35d0..a1053a918c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/RemoteRestoreActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/RemoteRestoreActivity.kt @@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.RemoteRestoreViewMod import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobs.ProfileUploadJob +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.profiles.AvatarHelper import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity import org.thoughtcrime.securesms.recipients.Recipient @@ -101,6 +102,7 @@ class RemoteRestoreActivity : BaseActivity() { ) if (state.importState == RemoteRestoreViewModel.ImportState.RESTORED) { SideEffect { + SignalStore.registration.markRestoreCompleted() RegistrationUtil.maybeMarkRegistrationComplete() AppDependencies.jobManager.add(ProfileUploadJob()) startActivity(MainActivity.clearTop(this)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt index 3b092babee..dbbe5e7a93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt @@ -99,6 +99,7 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc onBackupCompletedSuccessfully() } else { handleBackupImportError(importResult) + restoreLocalBackupViewModel.backupImportErrorShown() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupViewModel.kt index ee6cc891a8..54e147528e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupViewModel.kt @@ -16,6 +16,9 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.backup.BackupEvent +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.registration.RegistrationUtil +import org.thoughtcrime.securesms.registration.data.RegistrationRepository import org.thoughtcrime.securesms.restore.RestoreRepository /** @@ -82,13 +85,23 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() { } viewModelScope.launch { - val importResult = RestoreRepository.restoreBackupAsynchronously(context, backupFileUri, backupPassphrase) + val importResult: RestoreRepository.BackupImportResult = RestoreRepository.restoreBackupAsynchronously(context, backupFileUri, backupPassphrase) + + if (importResult == RestoreRepository.BackupImportResult.SUCCESS) { + SignalStore.registration.localRegistrationMetadata?.let { + RegistrationRepository.registerAccountLocally(context, it) + SignalStore.registration.clearLocalRegistrationMetadata() + RegistrationUtil.maybeMarkRegistrationComplete() + } + + SignalStore.registration.markRestoreCompleted() + } store.update { it.copy( backupImportResult = if (importResult == RestoreRepository.BackupImportResult.SUCCESS) null else importResult, restoreInProgress = false, - backupRestoreComplete = true, + backupRestoreComplete = importResult == RestoreRepository.BackupImportResult.SUCCESS, backupEstimatedTotalCount = -1L, backupProgressCount = -1L, backupVerifyingInProgress = false @@ -102,9 +115,7 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() { it.copy( backupProgressCount = event.count, backupEstimatedTotalCount = event.estimatedTotalCount, - backupVerifyingInProgress = event.type == BackupEvent.Type.PROGRESS_VERIFYING, - backupRestoreComplete = event.type == BackupEvent.Type.FINISHED, - restoreInProgress = event.type != BackupEvent.Type.FINISHED + backupVerifyingInProgress = event.type == BackupEvent.Type.PROGRESS_VERIFYING ) } } @@ -113,6 +124,14 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() { store.update { it.copy(backupFileStateError = null) } } + fun backupImportErrorShown() { + store.update { + it.copy( + backupImportResult = null + ) + } + } + companion object { private val TAG = Log.tag(RestoreLocalBackupViewModel::class.java) } diff --git a/app/src/main/protowire/Database.proto b/app/src/main/protowire/Database.proto index 2a9aa3d073..05f6de813a 100644 --- a/app/src/main/protowire/Database.proto +++ b/app/src/main/protowire/Database.proto @@ -514,3 +514,23 @@ message PaymentTombstone { CryptoValue amount = 2; CryptoValue fee = 3; } + +message LocalRegistrationMetadata { + bytes aciIdentityKey = 1; + bytes aciSignedPreKey = 2; + bytes aciLastRestoreKyberPreKey = 3; + bytes pniIdentityKey = 4; + bytes pniSignedPreKey = 5; + bytes pniLastRestoreKyberPreKey = 6; + string aci = 7; + string pni = 8; + bool hasPin = 9; + optional string pin = 10; + optional bytes masterKey = 11; + string e164 = 12; + bool fcmEnabled = 13; + string fcmToken = 14; + bytes profileKey = 15; + string servicePassword = 16; + bool reglockEnabled = 17; +}