From 09f53cad05986efaa8b566e01fa5594c3ca15b07 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Wed, 4 Feb 2026 11:10:35 +0100 Subject: [PATCH 1/4] feat(ffi): expose `e2ei_conversation_state()` on `CoreCrypto` [WPB-22282] --- crypto-ffi/src/core_crypto/e2ei/mod.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crypto-ffi/src/core_crypto/e2ei/mod.rs b/crypto-ffi/src/core_crypto/e2ei/mod.rs index b7dc06ea3f..233d02096c 100644 --- a/crypto-ffi/src/core_crypto/e2ei/mod.rs +++ b/crypto-ffi/src/core_crypto/e2ei/mod.rs @@ -1,6 +1,6 @@ -use core_crypto::RecursiveError; +use core_crypto::{RecursiveError, mls::conversation::Conversation as _}; -use crate::{Ciphersuite, CoreCryptoFfi, CoreCryptoResult}; +use crate::{Ciphersuite, ConversationId, CoreCryptoFfi, CoreCryptoResult, E2eiConversationState}; pub(crate) mod identities; @@ -26,4 +26,21 @@ impl CoreCryptoFfi { .map_err(RecursiveError::mls_client("checking if e2ei is enabled")) .map_err(Into::into) } + + /// See [core_crypto::mls::conversation::Conversation::e2ei_conversation_state] + pub async fn e2ei_conversation_state( + &self, + conversation_id: &ConversationId, + ) -> CoreCryptoResult { + self.inner + .mls_session() + .await? + .get_raw_conversation(conversation_id.as_ref()) + .await + .map_err(RecursiveError::mls_client("getting conversation by id"))? + .e2ei_conversation_state() + .await + .map(Into::into) + .map_err(Into::into) + } } From 86d9767f983aaab4c6b03e45820aa63823725e17 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Wed, 4 Feb 2026 10:16:19 +0100 Subject: [PATCH 2/4] feat(kotlin): expose entire `CoreCryptoFfiInterface` API via `CoreCrypto` [WPB-22282] --- .../main/kotlin/com/wire/crypto/CoreCrypto.kt | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/CoreCrypto.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/CoreCrypto.kt index eb55545ac8..28f4aed597 100644 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/CoreCrypto.kt +++ b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/CoreCrypto.kt @@ -17,7 +17,7 @@ fun CoreCryptoFfi.lift() = CoreCrypto(this) * This wrapper should be largely transparent to end users. It exists to improve the * callback interfaces: `.transaction(...)`, `.registerFooObserver(...)`, etc. */ -class CoreCrypto(private val cc: CoreCryptoFfi) { +class CoreCrypto(private val cc: CoreCryptoFfi) : CoreCryptoFfiInterface by cc { companion object { /** Opens a core crypto client with the specified database, previously instantiated via [openDatabase]. */ operator fun invoke( @@ -128,26 +128,6 @@ class CoreCrypto(private val cc: CoreCryptoFfi) { return cc.registerHistoryObserver(observerIndirector) } - /** - * See [CoreCryptoContext.isHistorySharingEnabled] - * - * @param conversationId conversation identifier - * @return true if history sharing is enabled - */ - suspend fun isHistorySharingEnabled(conversationId: ConversationId): Boolean = cc.isHistorySharingEnabled(conversationId) - - /** - * Set the PkiEnvironment of the CoreCrypto instance - * @param pkiEnvironment the pki environment to set - */ - suspend fun setPkiEnvironment(pkiEnvironment: PkiEnvironment?) = cc.setPkiEnvironment(pkiEnvironment) - - /** - * Get the Pki Environment of the CoreCrypto instance - * @return the pki environment or null if not set - */ - suspend fun getPkiEnvironment(): PkiEnvironment? = cc.getPkiEnvironment() - /** * Closes this [CoreCrypto] instance and deallocates all loaded resources. * From e381eb9a30f396afdf7eac248790634945267acd Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Wed, 4 Feb 2026 10:23:53 +0100 Subject: [PATCH 3/4] test(kotlin): up-to-date conversation data can be accessed without new transaction --- .../test/kotlin/com/wire/crypto/MLSTest.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/MLSTest.kt b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/MLSTest.kt index 7008d215ce..912afeef85 100644 --- a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/MLSTest.kt +++ b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/MLSTest.kt @@ -411,6 +411,42 @@ class MLSTest : HasMockDeliveryService() { } } + @Test + fun epochObserverEvent_shouldAllowReadingData(): TestResult { + val scope = TestScope() + return scope.runTest { + val (alice) = newClients(this@MLSTest, genClientId()) + + data class ObserverEvent(val eventEpoch: ULong, val conversationEpoch: ULong) + + class Observer : EpochObserver { + val observedEvents = emptyList().toMutableList() + + override suspend fun epochChanged(conversationId: ConversationId, epoch: ULong) { + val conversationEpoch = alice.conversationEpoch(conversationId) + observedEvents.add(ObserverEvent(epoch, conversationEpoch)) + } + } + + val aliceObserver = Observer() + + alice.transaction { it.createConversationShort(id) } + val initialEpoch = alice.conversationEpoch(id) + + alice.registerEpochObserver(scope, aliceObserver) + + alice.transaction { it.updateKeyingMaterial(id) } + val laterEpoch = alice.conversationEpoch(id) + + assertEquals(initialEpoch + 1U, laterEpoch) + assertEquals(1, aliceObserver.observedEvents.size, "we triggered exactly 1 epoch change and must have observed that") + assertTrue( + aliceObserver.observedEvents.all { it.eventEpoch == it.conversationEpoch && it.eventEpoch == laterEpoch }, + "event epoch must equal the epoch read during the event" + ) + } + } + @Test fun registerHistoryObserver_should_notify_observer_on_new_secret(): TestResult { val scope = TestScope() From 1414a41fe443d0824cc66813fc3eb4893489f983 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Wed, 4 Feb 2026 10:53:29 +0100 Subject: [PATCH 4/4] docs: update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3736d2c3b9..a16fc52d04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Features +- expose enitre read-only API in Kotlin on the `CoreCrypto` type. This allows reading data without opening a + transaction. + - replaced `CoreCrypto.init(database: Database)` with class constructor `new CoreCrypto(database: Database)` Affected platforms: web