Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement]: Stabilize collaboration #795

Open
1 task done
CodeDoctorDE opened this issue Feb 15, 2025 · 1 comment
Open
1 task done

[Enhancement]: Stabilize collaboration #795

CodeDoctorDE opened this issue Feb 15, 2025 · 1 comment
Assignees
Labels
enhancement Small enhancements to existing features
Milestone

Comments

@CodeDoctorDE
Copy link
Member

Which feature is your request related to?

No response

Describe your request for enhancements!

Currently collaboration is experimental since a long time.
We should finally stabilize it.
Some things which should be added

  • Add useful error messages
  • Hide technical options inside an advanced tab
  • Add username field
  • Add option to configure a "Lobby" system to not allow everyone to join
  • Add browse lan games
  • Add qr code (and note that the person needs to be in the same network)

In the future maybe:

  • Add relay server system

Additional context

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct
@CodeDoctorDE CodeDoctorDE added the enhancement Small enhancements to existing features label Feb 15, 2025
@CodeDoctorDE CodeDoctorDE added this to the 2.3 milestone Feb 15, 2025
@CodeDoctorDE CodeDoctorDE self-assigned this Feb 15, 2025
@github-project-automation github-project-automation bot moved this to 📕 Todo in Butterfly Feb 15, 2025
@CodeDoctorDE
Copy link
Member Author

CodeDoctorDE commented Feb 26, 2025

For end to end encryption to easily allow relay server we could use these examples:

Example Dart code

import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

import 'package:cryptography/cryptography.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

/// Encrypts a message using AES-GCM.
/// The key is derived by hashing the secret (SHA-256) to produce a 256-bit key.
/// A random 12-byte nonce is generated for each message. The output is:
///   base64( nonce || ciphertext || mac )
Future<String> encryptMessage(String message, String secret) async {
  // Derive a 256-bit key from the secret:
  final keyBytes = utf8.encode(secret);
  final hash = await Sha256().hash(keyBytes);
  final secretKey = SecretKey(hash.bytes);

  // AES-GCM with 256-bit key:
  final algorithm = AesGcm.with256bits();

  // Generate a random 12-byte nonce:
  final nonce = _generateRandomNonce(12);

  // Encrypt the message:
  final secretBox = await algorithm.encrypt(
    utf8.encode(message),
    secretKey: secretKey,
    nonce: nonce,
  );

  // Concatenate nonce, ciphertext, and MAC:
  final combined = <int>[];
  combined.addAll(nonce);
  combined.addAll(secretBox.cipherText);
  combined.addAll(secretBox.mac.bytes);

  // Return as a Base64-encoded string.
  return base64.encode(combined);
}

/// Decrypts a Base64-encoded string that contains nonce || ciphertext || mac.
Future<String> decryptMessage(String encryptedMessage, String secret) async {
  // Decode from Base64:
  final combined = base64.decode(encryptedMessage);

  // AES-GCM typically uses a 12-byte nonce and 16-byte MAC:
  const nonceLength = 12;
  const macLength = 16;
  if (combined.length < nonceLength + macLength) {
    throw Exception('Invalid encrypted message');
  }
  final nonce = combined.sublist(0, nonceLength);
  final macBytes = combined.sublist(combined.length - macLength);
  final cipherText = combined.sublist(nonceLength, combined.length - macLength);

  // Derive the same key from the secret:
  final keyBytes = utf8.encode(secret);
  final hash = await Sha256().hash(keyBytes);
  final secretKey = SecretKey(hash.bytes);

  final algorithm = AesGcm.with256bits();
  final secretBox = SecretBox(
    cipherText,
    nonce: nonce,
    mac: Mac(macBytes),
  );

  // Decrypt:
  final clearTextBytes = await algorithm.decrypt(
    secretBox,
    secretKey: secretKey,
  );

  return utf8.decode(clearTextBytes);
}

/// Generates a list of random bytes of the specified [length].
List<int> _generateRandomNonce(int length) {
  final random = Random.secure();
  return List<int>.generate(length, (_) => random.nextInt(256));
}

/// ChatClient using WebSockets with end-to-end encryption.
class ChatClient {
  final WebSocketChannel channel;
  final String encryptionKey; // e.g. the secret extracted from the room URL

  ChatClient(String url, this.encryptionKey)
      : channel = WebSocketChannel.connect(Uri.parse(url)) {
    // Listen for incoming messages.
    channel.stream.listen((data) async {
      try {
        // Assume the incoming data is a Base64 string.
        final decrypted = await decryptMessage(data, encryptionKey);
        print("Received decrypted: $decrypted");
        // Process the decrypted message (update UI, etc.)
      } catch (e) {
        print("Decryption error: $e");
      }
    }, onError: (error) {
      print("Socket error: $error");
    });
  }

  /// Encrypts the [message] and sends it over the WebSocket.
  Future<void> sendMessage(String message) async {
    try {
      final encrypted = await encryptMessage(message, encryptionKey);
      channel.sink.add(encrypted);
      print("Sent encrypted: $encrypted");
    } catch (e) {
      print("Encryption error: $e");
    }
  }

  void dispose() {
    channel.sink.close();
  }
}

void main() async {
  // For example, your room URL might be:
  // https://excalidraw.com/#room=968bc76e9d3fa44fbcbd,6TSIcavZwRpKdl7cNLL61w
  // Extract the secret part ("6TSIcavZwRpKdl7cNLL61w") to use as the encryption key.
  final roomWebSocketUrl = "wss://your-relay-server.example.com"; 
  final secretKey = "6TSIcavZwRpKdl7cNLL61w";

  final chatClient = ChatClient(roomWebSocketUrl, secretKey);

  // Send an encrypted message:
  await chatClient.sendMessage("Hello, end-to-end encrypted world!");

  // The client remains active to receive messages.
}

For inspiration:
Excalidraw: https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/encryption.ts

ID and secret can be inside the url with the relay server that will be used. When connecting we need to confirm the user if we really want to connect since we then communicate with the relay server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Small enhancements to existing features
Projects
Status: 📕 Todo
Development

No branches or pull requests

1 participant