in theory sign others keys
This commit is contained in:
parent
97a10c7de1
commit
c13f66c85f
|
@ -48,6 +48,7 @@ import 'package:pedantic/pedantic.dart';
|
|||
import 'event.dart';
|
||||
import 'room.dart';
|
||||
import 'ssss.dart';
|
||||
import 'cross_signing.dart';
|
||||
import 'sync/event_update.dart';
|
||||
import 'sync/room_update.dart';
|
||||
import 'sync/user_update.dart';
|
||||
|
@ -81,6 +82,7 @@ class Client {
|
|||
bool enableE2eeRecovery;
|
||||
|
||||
SSSS ssss;
|
||||
CrossSigning crossSigning;
|
||||
|
||||
/// Create a client
|
||||
/// clientName = unique identifier of this client
|
||||
|
@ -90,6 +92,7 @@ class Client {
|
|||
Client(this.clientName,
|
||||
{this.debug = false, this.database, this.enableE2eeRecovery = false}) {
|
||||
ssss = SSSS(this);
|
||||
crossSigning = CrossSigning(this);
|
||||
onLoginStateChanged.stream.listen((loginState) {
|
||||
print('LoginState: ${loginState.toString()}');
|
||||
});
|
||||
|
@ -1845,6 +1848,11 @@ class Client {
|
|||
return payload;
|
||||
}
|
||||
|
||||
/// Just gets the signature of a string
|
||||
String signString(String s) {
|
||||
return _olmAccount.sign(s);
|
||||
}
|
||||
|
||||
/// Checks the signature of a signed json object.
|
||||
bool checkJsonSignature(String key, Map<String, dynamic> signedJson,
|
||||
String userId, String deviceId) {
|
||||
|
|
126
lib/src/cross_signing.dart
Normal file
126
lib/src/cross_signing.dart
Normal file
|
@ -0,0 +1,126 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import 'client.dart';
|
||||
import 'utils/device_keys_list.dart';
|
||||
|
||||
const SELF_SIGNING_KEY = 'm.cross_signing.self_signing';
|
||||
const USER_SIGNING_KEY = 'm.cross_signing.user_signing';
|
||||
const MASTER_KEY = 'm.cross_signing.master';
|
||||
|
||||
class CrossSigning {
|
||||
final Client client;
|
||||
CrossSigning(this.client);
|
||||
|
||||
bool get enabled =>
|
||||
client.accountData[SELF_SIGNING_KEY] != null &&
|
||||
client.accountData[USER_SIGNING_KEY] != null &&
|
||||
client.accountData[MASTER_KEY] != null;
|
||||
|
||||
Future<bool> isCached() async {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
return (await client.ssss.getCached(SELF_SIGNING_KEY)) != null &&
|
||||
(await client.ssss.getCached(USER_SIGNING_KEY)) != null;
|
||||
}
|
||||
|
||||
bool signable(List<SignedKey> keys) {
|
||||
for (final key in keys) {
|
||||
if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||
return true;
|
||||
}
|
||||
if (key.userId == client.userID &&
|
||||
(key is DeviceKeys) &&
|
||||
key.identifier != client.deviceID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> sign(List<SignedKey> keys) async {
|
||||
Uint8List selfSigningKey;
|
||||
Uint8List userSigningKey;
|
||||
final signatures = <String, dynamic>{};
|
||||
var signedKey = false;
|
||||
final addSignature =
|
||||
(SignedKey key, SignedKey signedWith, String signature) {
|
||||
if (key == null || signedWith == null || signature == null) {
|
||||
return;
|
||||
}
|
||||
if (!signatures.containsKey(key.userId)) {
|
||||
signatures[key.userId] = <String, dynamic>{};
|
||||
}
|
||||
if (!signatures[key.userId].containsKey(key.identifier)) {
|
||||
signatures[key.userId][key.identifier] = key.toJson();
|
||||
}
|
||||
if (!signatures[key.userId][key.identifier].containsKey('signatures')) {
|
||||
signatures[key.userId][key.identifier]
|
||||
['signatures'] = <String, dynamic>{};
|
||||
}
|
||||
if (!signatures[key.userId][key.identifier]['signatures']
|
||||
.containsKey(signedWith.userId)) {
|
||||
signatures[key.userId][key.identifier]['signatures']
|
||||
[signedWith.userId] = <String, dynamic>{};
|
||||
}
|
||||
signatures[key.userId][key.identifier]['signatures'][signedWith.userId]
|
||||
[signedWith.identifier] = signature;
|
||||
signedKey = true;
|
||||
};
|
||||
for (final key in keys) {
|
||||
if (key.userId == client.userID) {
|
||||
// we are singing a key of ourself
|
||||
if (key is CrossSigningKey) {
|
||||
if (key.usage.contains('master')) {
|
||||
// okay, we'll sign our own master key
|
||||
final signature = client.signString(key.signingContent);
|
||||
addSignature(
|
||||
key,
|
||||
client.userDeviceKeys[client.userID].deviceKeys[client.deviceID],
|
||||
signature);
|
||||
}
|
||||
// we don't care about signing other cross-signing keys
|
||||
} else if (key.identifier != client.deviceID) {
|
||||
// okay, we'll sign a device key with our self signing key
|
||||
selfSigningKey ??=
|
||||
base64.decode(await client.ssss.getCached(SELF_SIGNING_KEY) ?? '');
|
||||
if (selfSigningKey != null) {
|
||||
final signature = _sign(key.signingContent, selfSigningKey);
|
||||
addSignature(key, client.userDeviceKeys[client.userID].selfSigningKey,
|
||||
signature);
|
||||
}
|
||||
}
|
||||
} else if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||
// we are signing someone elses master key
|
||||
userSigningKey ??=
|
||||
base64.decode(await client.ssss.getCached(USER_SIGNING_KEY) ?? '');
|
||||
if (userSigningKey != null) {
|
||||
final signature = _sign(key.signingContent, userSigningKey);
|
||||
addSignature(
|
||||
key, client.userDeviceKeys[client.userID].userSigningKey, signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (signedKey) {
|
||||
// post our new keys!
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: '/client/r0/keys/signatures/upload',
|
||||
data: signatures,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _sign(String canonicalJson, Uint8List key) {
|
||||
final keyObj = olm.PkSigning();
|
||||
keyObj.init_with_seed(key);
|
||||
try {
|
||||
return keyObj.sign(canonicalJson);
|
||||
} finally {
|
||||
keyObj.free();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import "package:base58check/base58.dart";
|
||||
import 'package:base58check/base58.dart';
|
||||
import 'package:password_hash/password_hash.dart';
|
||||
import 'package:random_string/random_string.dart';
|
||||
|
||||
|
@ -21,7 +21,7 @@ const ZERO_STR =
|
|||
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00';
|
||||
const BASE58_ALPHABET =
|
||||
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||
const base58 = const Base58Codec(BASE58_ALPHABET);
|
||||
const base58 = Base58Codec(BASE58_ALPHABET);
|
||||
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
|
||||
const OLM_PRIVATE_KEY_LENGTH = 32; // TODO: fetch from dart-olm
|
||||
const AES_BLOCKSIZE = 16;
|
||||
|
@ -38,7 +38,6 @@ class SSSS {
|
|||
b[0] = 1;
|
||||
final aesKey = Hmac(sha256, prk.bytes).convert(utf8.encode(name) + b);
|
||||
b[0] = 2;
|
||||
final a = aesKey.bytes + utf8.encode(name) + b;
|
||||
final hmacKey =
|
||||
Hmac(sha256, prk.bytes).convert(aesKey.bytes + utf8.encode(name) + b);
|
||||
return _DerivedKeys(aesKey: aesKey.bytes, hmacKey: hmacKey.bytes);
|
||||
|
@ -239,6 +238,15 @@ class SSSS {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> maybeCacheAll(String keyId, Uint8List key) async {
|
||||
for (final type in CACHE_TYPES) {
|
||||
final secret = await getCached(type);
|
||||
if (secret == null) {
|
||||
await getStored(type, keyId, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> maybeRequestAll(List<DeviceKeys> devices) async {
|
||||
for (final type in CACHE_TYPES) {
|
||||
final secret = await getCached(type);
|
||||
|
@ -347,8 +355,7 @@ class SSSS {
|
|||
return null;
|
||||
}
|
||||
if (data.content['encrypted'] is Map) {
|
||||
final keys = Set<String>();
|
||||
String maybeKey;
|
||||
final Set keys = <String>{};
|
||||
for (final key in data.content['encrypted'].keys) {
|
||||
keys.add(key);
|
||||
}
|
||||
|
@ -369,9 +376,7 @@ class SSSS {
|
|||
}
|
||||
|
||||
OpenSSSS open([String identifier]) {
|
||||
if (identifier == null) {
|
||||
identifier = defaultKeyId;
|
||||
}
|
||||
identifier ??= defaultKeyId;
|
||||
if (identifier == null) {
|
||||
throw 'Dont know what to open';
|
||||
}
|
||||
|
@ -454,4 +459,8 @@ class OpenSSSS {
|
|||
Future<String> getStored(String type) async {
|
||||
return await ssss.getStored(type, keyId, privateKey);
|
||||
}
|
||||
|
||||
Future<void> maybeCacheAll() async {
|
||||
await ssss.maybeCacheAll(keyId, privateKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,28 @@ class DeviceKeysList {
|
|||
Map<String, DeviceKeys> deviceKeys = {};
|
||||
Map<String, CrossSigningKey> crossSigningKeys = {};
|
||||
|
||||
SignedKey getKey(String id) {
|
||||
if (deviceKeys.containsKey(id)) {
|
||||
return deviceKeys[id];
|
||||
}
|
||||
if (crossSigningKeys.containsKey(id)) {
|
||||
return crossSigningKeys[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
CrossSigningKey getCrossSigningKey(String type) {
|
||||
final keys = crossSigningKeys.values.where((k) => k.usage.contains(type));
|
||||
if (keys.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return keys.first;
|
||||
}
|
||||
|
||||
CrossSigningKey get masterKey => getCrossSigningKey('master');
|
||||
CrossSigningKey get selfSigningKey => getCrossSigningKey('self_signing');
|
||||
CrossSigningKey get userSigningKey => getCrossSigningKey('user_signing');
|
||||
|
||||
DeviceKeysList.fromDb(
|
||||
DbUserDeviceKey dbEntry,
|
||||
List<DbUserDeviceKeysKey> childEntries,
|
||||
|
@ -73,7 +95,7 @@ class DeviceKeysList {
|
|||
DeviceKeysList(this.userId);
|
||||
}
|
||||
|
||||
abstract class _SignedKey {
|
||||
abstract class SignedKey {
|
||||
Client client;
|
||||
String userId;
|
||||
String identifier;
|
||||
|
@ -106,7 +128,7 @@ abstract class _SignedKey {
|
|||
}
|
||||
}
|
||||
|
||||
String _getSigningContent() {
|
||||
String get signingContent {
|
||||
final data = Map<String, dynamic>.from(content);
|
||||
// some old data might have the custom verified and blocked keys
|
||||
data.remove('verified');
|
||||
|
@ -121,7 +143,7 @@ abstract class _SignedKey {
|
|||
final olmutil = olm.Utility();
|
||||
var valid = false;
|
||||
try {
|
||||
olmutil.ed25519_verify(pubKey, _getSigningContent(), signature);
|
||||
olmutil.ed25519_verify(pubKey, signingContent, signature);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
// bad signature
|
||||
|
@ -152,7 +174,7 @@ abstract class _SignedKey {
|
|||
continue;
|
||||
}
|
||||
final keyId = fullKeyId.substring('ed25519:'.length);
|
||||
_SignedKey key;
|
||||
SignedKey key;
|
||||
if (client.userDeviceKeys[otherUserId].deviceKeys.containsKey(keyId)) {
|
||||
key = client.userDeviceKeys[otherUserId].deviceKeys[keyId];
|
||||
} else if (client.userDeviceKeys[otherUserId].crossSigningKeys
|
||||
|
@ -204,6 +226,10 @@ abstract class _SignedKey {
|
|||
return false;
|
||||
}
|
||||
|
||||
Future<void> setVerified(bool newVerified);
|
||||
|
||||
Future<void> setBlocked(bool newBlocked);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = Map<String, dynamic>.from(content);
|
||||
// some old data may have the verified and blocked keys which are unneeded now
|
||||
|
@ -216,19 +242,21 @@ abstract class _SignedKey {
|
|||
String toString() => json.encode(toJson());
|
||||
}
|
||||
|
||||
class CrossSigningKey extends _SignedKey {
|
||||
class CrossSigningKey extends SignedKey {
|
||||
String get publicKey => identifier;
|
||||
List<String> usage;
|
||||
|
||||
bool get isValid =>
|
||||
userId != null && publicKey != null && keys != null && ed25519Key != null;
|
||||
|
||||
@override
|
||||
Future<void> setVerified(bool newVerified) {
|
||||
_verified = newVerified;
|
||||
return client.database?.setVerifiedUserCrossSigningKey(
|
||||
newVerified, client.id, userId, publicKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setBlocked(bool newBlocked) {
|
||||
blocked = newBlocked;
|
||||
return client.database?.setBlockedUserCrossSigningKey(
|
||||
|
@ -267,7 +295,7 @@ class CrossSigningKey extends _SignedKey {
|
|||
}
|
||||
}
|
||||
|
||||
class DeviceKeys extends _SignedKey {
|
||||
class DeviceKeys extends SignedKey {
|
||||
String get deviceId => identifier;
|
||||
List<String> algorithms;
|
||||
Map<String, dynamic> unsigned;
|
||||
|
@ -281,12 +309,14 @@ class DeviceKeys extends _SignedKey {
|
|||
curve25519Key != null &&
|
||||
ed25519Key != null;
|
||||
|
||||
@override
|
||||
Future<void> setVerified(bool newVerified) {
|
||||
_verified = newVerified;
|
||||
return client.database
|
||||
?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setBlocked(bool newBlocked) {
|
||||
blocked = newBlocked;
|
||||
for (var room in client.rooms) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:random_string/random_string.dart';
|
||||
import 'package:canonical_json/canonical_json.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'device_keys_list.dart';
|
||||
import '../client.dart';
|
||||
|
@ -44,6 +45,7 @@ import '../room.dart';
|
|||
|
||||
enum KeyVerificationState {
|
||||
askAccept,
|
||||
askSSSS,
|
||||
waitingAccept,
|
||||
askSas,
|
||||
waitingSas,
|
||||
|
@ -103,6 +105,8 @@ class KeyVerification {
|
|||
_KeyVerificationMethod method;
|
||||
List<String> possibleMethods;
|
||||
Map<String, dynamic> startPaylaod;
|
||||
String _nextAction;
|
||||
List<SignedKey> _verifiedDevices;
|
||||
|
||||
DateTime lastActivity;
|
||||
String lastStep;
|
||||
|
@ -130,7 +134,7 @@ class KeyVerification {
|
|||
: null);
|
||||
}
|
||||
|
||||
Future<void> start() async {
|
||||
Future<void> sendStart() async {
|
||||
if (room == null) {
|
||||
transactionId =
|
||||
randomString(512) + DateTime.now().millisecondsSinceEpoch.toString();
|
||||
|
@ -141,6 +145,17 @@ class KeyVerification {
|
|||
});
|
||||
startedVerification = true;
|
||||
setState(KeyVerificationState.waitingAccept);
|
||||
lastActivity = DateTime.now();
|
||||
}
|
||||
|
||||
Future<void> start() async {
|
||||
if (client.crossSigning.enabled &&
|
||||
!(await client.crossSigning.isCached())) {
|
||||
setState(KeyVerificationState.askSSSS);
|
||||
_nextAction = 'request';
|
||||
} else {
|
||||
await sendStart();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handlePayload(String type, Map<String, dynamic> payload,
|
||||
|
@ -228,6 +243,29 @@ class KeyVerification {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> openSSSS(
|
||||
{String password, String recoveryKey, bool skip = false}) async {
|
||||
final next = () {
|
||||
if (_nextAction == 'request') {
|
||||
sendStart();
|
||||
} else if (_nextAction == 'done') {
|
||||
if (_verifiedDevices != null) {
|
||||
// and now let's sign them all in the background
|
||||
client.crossSigning.sign(_verifiedDevices);
|
||||
}
|
||||
setState(KeyVerificationState.done);
|
||||
}
|
||||
};
|
||||
if (skip) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
final handle = client.ssss.open('m.cross_signing.user_signing');
|
||||
await handle.unlock(password: password, recoveryKey: recoveryKey);
|
||||
await handle.maybeCacheAll();
|
||||
next();
|
||||
}
|
||||
|
||||
/// called when the user accepts an incoming verification
|
||||
Future<void> acceptVerification() async {
|
||||
if (!(await verifyLastStep(
|
||||
|
@ -293,8 +331,8 @@ class KeyVerification {
|
|||
}
|
||||
|
||||
Future<void> verifyKeys(Map<String, String> keys,
|
||||
Future<bool> Function(String, dynamic) verifier) async {
|
||||
final verifiedDevices = <String>[];
|
||||
Future<bool> Function(String, SignedKey) verifier) async {
|
||||
_verifiedDevices = <SignedKey>[];
|
||||
|
||||
if (!client.userDeviceKeys.containsKey(userId)) {
|
||||
await cancel('m.key_mismatch');
|
||||
|
@ -304,53 +342,48 @@ class KeyVerification {
|
|||
final keyId = entry.key;
|
||||
final verifyDeviceId = keyId.substring('ed25519:'.length);
|
||||
final keyInfo = entry.value;
|
||||
if (client.userDeviceKeys[userId].deviceKeys
|
||||
.containsKey(verifyDeviceId)) {
|
||||
if (!(await verifier(keyInfo,
|
||||
client.userDeviceKeys[userId].deviceKeys[verifyDeviceId]))) {
|
||||
final key = client.userDeviceKeys[userId].getKey(verifyDeviceId);
|
||||
if (key != null) {
|
||||
if (!(await verifier(keyInfo, key))) {
|
||||
await cancel('m.key_mismatch');
|
||||
return;
|
||||
}
|
||||
verifiedDevices.add(verifyDeviceId);
|
||||
} else if (client.userDeviceKeys[userId].crossSigningKeys
|
||||
.containsKey(verifyDeviceId)) {
|
||||
// this is a cross signing key!
|
||||
if (!(await verifier(keyInfo,
|
||||
client.userDeviceKeys[userId].crossSigningKeys[verifyDeviceId]))) {
|
||||
await cancel('m.key_mismatch');
|
||||
return;
|
||||
}
|
||||
verifiedDevices.add(verifyDeviceId);
|
||||
_verifiedDevices.add(key);
|
||||
}
|
||||
}
|
||||
// okay, we reached this far, so all the devices are verified!
|
||||
var verifiedMasterKey = false;
|
||||
final verifiedUserDevices = <DeviceKeys>[];
|
||||
for (final verifyDeviceId in verifiedDevices) {
|
||||
if (client.userDeviceKeys[userId].deviceKeys
|
||||
.containsKey(verifyDeviceId)) {
|
||||
final key = client.userDeviceKeys[userId].deviceKeys[verifyDeviceId];
|
||||
await key.setVerified(true);
|
||||
verifiedUserDevices.add(key);
|
||||
} else if (client.userDeviceKeys[userId].crossSigningKeys
|
||||
.containsKey(verifyDeviceId)) {
|
||||
final key =
|
||||
client.userDeviceKeys[userId].crossSigningKeys[verifyDeviceId];
|
||||
await key.setVerified(true);
|
||||
if (key.usage.contains('master')) {
|
||||
verifiedMasterKey = true;
|
||||
}
|
||||
for (final key in _verifiedDevices) {
|
||||
await key.setVerified(true);
|
||||
if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||
verifiedMasterKey = true;
|
||||
}
|
||||
}
|
||||
if (verifiedMasterKey) {
|
||||
if (userId == client.userID) {
|
||||
// it was our own master key, let's request the cross signing keys
|
||||
// we do it in the background, thus no await needed here
|
||||
client.ssss.maybeRequestAll(verifiedUserDevices);
|
||||
if (verifiedMasterKey && userId == client.userID) {
|
||||
// it was our own master key, let's request the cross signing keys
|
||||
// we do it in the background, thus no await needed here
|
||||
unawaited(client.ssss.maybeRequestAll(
|
||||
_verifiedDevices.whereType<DeviceKeys>().toList()));
|
||||
}
|
||||
await send('m.key.verification.done', {});
|
||||
|
||||
var askingSSSS = false;
|
||||
if (client.crossSigning.enabled &&
|
||||
client.crossSigning.signable(_verifiedDevices)) {
|
||||
// these keys can be signed! Let's do so
|
||||
if (await client.crossSigning.isCached()) {
|
||||
// and now let's sign them all in the background
|
||||
unawaited(client.crossSigning.sign(_verifiedDevices));
|
||||
} else {
|
||||
// it was someone elses master key, let's sign it
|
||||
askingSSSS = true;
|
||||
}
|
||||
}
|
||||
if (askingSSSS) {
|
||||
setState(KeyVerificationState.askSSSS);
|
||||
_nextAction = 'done';
|
||||
} else {
|
||||
setState(KeyVerificationState.done);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> verifyActivity() async {
|
||||
|
@ -729,22 +762,10 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
|
|||
mac[entry.key] = entry.value;
|
||||
}
|
||||
}
|
||||
await request.verifyKeys(mac, (String mac, dynamic device) async {
|
||||
if (device is DeviceKeys) {
|
||||
return mac ==
|
||||
_calculateMac(
|
||||
device.ed25519Key, baseInfo + 'ed25519:' + device.deviceId);
|
||||
} else if (device is CrossSigningKey) {
|
||||
return mac ==
|
||||
_calculateMac(
|
||||
device.ed25519Key, baseInfo + 'ed25519:' + device.publicKey);
|
||||
}
|
||||
return false;
|
||||
await request.verifyKeys(mac, (String mac, SignedKey key) async {
|
||||
return mac ==
|
||||
_calculateMac(key.ed25519Key, baseInfo + 'ed25519:' + key.identifier);
|
||||
});
|
||||
await request.send('m.key.verification.done', {});
|
||||
if (request.state != KeyVerificationState.error) {
|
||||
request.setState(KeyVerificationState.done);
|
||||
}
|
||||
}
|
||||
|
||||
String _makeCommitment(String pubKey, String canonicalJson) {
|
||||
|
|
Loading…
Reference in a new issue