Compare commits

...

13 Commits

Author SHA1 Message Date
Alexander Bondarenko c3147e47a1 ios: wrap chatMigrateInit in bgTask 2024-03-27 17:07:37 +02:00
spaced4ndy c9bf36291b ios: fix default (#3958) 2024-03-27 09:59:15 +00:00
spaced4ndy 42ebe8bc60 core: batch broadcast send db operations (#3956)
* core: batch broadcast send db operations

* refactor

* Update src/Simplex/Chat.hs

Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
2024-03-27 11:49:14 +04:00
Stanislav Dmitrenko 96fb61ccfc desktop: show toast if failed to show notification (#3955)
* desktop: show toast if failed to show notification

* longer timeout

* returned old version of the lib
2024-03-26 12:43:45 +00:00
spaced4ndy 1d698c114f docs: communicate rejection rfc (#3946) 2024-03-26 11:29:28 +04:00
Stanislav Dmitrenko 154ce5843d android, desktop: update dependencies (#3954)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2024-03-25 15:59:00 +00:00
Evgeny Poberezkin a5817d9feb core: fix cabal paths (#3952)
* fixed install cabal file paths.

* updated package.yaml

* fix

---------

Co-authored-by: Andor Kesselman <andor@magic-pro-3.local>
2024-03-25 08:24:46 +00:00
Evgeny Poberezkin 72082af139 docs: update version 2024-03-23 19:20:37 +00:00
Evgeny Poberezkin eb36704f59 Merge branch 'stable' 2024-03-23 18:42:53 +00:00
Evgeny Poberezkin 84fbff6148 blog: v5.6 release, non-profit governance (#3951)
* blog: v5.6 release

* images, preview

* corrections

* update image

* update readme
2024-03-23 18:42:18 +00:00
Evgeny Poberezkin dd75d30c08 blog: update PQ encryption post (#3948)
* blog: update pq encryption post

* correction
2024-03-23 18:29:05 +00:00
spaced4ndy 52803ae976 core: add delays to some failing tests (#3947) 2024-03-22 22:01:11 +04:00
spaced4ndy ec73a31cb5 directory: fix double logic inversion on roles check, incorrect error message (#3944) 2024-03-22 15:07:01 +04:00
49 changed files with 454 additions and 149 deletions
+4 -1
View File
@@ -234,6 +234,8 @@ You can use SimpleX with your own servers and still communicate with people usin
Recent and important updates:
[Mar 23, 2024. SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration.](./blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md)
[Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md)
[Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md)
@@ -377,8 +379,9 @@ Please also join [#simplex-devs](https://simplex.chat/contact#/?v=1-2&smp=smp%3A
- ✅ Using mobile profiles from the desktop app.
- ✅ Private notes.
- ✅ Improve sending videos (including encryption of locally stored videos).
- ✅ Post-quantum resistant key exchange in double ratchet protocol.
- 🏗 Improve stability and reduce battery usage.
- 🏗 Improve experience for the new users.
- 🏗 Post-quantum resistant key exchange in double ratchet protocol.
- 🏗 Large groups, communities and public channels.
- 🏗 Message delivery relay for senders (to conceal IP address from the recipients' servers and to reduce the traffic).
- Privacy & security slider - a simple way to set all settings at once.
+4 -1
View File
@@ -1293,7 +1293,10 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni
let m = ChatModel.shared
m.ctrlInitInProgress = true
defer { m.ctrlInitInProgress = false }
(m.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey, confirmMigrations: confirmMigrations)
let endTask = beginBGTask({
// run DB operations as "backgroundable" to prevent 0xdead10cc SIGKILLs
(m.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey, confirmMigrations: confirmMigrations)
})
if m.chatDbStatus != .ok { return }
// If we migrated successfully means previous re-encryption process on database level finished successfully too
if encryptionStartedDefault.get() {
+1 -1
View File
@@ -197,7 +197,7 @@ public let confirmDBUpgradesGroupDefault = BoolDefault(defaults: groupDefaults,
public let callKitEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CALL_KIT_ENABLED)
public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES)
public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED)
public class DateDefault {
var defaults: UserDefaults
+39 -35
View File
@@ -8,12 +8,14 @@ plugins {
}
android {
compileSdkVersion(34)
compileSdk = 34
defaultConfig {
applicationId = "chat.simplex.app"
minSdkVersion(28)
targetSdkVersion(33)
namespace = "chat.simplex.app"
minSdk = 28
//noinspection OldTargetApi
targetSdk = 33
// !!!
// skip version code after release to F-Droid, as it uses two version codes
versionCode = (extra["android.version_code"] as String).toInt()
@@ -47,11 +49,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
freeCompilerArgs += "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi"
freeCompilerArgs += "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
freeCompilerArgs += "-opt-in=androidx.compose.ui.text.ExperimentalTextApi"
@@ -71,18 +73,20 @@ android {
isMinifyEnabled = false
}
}
packagingOptions {
buildFeatures {
buildConfig = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
jniLibs.useLegacyPackaging = rootProject.extra["compression.level"] as Int != 0
}
android.sourceSets["main"].assets.setSrcDirs(listOf("../common/src/commonMain/resources/assets"))
val isRelease = gradle.startParameter.taskNames.find { it.toLowerCase().contains("release") } != null
val isBundle = gradle.startParameter.taskNames.find { it.toLowerCase().contains("bundle") } != null
// if (isRelease) {
val isRelease = gradle.startParameter.taskNames.find { it.lowercase().contains("release") } != null
val isBundle = gradle.startParameter.taskNames.find { it.lowercase().contains("bundle") } != null
// Comma separated list of languages that will be included in the apk
android.defaultConfig.resConfigs(
android.defaultConfig.resourceConfigurations += listOf(
"en",
"ar",
"bg",
@@ -104,7 +108,7 @@ android {
"uk",
"zh-rCN"
)
// }
ndkVersion = "23.1.7779620"
if (isBundle) {
defaultConfig.ndk.abiFilters("arm64-v8a", "armeabi-v7a")
} else {
@@ -125,41 +129,41 @@ android {
dependencies {
implementation(project(":common"))
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.core:core-ktx:1.12.0")
//implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}")
//implementation("androidx.compose.material:material:$compose_version")
//implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1")
implementation("androidx.lifecycle:lifecycle-process:2.4.1")
implementation("androidx.activity:activity-compose:1.5.0")
val work_version = "2.7.1"
implementation("androidx.work:work-runtime-ktx:$work_version")
implementation("androidx.work:work-multiprocess:$work_version")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-process:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
val workVersion = "2.9.0"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("androidx.work:work-multiprocess:$workVersion")
implementation("com.jakewharton:process-phoenix:2.1.2")
implementation("com.jakewharton:process-phoenix:2.2.0")
//implementation("androidx.compose.material:material-icons-extended:$compose_version")
//implementation("androidx.compose.ui:ui-util:$compose_version")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
//androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
debugImplementation("androidx.compose.ui:ui-tooling:1.4.3")
debugImplementation("androidx.compose.ui:ui-tooling:1.6.4")
}
tasks {
val compressApk by creating {
doLast {
val isRelease = gradle.startParameter.taskNames.find { it.toLowerCase().contains("release") } != null
val isRelease = gradle.startParameter.taskNames.find { it.lowercase().contains("release") } != null
val buildType: String = if (isRelease) "release" else "debug"
val javaHome = System.getProperties()["java.home"] ?: org.gradle.internal.jvm.Jvm.current().javaHome
val sdkDir = android.sdkDirectory.absolutePath
var keyAlias = ""
var keyPassword = ""
var storeFile = ""
var storePassword = ""
val keyAlias: String
val keyPassword: String
val storeFile: String
val storePassword: String
if (project.properties["android.injected.signing.key.alias"] != null) {
keyAlias = project.properties["android.injected.signing.key.alias"] as String
keyPassword = project.properties["android.injected.signing.key.password"] as String
@@ -184,16 +188,16 @@ tasks {
}
exec {
workingDir("../../../scripts/android")
setEnvironment(mapOf("JAVA_HOME" to "$javaHome"))
environment = mapOf("JAVA_HOME" to "$javaHome")
commandLine = listOf(
"./compress-and-sign-apk.sh",
"${rootProject.extra["compression.level"]}",
"$outputDir",
"$sdkDir",
"$storeFile",
"$storePassword",
"$keyAlias",
"$keyPassword"
sdkDir,
storeFile,
storePassword,
keyAlias,
keyPassword
)
}
@@ -6,6 +6,7 @@ import android.content.*
import android.content.pm.PackageManager
import android.net.Uri
import android.os.*
import android.os.SystemClock
import android.provider.Settings
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
@@ -4,6 +4,7 @@ import android.app.*
import android.app.TaskStackBuilder
import android.content.*
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.hardware.display.DisplayManager
import android.media.AudioAttributes
@@ -218,7 +219,7 @@ object NtfManager {
.setGroup(MessageGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
.setSmallIcon(R.drawable.ntf_icon)
.setLargeIcon(null)
.setLargeIcon(null as Bitmap?)
.setColor(0x88FFFF)
.setAutoCancel(true)
.setVibrate(null)
+3
View File
@@ -47,6 +47,9 @@ buildscript {
classpath(kotlin("gradle-plugin", version = rootProject.extra["kotlin.version"] as String))
classpath("dev.icerock.moko:resources-generator:0.23.0")
// Workaround gradle sync issue: https://github.com/gmazzo/gradle-buildconfig-plugin/issues/137#issuecomment-1935739759
classpath("com.squareup:kotlinpoet:1.16.0")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
+42 -39
View File
@@ -4,17 +4,18 @@ plugins {
id("com.android.library")
id("org.jetbrains.kotlin.plugin.serialization")
id("dev.icerock.mobile.multiplatform-resources")
id("com.github.gmazzo.buildconfig") version "4.0.4"
id("com.github.gmazzo.buildconfig") version "5.3.5"
}
group = "chat.simplex"
version = extra["android.version_name"] as String
kotlin {
android()
androidTarget()
jvm("desktop") {
jvmToolchain(11)
}
applyDefaultHierarchyTemplate()
sourceSets {
all {
languageSettings {
@@ -35,20 +36,20 @@ kotlin {
api(compose.runtime)
api(compose.foundation)
api(compose.material)
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
api("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2")
api("com.russhwolf:multiplatform-settings:1.0.0")
api("com.charleskorn.kaml:kaml:0.43.0")
api("dev.icerock.moko:resources-compose:0.23.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
api("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
api("com.russhwolf:multiplatform-settings:1.1.1")
api("com.charleskorn.kaml:kaml:0.58.0")
api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}")
implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}")
//Barcode
api("org.boofcv:boofcv-core:0.40.1")
api("org.boofcv:boofcv-core:1.1.3")
implementation("com.godaddy.android.colorpicker:compose-color-picker-jvm:0.7.0")
// Link Previews
implementation("org.jsoup:jsoup:1.13.1")
implementation("org.jsoup:jsoup:1.17.2")
// Resources
implementation("dev.icerock.moko:resources:0.23.0")
api("dev.icerock.moko:resources:0.23.0")
api("dev.icerock.moko:resources-compose:0.23.0")
}
}
val commonTest by getting {
@@ -57,47 +58,47 @@ kotlin {
}
}
val androidMain by getting {
kotlin.srcDir("build/generated/moko/androidMain/src")
dependencies {
implementation("androidx.activity:activity-compose:1.5.0")
val work_version = "2.7.1"
implementation("androidx.work:work-runtime-ktx:$work_version")
implementation("com.google.accompanist:accompanist-insets:0.23.0")
implementation("dev.icerock.moko:resources:0.23.0")
implementation("androidx.activity:activity-compose:1.8.2")
val workVersion = "2.9.0"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("com.google.accompanist:accompanist-insets:0.30.1")
// Video support
implementation("com.google.android.exoplayer:exoplayer:2.17.1")
implementation("com.google.android.exoplayer:exoplayer:2.19.1")
// Biometric authentication
implementation("androidx.biometric:biometric:1.2.0-alpha04")
implementation("androidx.biometric:biometric:1.2.0-alpha05")
//Barcode
implementation("org.boofcv:boofcv-android:0.40.1")
implementation("org.boofcv:boofcv-android:1.1.3")
//Camera Permission
implementation("com.google.accompanist:accompanist-permissions:0.23.0")
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
implementation("androidx.webkit:webkit:1.4.0")
implementation("androidx.webkit:webkit:1.10.0")
// GIFs support
implementation("io.coil-kt:coil-compose:2.1.0")
implementation("io.coil-kt:coil-gif:2.1.0")
implementation("io.coil-kt:coil-compose:2.6.0")
implementation("io.coil-kt:coil-gif:2.6.0")
implementation("com.jakewharton:process-phoenix:2.1.2")
implementation("com.jakewharton:process-phoenix:2.2.0")
val camerax_version = "1.1.0-beta01"
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-view:${camerax_version}")
val cameraXVersion = "1.3.2"
implementation("androidx.camera:camera-core:${cameraXVersion}")
implementation("androidx.camera:camera-camera2:${cameraXVersion}")
implementation("androidx.camera:camera-lifecycle:${cameraXVersion}")
implementation("androidx.camera:camera-view:${cameraXVersion}")
}
}
val desktopMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1")
implementation("com.github.Dansoftowner:jSystemThemeDetector:3.6")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0")
implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8")
implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT")
implementation("org.slf4j:slf4j-simple:2.0.7")
implementation("uk.co.caprica:vlcj:4.7.3")
implementation("org.slf4j:slf4j-simple:2.0.12")
implementation("uk.co.caprica:vlcj:4.8.2")
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a")
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a")
}
@@ -107,23 +108,25 @@ kotlin {
}
android {
compileSdkVersion(34)
namespace = "chat.simplex.common"
compileSdk = 34
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(28)
targetSdkVersion(33)
minSdk = 28
}
testOptions.targetSdk = 33
lint.targetSdk = 33
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
val isAndroid = gradle.startParameter.taskNames.find {
val lower = it.toLowerCase()
val lower = it.lowercase()
lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install")
} != null
if (isAndroid) {
// This is not needed on Android but can't be moved to desktopMain because MR lib don't support this.
// No other ways to exclude a file work but it's large and should be excluded
// No other ways to exclude a file work, but it's large and should be excluded
kotlin.sourceSets["commonMain"].resources.exclude("/MR/fonts/NotoColorEmoji-Regular.ttf")
}
}
@@ -15,6 +15,7 @@ import chat.simplex.common.platform.FileChooserLauncher
import chat.simplex.common.platform.saveImage
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@@ -30,7 +31,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
ItemAction(stringResource(MR.strings.save_verb), painterResource(if (cItem.file?.fileSource?.cryptoArgs == null) MR.images.ic_download else MR.images.ic_lock_open_right), onClick = {
when (cItem.content.msgContent) {
is MsgContent.MCImage -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.hasPermission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || writePermissionState.status == PermissionStatus.Granted) {
saveImage(cItem.file)
} else {
writePermissionState.launchPermissionRequest()
@@ -11,6 +11,7 @@ import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
@@ -27,6 +28,7 @@ import chat.simplex.common.platform.TAG
import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState
import com.google.common.util.concurrent.ListenableFuture
import dev.icerock.moko.resources.compose.painterResource
@@ -65,7 +67,7 @@ actual fun QRCodeScanner(
.widthIn(max = 400.dp)
.aspectRatio(1f)
val showScanner = remember { showQRCodeScanner }
if (showScanner.value && cameraPermissionState.hasPermission) {
if (showScanner.value && cameraPermissionState.status == PermissionStatus.Granted) {
AndroidView(
factory = { AndroidViewContext ->
PreviewView(AndroidViewContext).apply {
@@ -126,20 +128,22 @@ actual fun QRCodeScanner(
disabledBackgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f),
disabledContentColor = MaterialTheme.colors.primary,
)
var permissionRequested by rememberSaveable { mutableStateOf(false) }
when {
!cameraPermissionState.hasPermission && !cameraPermissionState.permissionRequested && showScanner.value -> {
cameraPermissionState.status is PermissionStatus.Denied && !permissionRequested && showScanner.value -> {
LaunchedEffect(Unit) {
permissionRequested = true
cameraPermissionState.launchPermissionRequest()
}
}
!cameraPermissionState.hasPermission -> {
cameraPermissionState.status is PermissionStatus.Denied -> {
Button({ cameraPermissionState.launchPermissionRequest() }, modifier = modifier, colors = buttonColors) {
Icon(painterResource(MR.images.ic_camera_enhance), null)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Text(stringResource(MR.strings.enable_camera_access))
}
}
cameraPermissionState.hasPermission -> {
cameraPermissionState.status == PermissionStatus.Granted -> {
Button({ showQRCodeScanner.value = true }, modifier = modifier, colors = buttonColors) {
Icon(painterResource(MR.images.ic_qr_code), null)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
@@ -5,14 +5,15 @@ import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import chat.simplex.common.platform.ntfManager
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState
@Composable
actual fun SetNotificationsModeAdditions() {
if (Build.VERSION.SDK_INT >= 33) {
val notificationsPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS)
LaunchedEffect(notificationsPermissionState.hasPermission) {
if (notificationsPermissionState.hasPermission) {
LaunchedEffect(notificationsPermissionState.status == PermissionStatus.Granted) {
if (notificationsPermissionState.status == PermissionStatus.Granted) {
ntfManager.androidCreateNtfChannelsMaybeShowAlert()
} else {
notificationsPermissionState.launchPermissionRequest()
@@ -109,6 +109,7 @@ private fun fileReady(file: CIFile, filePath: String) =
@Composable
expect fun rememberFileChooserLauncher(getContent: Boolean, rememberedValue: Any? = null, onResult: (URI?) -> Unit): FileChooserLauncher
@Composable
expect fun rememberFileChooserMultipleLauncher(onResult: (List<URI>) -> Unit): FileChooserMultipleLauncher
expect class FileChooserLauncher() {
@@ -260,6 +260,7 @@ val CurrentColors: MutableStateFlow<ThemeManager.ActiveTheme> = MutableStateFlow
@Composable
fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight
@Composable
expect fun isSystemInDarkTheme(): Boolean
fun reactOnDarkThemeChanges(isDark: Boolean) {
@@ -211,6 +211,7 @@
<string name="notification_preview_new_message">new message</string>
<string name="notification_new_contact_request">New contact request</string>
<string name="notification_contact_connected">Connected</string>
<string name="error_showing_desktop_notification">Error showing notification, contact developers.</string>
<!-- local authentication notice - SimpleXAPI.kt -->
<string name="la_notice_title_simplex_lock">SimpleX Lock</string>
@@ -111,8 +111,12 @@ object NtfManager {
}
try {
prevNtfs.add(chatId to builder.toast())
} catch (e: Exception) {
} catch (e: Throwable) {
Log.e(TAG, e.stackTraceToString())
if (e !is Exception) {
val text = e.stackTraceToString().lines().getOrNull(0) ?: ""
showToast(generalGetString(MR.strings.error_showing_desktop_notification) + " " + text, 4_000)
}
}
}
@@ -32,9 +32,9 @@ fun initApp() {
//testCrypto()
}
fun discoverVlcLibs(path: String) {
uk.co.caprica.vlcj.binding.LibC.INSTANCE.setenv("VLC_PLUGIN_PATH", path, 1)
}
//fun discoverVlcLibs(path: String) {
// uk.co.caprica.vlcj.binding.LibC.INSTANCE.setenv("VLC_PLUGIN_PATH", path, 1)
//}
private fun applyAppLocale() {
val lang = ChatController.appPrefs.appLanguage.get()
@@ -1,5 +1,6 @@
package chat.simplex.common.ui.theme
import androidx.compose.runtime.Composable
import chat.simplex.common.platform.Log
import chat.simplex.common.platform.TAG
import com.jthemedetecor.OsThemeDetector
@@ -9,6 +10,7 @@ private val detector: OsThemeDetector = OsThemeDetector.getDetector()
registerListener(::reactOnDarkThemeChanges)
}
@Composable
actual fun isSystemInDarkTheme(): Boolean = try {
detector.isDark
}
+1 -1
View File
@@ -21,7 +21,7 @@ kotlin {
dependencies {
implementation(project(":common"))
implementation(compose.desktop.currentOs)
implementation("net.java.dev.jna:jna:5.13.0")
implementation("net.java.dev.jna:jna:5.14.0")
}
}
val jvmTest by getting
+3 -3
View File
@@ -31,6 +31,6 @@ android.version_code=191
desktop.version_name=5.6
desktop.version_code=35
kotlin.version=1.8.20
gradle.plugin.version=7.4.2
compose.version=1.5.10
kotlin.version=1.9.23
gradle.plugin.version=8.2.0
compose.version=1.6.1
@@ -1,6 +1,6 @@
#Mon Feb 14 14:23:51 GMT 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
@@ -177,8 +177,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
badRolesMsg :: GroupRolesStatus -> Maybe String
badRolesMsg = \case
GRSOk -> Nothing
GRSServiceNotAdmin -> Just "You must have a group *owner* role to register the group"
GRSContactNotOwner -> Just "You must grant directory service *admin* role to register the group"
GRSServiceNotAdmin -> Just "You must grant directory service *admin* role to register the group"
GRSContactNotOwner -> Just "You must have a group *owner* role to register the group"
GRSBadRoles -> Just "You must have a group *owner* role and you must grant directory service *admin* role to register the group"
getGroupRolesStatus :: GroupInfo -> GroupReg -> IO (Maybe GroupRolesStatus)
@@ -190,8 +190,8 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
groupRolesStatus :: GroupMemberRole -> GroupMemberRole -> GroupRolesStatus
groupRolesStatus contactRole serviceRole = case (contactRole, serviceRole) of
(GROwner, GRAdmin) -> GRSOk
(_, GRAdmin) -> GRSServiceNotAdmin
(GROwner, _) -> GRSContactNotOwner
(_, GRAdmin) -> GRSContactNotOwner
(GROwner, _) -> GRSServiceNotAdmin
_ -> GRSBadRoles
getGroupMember :: GroupReg -> IO (Maybe GroupMember)
@@ -15,7 +15,7 @@ This is a major upgrade for SimpleX messaging protocols, we are really proud to
This post also covers various aspects of end-to-end encryption, compares different messengers, and explains why and how quantum-resistant encryption is added to SimpleX Chat:
- [Why do we need end-to-end encryption?](#why-do-we-need-end-to-end-encryption)
- [Why encryption is even allowed?](#why-encryption-is-even-allowed)
- [Why is encryption even allowed?](#why-is-encryption-even-allowed)
- [End-to-end encryption security: attacks and defense.](#end-to-end-encryption-security-attacks-and-defense)
- Compromised message size - mitigated by padding messages to a fixed block size.
- Compromised confidentiality - mitigated by repudiation (deniability).
@@ -40,7 +40,7 @@ End-to-end encryption is an important component of our individual and business s
It's very sad to see the same people who keep their financial affairs private to protect from financial crimes, lock their doors to protect from thieves, and curtain their windows to protect from the occasional prying eyes, when it comes to protecting their personal lives from the data criminals say "we don't care about privacy, we have nothing to hide". Everybody's safety depends on keeping their affairs and relations private, not visible to a vast and ruthless data gathering machines, that abuse our data for commercial gain, without any regard to our interests or even [the safety of our families and children](https://nmdoj.gov/press-release/attorney-general-raul-torrez-files-lawsuit-against-meta-platforms-and-mark-zuckerberg-to-protect-children-from-sexual-abuse-and-human-trafficking/).
## Why encryption is even allowed?
## Why is encryption even allowed?
<img src="./images/20240314-djb.jpg" class="float-to-right">
@@ -75,7 +75,7 @@ While the content encryption is the most important, concealing the actual messag
The only effective mitigation to these attacks is to pad all messages to a fixed size. Using space-efficient schemes like Padme, or padding to encryption block size is ineffective for mitigating these attacks, as they still allow differentiating message sizes.
To the best of our knowledge the only messenger other than SimpleX Chat that padded all messages to a fixed packet size was [Pond](https://github.com/agl/pond) - SimpleX design as an evolution of it.
To the best of our knowledge, the only messengers other than SimpleX Chat that pad all messages to a fixed packet size are Cwtch and no longer developed [Pond](https://github.com/agl/pond) - SimpleX design can be seen as an evolution of Pond design.
### 2. Compromised confidential messages - mitigated by repudiation (deniability)
@@ -97,15 +97,15 @@ This property is well understood by the users, and most messengers that focus on
### 4. Compromised long-term or session - mitigated by break-in recovery
This attack is much less understood by the users, and forward secrecy does not protect from it. Arguably, it's almost impossible to compromise individual message keys without compromising long-term or session keys. So the ability of the encryption to recover from break-in (attacker making a copy of the device data without retaining the ongoing access) is both very and pragmatic - break-in attacks are simpler to execute on mobile devices during short-term device access than long-term ongoing compromise.
This attack is much less understood by the users, and forward secrecy does not protect from it. Arguably, it's almost impossible to compromise individual message keys without compromising long-term or session keys. So the ability of the encryption to recover from break-in (attacker making a copy of the device data without retaining the ongoing access) is both very important and pragmatic - break-in attacks are simpler to execute on mobile devices during short-term device access than long-term ongoing compromise.
Out of all encryption algorithms known to us only Signal double ratchet algorithm provides the ability to encryption security after break-ins. This recovery happens automatically and transparently to the users, without them doing anything special even knowing about break-in, by simply sending messages. Every time one of the communication parties replies to another party message, new random keys are generated and previously stolen keys become useless.
Out of all encryption algorithms known to us only _Signal double ratchet algorithm_ (also referred to as _Signal algorithm_ or _double ratchet algorithm_, which is not the same as Signal messaging platform and protocols) provides the ability for the encryption security to recover after break-ins attacks. This recovery happens automatically and transparently to the users, without them doing anything special or even knowing about break-in, by simply sending messages. Every time one of the communication parties replies to another party message, new random keys are generated and previously stolen keys become useless.
Signal double ratchet algorithm is used in Signal, Cwtch and SimpleX Chat. This is why you cannot use SimpleX Chat profile on more than one device at the same time - the encryption scheme rotates the long term keys, randomly, and keys on another device become useless, as they would become useless for the attacker who stole them. Security always has some costs to the convenience.
Double ratchet algorithm is used in Signal, Cwtch and SimpleX Chat. This is why you cannot use SimpleX Chat profile on more than one device at the same time - the encryption scheme rotates the long term keys, randomly, and keys on another device become useless, as they would become useless for the attacker who stole them. Security always has some costs to the convenience.
### 5. Man-in-the-middle attack - mitigated by two-factor key exchange
Many people incorrectly believe that security of end-to-end encryption cannot be broken by communication provider. But end-to-end encryption is as secure as key exchange. While any intermediary passing the keys between senders and recipients cannot recover the private keys from the public keys, they can simply replace the passed public keys with their own and then proxy all communication between the users having full access to the original messages. So instead of having an end-to-end encrypted channel, users would have two half-way encrypted channels - between users and their communication intermediary.
Many people incorrectly believe that security of end-to-end encryption cannot be broken by communication provider. But end-to-end encryption is only as secure as key exchange. While any intermediary passing the keys between senders and recipients cannot recover the private keys from the public keys, they can simply replace the passed public keys with their own and then proxy all communication between the users having full access to the original messages. So instead of having an end-to-end encrypted channel, users would have two half-way encrypted channels - between users and their communication intermediary.
Pictures below illustrate how this attack works for RSA encryption.
@@ -143,7 +143,7 @@ Post-quantum cryptography, or encryption algorithms that are resistant to quantu
- many of post-quantum algorithms have known patent claims, so any system deploying them accepts the risks of patent litigation.
- the silver lining to these limitations is that the risk of appearance of commercially viable quantum computers in the next decade may be exaggerated.
So, to put it bluntly and provocatively, post-quantum cryptography can be compared with a remedy against the illness that nobody has, without any guarantee that it will work. The closest analogy in the history of medicine is _snake oil_.
So, to say it provocatively, post-quantum cryptography can be compared with a remedy against the illness that nobody has, without any guarantee that it will work. While there is a reasonable hope that it _might_ work, so it's not exactly a _snake oil_, these limitations and risks have to be much better communicated to the end users than they are.
<img src="./images/20240314-datacenter.jpg" width="400" class="float-to-right">
@@ -164,8 +164,8 @@ The main objective here is to establish the framework for comparing the security
Some columns are marked with a yellow checkmark:
- when messages are padded, but not to a fixed size.
- when repudiation does not include client-server connection. In case of Cwtch it appears that the presence of cryptographic signatures compromises repudiation (deniability), but it needs to be clarified.
- when 2-factor key exchange is optional, via security code verification.
- when post-quantum cryptography is only added to the initial key agreement, does not protect break-in recovery.
- when 2-factor key exchange is optional (via security code verification).
- when post-quantum cryptography is only added to the initial key agreement and does not protect break-in recovery.
## Adding quantum resistance to Signal double ratchet algorithm
@@ -201,7 +201,7 @@ The reason it is released as opt-in is because once the conversation is upgraded
- enable _Show developer options_ toggle.
- now you will see _Post-quantum E2EE_ toggle - enable it as well.
Now all new contacts you add to the app will use quantum resistant Signal double ratchet algorithm.
Now all new contacts you add to the app will use quantum resistant double ratchet algorithm.
Once you have enabled it for the new contacts, you can also **enable it for some of the existing contacts**:
- open the chat with the contact you want to upgrade to be quantum resistant.
@@ -211,7 +211,7 @@ Once you have enabled it for the new contacts, you can also **enable it for some
## Next for post-quantum crypto - all direct chats, small groups and security audit
We will be making quantum resistance default for all direct chats in v5.7, and they will be upgraded for all users without any action.
We will be making quantum resistance default for all direct chats in v5.7, and we plan that all existing direct chats will be automatically upgraded when both contacts install v5.7.
We will also be adding quantum resistance to small groups up to 10-20 members. Computing cryptographic keys is much slower, in comparison, and it would be very inefficient (and completely unnecessary) for large public groups.
@@ -1,20 +1,128 @@
---
layout: layouts/article.html
title: "SimpleX network: real privacy and stable profits, non-profit protocol governance, v5.6 released with quantum resistant e2e encryption and simple profile migration"
title: "SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration"
date: 2024-03-23
# previewBody: blog_previews/20240314.html
preview: TODO
draft: true
# image: images/20240314-kem.jpg
# imageWide: true
previewBody: blog_previews/20240323.html
image: images/20240323-post-preview.png
imageBottom: true
permalink: "/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html"
---
# SimpleX network: real privacy and stable profits, non-profit protocol governance, v5.6 released with quantum resistant e2e encryption and simple profile migration
# SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration
This is a stub for release permalink
**Published:** Mar 23, 2024
TODO
SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance:
- [community and business interests are aligned](#community-and-business-interests-are-aligned).
- [the journey to the decentralized non-profit protocol governance](#the-journey-to-the-decentralized-non-profit-protocol-governance).
- [welcome, Esraa](#welcome-esraa)!
What's new in v5.6:
- [quantum resistant end-to-end encryption](#quantum-resistant-end-to-end-encryption-beta) (BETA) enable it for the new contacts.
- [use the app during the audio and video calls](#use-the-app-during-the-audio-and-video-calls).
- [migrate all app data to another device via QR code](#migrate-all-app-data-to-another-device-via-qr-code).
There are many other improvements and fixes in this release:
- group admins can block a member for all other members.
- filtering chats no longer includes muted chats with unread messages.
- and more - see the [release notes](https://github.com/simplex-chat/simplex-chat/releases/tag/v5.6.0).
## SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance
### Community and business interests are aligned
Some people in our users' community believe that there is a conflict between the business and the community interests. This view fails to see a bigger picture of how these interests are aligned. I wrote a blog post about it [here](https://www.poberezkin.com/posts/2023-10-31-why-privacy-impossible-without-venture-funding.html). Our goal is to grow the network and the ecosystem while enriching all stakeholders involved, both the community and the business shareholders. This strategy relies on the strengths and opportunities of both the business and the decentralized network we build, with its vibrant community of individuals and organizations. It positions the business not as a controller but as a supporter of the community growth, resilience and sustainability.
By leveraging access to investment resources, its ability to provide stock-based incentives beyond salaries, operating as a business will satisfy users' and community needs more effectively, ensuring the long-term viability and growth of the network, while remaining true to the mission. A purely nonprofit model with no avenue to sustain this growth is simply not possible. Sustainability requires profits, that is simply spending less than earning, as every responsible individual and organization should aim for, and these profits must be based on users' payments, and not on some other sources of revenue or funds. And building the software product that customers are willing to pay for requires substantial business investments. We will accomplish it while maintaining transparency and the commitment to keep SimpleX network and its protocols open and free to use.
### The journey to the decentralized non-profit protocol governance
At the current early stage of network growth the protocols will benefit most from the rapid evolution and centralized design. Many protocols that decentralized their evolution too early failed to evolve and to adapt to the product requirements of the majority of users, causing them to stall in growth. Even the open web became possible only thanks to Netscape, a venture funded company, that developed browsers and web server software taking it upon themselves to rapidly evolve web specification to make it into a robust application platform as we now know it. It is very unlikely that it would have happened if it was managed by a nonprofit or community, in a decentralized way.
Once SimpleX network protocols stabilize and reach the required maturity, we plan to transition its governance to several new nonprofit entities, ensuring its continued evolution aligns more closely with our vision of community-driven, independent and transparent governance. We and our investors believe that this is a win-win strategy. It both maximizes the business value, by creating a product built on protecting privacy and users' trust - a model that we see as the only viable path forward. It also maximizes the value created for the user community.
### Welcome, Esraa!
To help us deliver these goals, Esraa Al Shafei joined SimpleX Chat team - we are excited to welcome her!
Esra'a is a longtime privacy and human rights advocate from Bahrain. She is the founder of [Majal.org](http://majal.org), a network of platforms that amplify under-reported and marginalized voices in the Middle East and North Africa. She is also the co-founder of the [Numun Fund](https://numun.fund/), the first dedicated fund for women-led tech in the Global South. Esraa is currently the Vice Chair of the Board at the [Wikimedia Foundation](https://wikimediafoundation.org/), the nonprofit which hosts Wikipedia. She is also on the Board of the [Tor Project](https://www.torproject.org/), developers of one of the worlds strongest tools for privacy and freedom online. Previously, she served on the Board of [Access Now](https://www.accessnow.org/), an international nonprofit dedicated to an open and free Internet.
In her own words: *"Privacy and security have been the center of my work as a human rights advocate for the last 20 years in one of the most surveilled countries in the world where staying safe remains a burden. Our privacy is our dignity. It is something that belongs to us and something we should treasure - as people, as companies, as governments. Despite messaging being the most intimate aspect of our digital lives, the leading protocols in this space are centralized and we deserve a web that operates on the core principles of genuine privacy and security in a fully decentralized framework. SimpleX network will play a critical role towards that vision."*
Executing the plan towards decentralized nonprofit protocol governance will be Esraas primary responsibility. Our primary objective with this plan is to ensure that no single entity can control or have unrestricted ownership of the protocols, so it can maintain its integrity.
## What's new in v5.6
### Quantum resistant end-to-end encryption (BETA)
Adding post-quantum resistance to the double ratchet end-to-end encryption algorithm in SimpleX Chat is a major upgrade to messaging protocols, and it creates the security against the future quantum computers.
I wrote in detail in the previous post about various properties of end-to-end encryption and how exactly quantum resistance is implemented in SimpleX Chat - currently SimpleX Chat has [the most secure end-to-end encryption design](./20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md#how-secure-is-end-to-end-encryption-in-different-messengers) against the present and future attacks.
We plan to perform a security audit of this design and implementation later this year.
Post-quantum encryption is currently added as opt-in and has to be separately enabled to be active for the new contacts, and, additionally, can be enabled for the existing contacts.
<img src="./images/20240314-pq1.png" width="288"> <img src="./images/20240314-pq2.png" width="288"> <img src="./images/20240314-pq3.png" width="288">
**To enable quantum resistance for the new conversations**:
- open the app settings (tap user avatar in the top left corner).
- scroll down to _Developer tools_ and open them.
- enable _Show developer options_ toggle.
- now you will see _Post-quantum E2EE_ toggle - enable it as well.
Now all new contacts you add to the app will use quantum resistant Signal double ratchet algorithm.
Once you have enabled it for the new contacts, you can also **enable it for some of the existing contacts**:
- open the chat with the contact you want to upgrade to be quantum resistant.
- tap contact name above the chat.
- tap Allow PQ encryption.
- exchange several messages back and forth with that contact - the quantum resistant double ratchet will kick in after 3-5 messages (depending on how many messages you send in each direction), and you will see the notice in the chat once it enables.
We will be making quantum resistance default for all direct chats in v5.7, and we plan that all existing direct chats will be automatically upgraded when both contacts install v5.7.
### Use the app during the audio and video calls
<img src="./images/20240323-call1.png" width="220" class="float-to-left"> <img src="./images/20240323-call2.png" width="220" class="float-to-left">
Since we added the audio and video calls in 2022 a lot of work was done to improve the stability of calls. One of the usability downsides until this release was that it was impossible to use the app during the call.
This version solved this problem - now you can return back to the app without interrupting the call and use any of the app functions - for example, you can send the messages or files to the person you are in the call with. If this is a video call, you will continue seeing your contact in a small picture-in-picture view.
### Migrate all app data to another device via QR code
We always believed that cross-platform data portability is very important for any software. Users own their data, and they should have a way to export it from any software they use. So from the version of SimpleX Chat [released in July 2022](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#database-export-and-import) it was possible to export the database as a self-contained file, including all contacts, messages and files, and import it on a new device.
_"How can I migrate my data to another device?"_ was one of the most frequent user questions, but until this release this process was [quite complex](), requiring too many steps, and most non-technical users were not willing to try it, as even though it was reliable, it appeared risky.
This release made the app data migration very simple, see the steps below.
**Start the migration on the source device**
<img src="./images/20240323-migrate-from1.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-from2.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-from3.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-from4.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-from5.png" width="170">
On the device you migrate your app data from, follow these steps:
- choose _Migrate to another device_,
- if necessary, set the database passphrase (initially, the database is encrypted with a random passphrase), or, if it's already set, verify it - for security and to ensure that the archive will be usable on the new device,
- tap _Archive and upload_ to and upload the app data in chunks to the XFTP servers configured in the app,
- the app will show the QR code that should be scanned from the new device.
**Migrate data to the target device**
<img src="./images/20240323-migrate-to1.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-to2.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-to3.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-to4.png" width="170"> <img src="./images/arrow.png" width="24"> <img src="./images/20240323-migrate-to5.png" width="170">
On the device you migrate your data to, follow these steps:
- on the first app page choose _Migrate from another device_,
- scan the QR code shown on the source device,
- once the archive downloads, enter the database passphrase and tap _Open chat_,
**Finalize migration on the source device**
<img src="./images/20240323-migrate-from6.png" width="170" class="float-to-left">
When the chat starts on the target device, make sure to tap _Finalize migration_ and then _Delete database_ on the source device.
The database cannot be used on two devices at the same time, doing so would make it impossible to decrypt the received messages. This is a security measure - the break-in recovery property of double ratchet algorithm.
## SimpleX network
+14
View File
@@ -1,5 +1,19 @@
# Blog
Mar 23, 2024 [SimpleX network: real privacy and stable profits, non-profits for protocols, v5.6 released with quantum resistant e2e encryption and simple profile migration](./20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md)
SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance:
- community and business interests are aligned.
- the journey to the decentralized non-profit protocol governance.
- welcome, Esraa!
What's new in v5.6:
- quantum resistant end-to-end encryption (BETA) enable it for the new contacts.
- use the app during the audio and video calls.
- migrate all app data to another device via QR code.
---
Mar 14, 2024 [SimpleX Chat v5.6 (beta): adding quantum resistance to Signal double ratchet algorithm](./20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md)
This is a major upgrade for SimpleX Chat messaging protocol stack, I am really proud to present this work of the whole team.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

+2 -2
View File
@@ -4,10 +4,10 @@ permalink: /downloads/index.html
revision: 11.02.2024
---
| Updated 11.02.2024 | Languages: EN |
| Updated 23.03.2024 | Languages: EN |
# Download SimpleX apps
The latest stable version is v5.5.3.
The latest stable version is v5.6.
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
@@ -0,0 +1,95 @@
# Communicating rejection
## Problem
Many interactions support either approval, or silent rejection. From privacy perspective, rejection being silent is a correct default. However, for improved usability we could add optional communication of rejection, as many users don't mind sending such signals to their contacts.
Features currently not supporting communicating rejection:
- Rejecting contact request
- Rejecting group join request
- Rejecting call
- Rejecting group invitation
- TBC Other?
## Solution
## Rejection of contact / group join requests
- Contact and group join requests are very similar between each other and different from other features as they both use mechanism of "contact connections".
- Rejection can be made to the address proposed by requester in AgentInvitation message, where currently AgentConfirmation is sent in case of acceptance.
``` haskell
data AgentMsgEnvelope
= ...
| AgentRejection
{ e2eEncryption :: RcvE2ERatchetParams 'C.X448,
encRejectionInfo :: ByteString
}
```
- Unlike other AgentMsgEnvelope constructors, AgentRejection doesn't require agentVersion since connection will be deleted after sending this message.
- We may be able to re-use recently added mechanism of marking connection for deletion with deleted_at_wait_delivery field without much additional work.
- e2eEncryption will be used to encrypt first message, same as for AgentConfirmation. (?)
- Both sync and async versions of agent functions are required, as contact rejection will be user action, while group join rejection will be automated (in case, for example, if link host is no longer admin).
- Group requests non-automatic approval is a separate matter and requires UI consideration, but if it was added it would use sync function.
- For sync function either new API can be added, or rejectContact can be parameterized.
- Chat protocol requires adding new messages to be sent in encRejectionInfo, to be processed on requester side based on connections' semantics.
```haskell
-- / contact request rejection
XReject :: ChatMsgEvent 'Json
-- or
-- (contact can send reason for rejection; we don't even have welcome messages in XContact though)
XReject :: Maybe Text -> ChatMsgEvent 'Json
-- / group join request rejection
XGrpReject :: Maybe GrpRejectReason -> ChatMsgEvent 'Json
data GrpRejectReason
= GRRNone -- manual reject with no reason? use Nothing in GRRText instead?
| GRRCantInvite -- e.g., no longer admin
| GRRText {text :: Text} -- manual reject once supported? Maybe Text?
-- add similar type for contact request rejection?
-- minimal is to always send XReject without reasons
```
- As a side note, it may have been a design mistake to mix both "connInfo" messages as well as regular chat messages in a single type for protocol messages, so it may be best to keep these as separate constructors.
- Versioning considerations:
- Increase chat version.
- We already save peer chat version on contact_requests on initial REQ message. It can be used to differentiate UI whether contact supports rejection messages and not offer option to reject with notification.
- Increase agent version? Agent can prohibit sending AgentRejection based on version in AgentInvitation, though it shouldn't be reachable as chat should also prohibit it.
## Rejecting calls
- New chat protocol message is enough
- Based on user action in reply to XCallInv instead of XCallOffer.
- Send in APIRejectCall.
```haskell
XCallReject :: CallId -> ChatMsgEvent 'Json
```
- Same chat versioning considerations as above.
## Rejecting group invitation
- Same, new protocol message.
- Based on user action in reply to XGrpInv instead of XGrpAcpt.
- Can't be sent as simple chat message since there's no group ID in invitation? (only optional groupLinkId)
- So, have to send as "conn info" via join, same as for XGrpAcpt.
- APIDeleteChat is already used for deleting group invitations, can re-use. In this case `notify` parameter in APIDeleteChat can be used to send rejection.
```haskell
XGrpReject :: CallId -> ChatMsgEvent 'Json
```
- Same chat versioning considerations as above.
+8 -4
View File
@@ -104,16 +104,20 @@ executables:
- -threaded
simplex-broadcast-bot:
source-dirs: apps/simplex-broadcast-bot/src
main: ../Main.hs
source-dirs:
- apps/simplex-broadcast-bot
- apps/simplex-broadcast-bot/src
main: Main.hs
dependencies:
- simplex-chat
ghc-options:
- -threaded
simplex-directory-service:
source-dirs: apps/simplex-directory-service/src
main: ../Main.hs
source-dirs:
- apps/simplex-directory-service
- apps/simplex-directory-service/src
main: Main.hs
dependencies:
- simplex-chat
ghc-options:
+8 -6
View File
@@ -355,13 +355,14 @@ executable simplex-bot-advanced
, text >=1.2.3.0 && <1.3
executable simplex-broadcast-bot
main-is: ../Main.hs
main-is: Main.hs
hs-source-dirs:
apps/simplex-broadcast-bot
apps/simplex-broadcast-bot/src
other-modules:
Broadcast.Bot
Broadcast.Options
Paths_simplex_chat
hs-source-dirs:
apps/simplex-broadcast-bot/src
ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
build-depends:
aeson ==2.2.*
@@ -481,7 +482,10 @@ executable simplex-chat
, text >=1.2.3.0 && <1.3
executable simplex-directory-service
main-is: ../Main.hs
main-is: Main.hs
hs-source-dirs:
apps/simplex-directory-service
apps/simplex-directory-service/src
other-modules:
Directory.Events
Directory.Options
@@ -489,8 +493,6 @@ executable simplex-directory-service
Directory.Service
Directory.Store
Paths_simplex_chat
hs-source-dirs:
apps/simplex-directory-service/src
ghc-options: -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded
build-depends:
aeson ==2.2.*
+37 -17
View File
@@ -1563,19 +1563,40 @@ processChatCommand' vr = \case
processChatCommand . APISendMessage chatRef True Nothing $ ComposedMessage Nothing Nothing mc
SendMessageBroadcast msg -> withUser $ \user -> do
contacts <- withStore' $ \db -> getUserContacts db vr user
let cts = filter (\ct -> contactReady ct && contactActive ct && directOrUsed ct) contacts
ChatConfig {logLevel} <- asks config
withChatLock "sendMessageBroadcast" . procCmd $ do
(successes, failures) <- foldM (sendAndCount user logLevel) (0, 0) cts
timestamp <- liftIO getCurrentTime
pure CRBroadcastSent {user, msgContent = mc, successes, failures, timestamp}
let ctConns_ = L.nonEmpty $ foldr addContactConn [] contacts
case ctConns_ of
Nothing -> do
timestamp <- liftIO getCurrentTime
pure CRBroadcastSent {user, msgContent = mc, successes = 0, failures = 0, timestamp}
Just (ctConns :: NonEmpty (Contact, Connection)) -> do
let idsEvts = L.map ctSndEvent ctConns
sndMsgs <- createSndMessages idsEvts
let msgReqs_ :: NonEmpty (Either ChatError MsgReq) = L.zipWith (fmap . ctMsgReq) ctConns sndMsgs
(errs, ctSndMsgs :: [(Contact, SndMessage)]) <-
partitionEithers . L.toList . zipWith3' combineResults ctConns sndMsgs <$> deliverMessagesB msgReqs_
timestamp <- liftIO getCurrentTime
void $ withStoreBatch' $ \db -> map (createCI db user timestamp) ctSndMsgs
pure CRBroadcastSent {user, msgContent = mc, successes = length ctSndMsgs, failures = length errs, timestamp}
where
mc = MCText msg
sendAndCount user ll (s, f) ct =
(sendToContact user ct $> (s + 1, f)) `catchChatError` \e -> when (ll <= CLLInfo) (toView $ CRChatError (Just user) e) $> (s, f + 1)
sendToContact user ct = do
(sndMsg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing))
void $ saveSndChatItem user (CDDirectSnd ct) sndMsg (CISndMsgContent mc)
addContactConn :: Contact -> [(Contact, Connection)] -> [(Contact, Connection)]
addContactConn ct ctConns = case contactSendConn_ ct of
Right conn | directOrUsed ct -> (ct, conn) : ctConns
_ -> ctConns
ctSndEvent :: (Contact, Connection) -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json)
ctSndEvent (_, Connection {connId, pqSupport}) = (ConnectionId connId, pqSupport, XMsgNew $ MCSimple (extMsgContent mc Nothing))
ctMsgReq :: (Contact, Connection) -> SndMessage -> MsgReq
ctMsgReq (_, conn) SndMessage {msgId, msgBody} = (conn, MsgFlags {notification = hasNotification XMsgNew_}, msgBody, msgId)
zipWith3' :: (a -> b -> c -> d) -> NonEmpty a -> NonEmpty b -> NonEmpty c -> NonEmpty d
zipWith3' f ~(x :| xs) ~(y :| ys) ~(z :| zs) = f x y z :| zipWith3 f xs ys zs
combineResults :: (Contact, Connection) -> Either ChatError SndMessage -> Either ChatError (Int64, PQEncryption) -> Either ChatError (Contact, SndMessage)
combineResults (ct, _) (Right msg') (Right _) = Right (ct, msg')
combineResults _ (Left e) _ = Left e
combineResults _ _ (Left e) = Left e
createCI :: DB.Connection -> User -> UTCTime -> (Contact, SndMessage) -> IO ()
createCI db user createdAt (ct, sndMsg) =
void $ createNewSndChatItem db user (CDDirectSnd ct) sndMsg (CISndMsgContent mc) Nothing Nothing False createdAt
SendMessageQuote cName (AMsgDirection msgDir) quotedMsg msg -> withUser $ \user@User {userId} -> do
contactId <- withStore $ \db -> getContactIdByName db user cName
quotedItemId <- withStore $ \db -> getDirectChatItemIdByText db userId contactId msgDir quotedMsg
@@ -2221,7 +2242,7 @@ processChatCommand' vr = \case
summary <- case changedCts_ of
Nothing -> pure $ UserProfileUpdateSummary 0 0 []
Just changedCts -> do
let idsEvts = L.map ctSndMsg changedCts
let idsEvts = L.map ctSndEvent changedCts
msgReqs_ <- L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts
(errs, cts) <- partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_
unless (null errs) $ toView $ CRChatErrors (Just user) errs
@@ -2238,16 +2259,15 @@ processChatCommand' vr = \case
-- [incognito] filter out contacts with whom user has incognito connections
addChangedProfileContact :: User -> Contact -> [ChangedProfileContact] -> [ChangedProfileContact]
addChangedProfileContact user' ct changedCts = case contactSendConn_ ct' of
Left _ -> changedCts
Right conn
| connIncognito conn || mergedProfile' == mergedProfile -> changedCts
| otherwise -> ChangedProfileContact ct ct' mergedProfile' conn : changedCts
Right conn | not (connIncognito conn) && mergedProfile' /= mergedProfile ->
ChangedProfileContact ct ct' mergedProfile' conn : changedCts
_ -> changedCts
where
mergedProfile = userProfileToSend user Nothing (Just ct) False
ct' = updateMergedPreferences user' ct
mergedProfile' = userProfileToSend user' Nothing (Just ct') False
ctSndMsg :: ChangedProfileContact -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json)
ctSndMsg ChangedProfileContact {mergedProfile', conn = Connection {connId, pqSupport}} = (ConnectionId connId, pqSupport, XInfo mergedProfile')
ctSndEvent :: ChangedProfileContact -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json)
ctSndEvent ChangedProfileContact {mergedProfile', conn = Connection {connId, pqSupport}} = (ConnectionId connId, pqSupport, XInfo mergedProfile')
ctMsgReq :: ChangedProfileContact -> Either ChatError SndMessage -> Either ChatError MsgReq
ctMsgReq ChangedProfileContact {conn} =
fmap $ \SndMessage {msgId, msgBody} ->
+1 -1
View File
@@ -522,7 +522,7 @@ testNotApprovedBadRoles tmp =
let approve = "/approve 1:privacy 1"
superUser #> ("@SimpleX-Directory " <> approve)
superUser <# ("SimpleX-Directory> > " <> approve)
superUser <## " Group is not approved: user is not an owner."
superUser <## " Group is not approved: SimpleX-Directory is not an admin."
groupNotFound cath "privacy"
bob ##> "/mr privacy SimpleX-Directory admin"
bob <## "#privacy: you changed the role of SimpleX-Directory from member to admin"
+5
View File
@@ -2997,10 +2997,15 @@ testPQEnableContactCompression =
bob ##> "/pq @alice on"
bob <## "alice: enable quantum resistant end-to-end encryption"
PQSupportOn <- bob `pqSupportForCt` 2
threadDelay 300000
(alice, "lrg 1", v) \:#> (bob, v')
threadDelay 300000
(bob, "lrg 2", v') \:#> (alice, v')
threadDelay 300000
(alice, "lrg 3", v') \:#> (bob, v')
threadDelay 300000
(bob, "lrg 4", v') \:#> (alice, v')
threadDelay 300000
(alice, "lrg 5", v') +:#> (bob, v')
PQEncOff <- alice `pqForContact` 2
PQEncOff <- bob `pqForContact` 2
+8
View File
@@ -871,6 +871,9 @@ testGroupRemoveAdd =
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
\alice bob cath -> do
createGroup3 "team" alice bob cath
threadDelay 100000
-- remove member
alice ##> "/rm team bob"
concurrentlyN_
@@ -880,6 +883,9 @@ testGroupRemoveAdd =
bob <## "use /d #team to delete the group",
cath <## "#team: alice removed bob from the group"
]
threadDelay 100000
alice ##> "/a team bob"
alice <## "invitation to join the group #team sent to bob"
bob <## "#team_1: alice invites you to join the group as member"
@@ -1916,6 +1922,8 @@ testGroupLink =
(alice <# "#team cath> hey team")
(bob <# "#team cath> hey team")
threadDelay 100000
-- leaving team removes link
alice ##> "/l team"
concurrentlyN_
@@ -0,0 +1,16 @@
<p><strong>SimpleX network: deliver real privacy via a profitable business and non-profit protocol governance:</strong></p>
<ul class="mb-[12px]">
<li>community and business interests are aligned.</li>
<li>the journey to the decentralized non-profit protocol governance.</li>
<li>welcome, Esra&apos;a!</li>
</ul>
<p><strong>v5.6 is released:</strong></p>
<ul class="mb-[12px]">
<li>quantum resistant end-to-end encryption (BETA) - enable it for the new contacts.</li>
<li>use the app during the audio and video calls.</li>
<li>migrate all app data to another device via QR code.</li>
<li>blocking members and many other improvements.</li>
</ul>