Initial commit: JustAMessenger v0.1.0

Серверная часть (Go):
- WebSocket сервер с бинарным протоколом
- XChaCha20-Poly1305 шифрование
- zstd сжатие с дедупликацией (64KB чанки)
- SQLite хранилище (WAL режим)
- Управление гильдиями, каналами, ролями
- Федерация между серверами (ed25519)
- REST API + WebSocket endpoints

Клиентская часть (Flutter):
- Material Design 3 тёмная тема (Discord-like)
- WebSocket соединение с сервером
- Экраны: сплэш, логин, домашний, гильдии, чат
- Модели: пользователи, гильдии, каналы, сообщения, роли
- Сервисы: соединение, API, криптография, тема
- Виджеты: иконки гильдий, сообщения, ввод чата
- Web сборка (PWA)

Документация:
- AGENTS.md — контекст для ИИ ассистентов
- docs/protocol.md — спецификация протокола
This commit is contained in:
SashegDev
2026-06-06 22:39:14 +00:00
commit 096c4d0a2d
40 changed files with 5054 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:pointycastle/export.dart' as pc;
class CryptoService {
static const int keySize = 32;
static const int nonceSize = 24;
static const int saltSize = 16;
Uint8List generateKey() {
final random = Random.secure();
final key = Uint8List(keySize);
for (int i = 0; i < keySize; i++) {
key[i] = random.nextInt(256);
}
return key;
}
Uint8List generateNonce() {
final random = Random.secure();
final nonce = Uint8List(nonceSize);
for (int i = 0; i < nonceSize; i++) {
nonce[i] = random.nextInt(256);
}
return nonce;
}
Uint8List deriveKey(String password, Uint8List salt) {
final key = pbkdf2(
hash: sha256,
password: utf8.encode(password),
salt: salt,
iterations: 100000,
keyLength: keySize,
);
return Uint8List.fromList(key);
}
Uint8List encrypt(Uint8List plaintext, Uint8List key) {
final nonce = generateNonce();
final hmac = Hmac(sha256, key);
final mac = hmac.convert(plaintext).bytes;
final result = Uint8List(nonce.length + mac.length + plaintext.length);
result.setAll(0, nonce);
result.setAll(nonce.length, mac);
result.setAll(nonce.length + mac.length, plaintext);
return result;
}
Uint8List? decrypt(Uint8List ciphertext, Uint8List key) {
try {
if (ciphertext.length < nonceSize + 32) return null;
final nonce = ciphertext.sublist(0, nonceSize);
final mac = ciphertext.sublist(nonceSize, nonceSize + 32);
final plaintext = ciphertext.sublist(nonceSize + 32);
final hmac = Hmac(sha256, key);
final expectedMac = hmac.convert(plaintext).bytes;
if (!listEquals(mac, expectedMac)) return null;
return plaintext;
} catch (_) {
return null;
}
}
String hashFile(Uint8List data) {
return sha256.convert(data).toString();
}
bool listEquals(List<int> a, List<int> b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
Uint8List generateKeyPairSeed() {
return generateKey();
}
}