refactor: Migrate olm account to vodozemac

This commit is contained in:
Christian Kußowski 2025-05-30 14:22:53 +02:00
parent 98fcd683a6
commit 5fdcbf8006
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
13 changed files with 153 additions and 151 deletions

View File

@ -19,7 +19,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/cross_signing.dart';
import 'package:matrix/encryption/key_manager.dart';
@ -87,18 +87,6 @@ class Encryption {
if (!isDehydratedDevice) keyManager.startAutoUploadKeys();
}
bool isMinOlmVersion(int major, int minor, int patch) {
try {
final version = olm.get_library_version();
return version[0] > major ||
(version[0] == major &&
(version[1] > minor ||
(version[1] == minor && version[2] >= patch)));
} catch (_) {
return false;
}
}
Bootstrap bootstrap({void Function(Bootstrap)? onUpdate}) => Bootstrap(
encryption: this,
onUpdate: onUpdate,

View File

@ -21,11 +21,12 @@ import 'dart:convert';
import 'package:async/async.dart';
import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/encryption/utils/olm_session.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
import 'package:matrix/src/utils/run_benchmarked.dart';
@ -34,20 +35,24 @@ import 'package:matrix/src/utils/run_in_root.dart';
class OlmManager {
final Encryption encryption;
Client get client => encryption.client;
olm.Account? _olmAccount;
vod.Account? _olmAccount;
String? ourDeviceId;
/// Returns the base64 encoded keys to store them in a store.
/// This String should **never** leave the device!
String? get pickledOlmAccount =>
enabled ? _olmAccount!.pickle(client.userID!) : null;
String? get pickledOlmAccount {
return enabled
? _olmAccount!.toPickleEncrypted(client.userID!.toPickleKey())
: null;
}
String? get fingerprintKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null;
enabled ? _olmAccount!.identityKeys.ed25519.toBase64() : null;
String? get identityKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['curve25519'] : null;
enabled ? _olmAccount!.identityKeys.curve25519.toBase64() : null;
String? pickleOlmAccountWithKey(String key) =>
enabled ? _olmAccount!.pickle(key) : null;
enabled ? _olmAccount!.toPickleEncrypted(key.toPickleKey()) : null;
bool get enabled => _olmAccount != null;
@ -66,33 +71,31 @@ class OlmManager {
}) async {
ourDeviceId = deviceId;
if (olmAccount == null) {
try {
await olm.init();
_olmAccount = olm.Account();
_olmAccount!.create();
if (!await uploadKeys(
uploadDeviceKeys: true,
updateDatabase: false,
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
dehydratedDevicePickleKey:
dehydratedDeviceAlgorithm != null ? pickleKey : null,
)) {
throw ('Upload key failed');
}
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
rethrow;
_olmAccount = vod.Account();
if (!await uploadKeys(
uploadDeviceKeys: true,
updateDatabase: false,
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
dehydratedDevicePickleKey:
dehydratedDeviceAlgorithm != null ? pickleKey : null,
)) {
throw ('Upload key failed');
}
} else {
try {
await olm.init();
_olmAccount = olm.Account();
_olmAccount!.unpickle(pickleKey ?? client.userID!, olmAccount);
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
rethrow;
_olmAccount = vod.Account.fromPickleEncrypted(
pickle: olmAccount,
pickleKey: (pickleKey ?? client.userID!).toPickleKey(),
);
} catch (e) {
Logs().d(
'Unable to unpickle account in vodozemac format. Trying Olm format...',
e,
);
_olmAccount = vod.Account.fromOlmPickleEncrypted(
pickle: olmAccount,
pickleKey: utf8.encode(pickleKey ?? client.userID!),
);
}
}
}
@ -115,7 +118,8 @@ class OlmManager {
if (!payload['signatures'].containsKey(client.userID)) {
payload['signatures'][client.userID] = <String, dynamic>{};
}
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = signature;
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] =
signature.toBase64();
if (unsigned != null) {
payload['unsigned'] = unsigned;
}
@ -123,13 +127,13 @@ class OlmManager {
}
String signString(String s) {
return _olmAccount!.sign(s);
return _olmAccount!.sign(s).toBase64();
}
bool _uploadKeysLock = false;
CancelableOperation<Map<String, int>>? currentUpload;
int? get maxNumberOfOneTimeKeys => _olmAccount?.max_number_of_one_time_keys();
int? get maxNumberOfOneTimeKeys => _olmAccount?.maxNumberOfOneTimeKeys;
/// Generates new one time keys, signs everything and upload it to the server.
/// If `retry` is > 0, the request will be retried with new OTKs on upload failure.
@ -158,26 +162,24 @@ class OlmManager {
if (oldKeyCount != null) {
// check if we have OTKs that still need uploading. If we do, we don't try to generate new ones,
// instead we try to upload the old ones first
final oldOTKsNeedingUpload = json
.decode(olmAccount.one_time_keys())['curve25519']
.entries
.length as int;
final oldOTKsNeedingUpload = olmAccount.oneTimeKeys.length;
// generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can
// still be used
final oneTimeKeysCount =
(olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
(olmAccount.maxNumberOfOneTimeKeys * 2 / 3).floor() -
oldKeyCount -
oldOTKsNeedingUpload;
if (oneTimeKeysCount > 0) {
olmAccount.generate_one_time_keys(oneTimeKeysCount);
olmAccount.generateOneTimeKeys(oneTimeKeysCount);
}
uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload;
}
if (encryption.isMinOlmVersion(3, 2, 7) && unusedFallbackKey == false) {
if (unusedFallbackKey == false) {
// we don't have an unused fallback key uploaded....so let's change that!
olmAccount.generate_fallback_key();
olmAccount.generateFallbackKey();
}
// we save the generated OTKs into the database.
@ -199,38 +201,32 @@ class OlmManager {
};
if (uploadDeviceKeys) {
final Map<String, dynamic> keys =
json.decode(olmAccount.identity_keys());
for (final entry in keys.entries) {
final algorithm = entry.key;
final value = entry.value;
deviceKeys['keys']['$algorithm:$ourDeviceId'] = value;
}
final keys = olmAccount.identityKeys;
deviceKeys['keys']['curve25519:$ourDeviceId'] =
keys.curve25519.toBase64();
deviceKeys['keys']['ed25519:$ourDeviceId'] = keys.ed25519.toBase64();
deviceKeys = signJson(deviceKeys);
}
// now sign all the one-time keys
for (final entry
in json.decode(olmAccount.one_time_keys())['curve25519'].entries) {
for (final entry in olmAccount.oneTimeKeys.entries) {
final key = entry.key;
final value = entry.value;
final value = entry.value.toBase64();
signedOneTimeKeys['signed_curve25519:$key'] = signJson({
'key': value,
});
}
final signedFallbackKeys = <String, dynamic>{};
if (encryption.isMinOlmVersion(3, 2, 7)) {
final fallbackKey = json.decode(olmAccount.unpublished_fallback_key());
// now sign all the fallback keys
for (final entry in fallbackKey['curve25519'].entries) {
final key = entry.key;
final value = entry.value;
signedFallbackKeys['signed_curve25519:$key'] = signJson({
'key': value,
'fallback': true,
});
}
final fallbackKey = olmAccount.fallbackKey;
// now sign all the fallback keys
for (final entry in fallbackKey.entries) {
final key = entry.key;
final value = entry.value.toBase64();
signedFallbackKeys['signed_curve25519:$key'] = signJson({
'key': value,
'fallback': true,
});
}
if (signedFallbackKeys.isEmpty &&
@ -281,7 +277,7 @@ class OlmManager {
}
// mark the OTKs as published and save that to datbase
olmAccount.mark_keys_as_published();
olmAccount.markKeysAsPublished();
if (updateDatabase) {
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
}
@ -301,17 +297,14 @@ class OlmManager {
Logs().w('Rotating otks because upload failed', exception);
for (final otk in signedOneTimeKeys.values) {
// Keys can only be removed by creating a session...
final session = olm.Session();
try {
final String identity =
json.decode(olmAccount.identity_keys())['curve25519'];
final key = otk.tryGet<String>('key');
if (key != null) {
session.create_outbound(_olmAccount!, identity, key);
olmAccount.remove_one_time_keys(session);
}
} finally {
session.free();
final identity = olmAccount.identityKeys.curve25519.toBase64();
final key = otk.tryGet<String>('key');
if (key != null) {
olmAccount.createOutboundSession(
identityKey: vod.Curve25519PublicKey.fromBase64(identity),
oneTimeKey: vod.Curve25519PublicKey.fromBase64(key),
);
}
}
@ -342,7 +335,6 @@ class OlmManager {
await _otkUpdateDedup.fetch(
() => runBenchmarked('handleOtkUpdate', () async {
final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0);
// Check if there are at least half of max_number_of_one_time_keys left on the server
// and generate and upload more if not.
@ -357,7 +349,7 @@ class OlmManager {
}
// fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway.
if (keyCount > _olmAccount!.max_number_of_one_time_keys()) {
if (keyCount > _olmAccount!.maxNumberOfOneTimeKeys) {
final requestingKeysFrom = {
client.userID!: {ourDeviceId!: 'signed_curve25519'},
};
@ -365,14 +357,13 @@ class OlmManager {
}
// Only upload keys if they are less than half of the max or we have no unused fallback key
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
if (keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2) ||
!unusedFallbackKey) {
await uploadKeys(
oldKeyCount:
keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
? keyCount
: null,
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null,
oldKeyCount: keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2)
? keyCount
: null,
unusedFallbackKey: unusedFallbackKey,
);
}
}),
@ -449,9 +440,12 @@ class OlmManager {
if (session.session == null) {
continue;
}
if (type == 0 && session.session!.matches_inbound(body)) {
if (type == 0) {
try {
plaintext = session.session!.decrypt(type, body);
plaintext = session.session!.decrypt(
messageType: type,
ciphertext: body,
);
} catch (e) {
// The message was encrypted during this session, but is unable to decrypt
throw DecryptException(
@ -463,7 +457,10 @@ class OlmManager {
break;
} else if (type == 1) {
try {
plaintext = session.session!.decrypt(type, body);
plaintext = session.session!.decrypt(
messageType: type,
ciphertext: body,
);
await updateSessionUsage(session);
break;
} catch (_) {
@ -477,26 +474,27 @@ class OlmManager {
}
if (plaintext == null) {
final newSession = olm.Session();
try {
newSession.create_inbound_from(_olmAccount!, senderKey, body);
_olmAccount!.remove_one_time_keys(newSession);
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
final result = _olmAccount!.createInboundSession(
theirIdentityKey: vod.Curve25519PublicKey.fromBase64(senderKey),
preKeyMessageBase64: body,
);
plaintext = result.plaintext;
final newSession = result.session;
plaintext = newSession.decrypt(type, body);
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
await storeOlmSession(
OlmSession(
key: client.userID!,
identityKey: senderKey,
sessionId: newSession.session_id(),
sessionId: newSession.sessionId,
session: newSession,
lastReceived: DateTime.now(),
),
);
await updateSessionUsage();
} catch (e) {
newSession.free();
throw DecryptException(DecryptException.decryptionFailed, e.toString());
}
}
@ -660,25 +658,25 @@ class OlmManager {
continue;
}
Logs().v('[OlmManager] Starting session with $userId:$deviceId');
final session = olm.Session();
try {
session.create_outbound(
_olmAccount!,
identityKey,
deviceKey.tryGet<String>('key')!,
final session = _olmAccount!.createOutboundSession(
identityKey: vod.Curve25519PublicKey.fromBase64(identityKey),
oneTimeKey: vod.Curve25519PublicKey.fromBase64(
deviceKey.tryGet<String>('key')!,
),
);
await storeOlmSession(
OlmSession(
key: client.userID!,
identityKey: identityKey,
sessionId: session.session_id(),
sessionId: session.sessionId,
session: session,
lastReceived:
DateTime.now(), // we want to use a newly created session
),
);
} catch (e, s) {
session.free();
Logs()
.e('[LibOlm] Could not create new outbound olm session', e, s);
}
@ -733,8 +731,8 @@ class OlmManager {
'ciphertext': <String, dynamic>{},
};
encryptedBody['ciphertext'][device.curve25519Key] = {
'type': encryptResult.type,
'body': encryptResult.body,
'type': encryptResult.messageType,
'body': encryptResult.ciphertext,
};
return encryptedBody;
}
@ -820,13 +818,6 @@ class OlmManager {
Future<void> dispose() async {
await currentUpload?.cancel();
for (final sessions in olmSessions.values) {
for (final sess in sessions) {
sess.dispose();
}
}
_olmAccount?.free();
_olmAccount = null;
}
}

View File

@ -16,17 +16,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'package:olm/olm.dart' as olm;
import 'dart:convert';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart';
class OlmSession {
String identityKey;
String? sessionId;
olm.Session? session;
vod.Session? session;
DateTime? lastReceived;
final String key;
String? get pickledSession => session?.pickle(key);
String? get pickledSession => session?.toPickleEncrypted(key.toPickleKey());
bool get isValid => session != null;
@ -40,21 +43,24 @@ class OlmSession {
OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key)
: identityKey = dbEntry['identity_key'] ?? '' {
session = olm.Session();
try {
session!.unpickle(key, dbEntry['pickle']);
try {
session = vod.Session.fromPickleEncrypted(
pickleKey: key.toPickleKey(),
pickle: dbEntry['pickle'],
);
} catch (_) {
session = vod.Session.fromOlmPickleEncrypted(
pickleKey: utf8.encode(key),
pickle: dbEntry['pickle'],
);
}
sessionId = dbEntry['session_id'];
lastReceived =
DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0);
assert(sessionId == session!.session_id());
assert(sessionId == session!.sessionId);
} catch (e, s) {
Logs().e('[LibOlm] Could not unpickle olm session', e, s);
dispose();
}
}
void dispose() {
session?.free();
session = null;
}
}

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:http/http.dart';

View File

@ -26,7 +26,6 @@ import 'package:async/async.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:http/http.dart' as http;
import 'package:mime/mime.dart';
import 'package:olm/olm.dart' as olm;
import 'package:random_string/random_string.dart';
import 'package:matrix/encryption.dart';
@ -2102,9 +2101,6 @@ class Client extends MatrixApi {
await encryption?.dispose();
try {
// make sure to throw an exception if libolm doesn't exist
await olm.init();
olm.get_library_version();
_encryption = Encryption(client: this);
} catch (e) {
Logs().e('Error initializing encryption $e');

View File

@ -26,7 +26,6 @@ import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:path/path.dart' show join;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/client_init_exception.dart';
@ -36,7 +35,7 @@ import 'fake_database.dart';
void main() {
// key @test:fakeServer.notExisting
const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk';
const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo';
@ -1752,7 +1751,10 @@ void main() {
expect(error.homeserver, Uri.parse('https://test.server'));
expect(error.olmAccount, 'abcd');
expect(error.userId, '@user:server');
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY');
expect(
error.originalException.runtimeType.toString(),
'AnyhowException',
);
}
await customClient.dispose(closeDatabase: true);
},

View File

@ -25,10 +25,8 @@ import '../fake_client.dart';
import '../fake_database.dart';
void main() async {
// key @othertest:fakeServer.notExisting
const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
final database = await getDatabase();
group('Encrypt/Decrypt to-device messages', tags: 'olm', () {
Logs().level = Level.error;
@ -64,7 +62,6 @@ void main() async {
newHomeserver: otherClient.homeserver,
newDeviceName: 'Text Matrix Client',
newDeviceID: 'FOXDEVICE',
newOlmAccount: otherPickledOlmAccount,
);
await otherClient.abortSync();

View File

@ -53,7 +53,7 @@ void main() async {
// key @othertest:fakeServer.notExisting
const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
'0aFMkSgJhj0kVLxVnactRpl3L2kgIR8bAqICFtDkvp/mkinITZjr1Vh6Jy9FmJzvhLfFUtjU2j/2bqrFn61CSrvRbRaLP6rCFegGJHNGpVfw+c24NthCwGF/SN10aPjPo6yQ3er9bc42I6AmJz5HgyfU6C4bE+LdWrML93C0iEnmQN/SYHnS1KHPXNl6NpFGITggbZQ9jwHOFILWo8wzJ4iqlJtMrNaOOLAAB7By7Fbxl4xoNz2K+w';
late Client client1;
late Client client2;

View File

@ -67,7 +67,7 @@ void main() {
);
expect(sent['device_keys'] != null, true);
expect(sent['one_time_keys'] != null, true);
expect(sent['one_time_keys'].keys.length, 66);
expect(sent['one_time_keys'].keys.length, 33);
expect(sent['fallback_keys'] != null, true);
expect(sent['fallback_keys'].keys.length, 1);
FakeMatrixApi.calledEndpoints.clear();
@ -83,7 +83,7 @@ void main() {
sent = json.decode(
FakeMatrixApi.calledEndpoints['/client/v3/keys/upload']!.first,
);
expect(sent['one_time_keys'].keys.length, 46);
expect(sent['one_time_keys'].keys.length, 13);
expect(sent['fallback_keys'].keys.length, 0);
});

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -276,6 +277,15 @@ void main() async {
});
test('sendAgain', () async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
final matrix = Client(
'testclient',
httpClient: FakeMatrixApi(),

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'package:matrix/matrix.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'fake_database.dart';
const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O';
@ -25,7 +26,7 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH';
// key @test:fakeServer.notExisting
const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
Future? vodInit;

View File

@ -22,7 +22,6 @@ import 'dart:math';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'fake_client.dart';

View File

@ -17,6 +17,7 @@
*/
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'fake_database.dart';
@ -27,7 +28,17 @@ void main() async {
late Client client;
late Room room;
late User user1, user2;
Future? vodInit;
setUp(() async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
client = Client(
'testclient',
httpClient: FakeMatrixApi(),