8081d98c67
Initial public release. Anonymous on-device identity (no phone number, e-mail, or password), Signal-protocol cryptography (X3DH + PQXDH, Double Ratchet, sealed sender) via libsignal, a metadata-minimizing relay, group chats, disappearing messages, encrypted local storage, optional Tor transport, and a UI localized into Czech, English, German, Russian, and Italian. Monorepo: Flutter client (app/), Rust relay + wire types (rust/), protobuf definitions (proto/), self-host deploy (deploy/). Licensed under AGPL-3.0 with an app-store distribution exception (§7).
75 lines
2.6 KiB
Dart
75 lines
2.6 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:sepot/core/crypto/safety_number.dart';
|
|
import 'package:sepot/core/crypto/sepot_crypto.dart';
|
|
|
|
void main() {
|
|
setUpAll(() async {
|
|
await SepotCrypto.init();
|
|
});
|
|
|
|
Uint8List fixedKey(int seed) {
|
|
// deterministický „privátní klíč" → veřejný přes libsignal derivaci
|
|
final priv = Uint8List.fromList(List.generate(32, (i) => (i * 7 + seed) & 0xFF));
|
|
return SepotCrypto.derivePublicKey(priv);
|
|
}
|
|
|
|
test('obě strany vidí stejné číslo (symetrie)', () {
|
|
final a = fixedKey(1);
|
|
final b = fixedKey(2);
|
|
final fromA = SafetyNumber.compute(localIdentityKey: a, remoteIdentityKey: b);
|
|
final fromB = SafetyNumber.compute(localIdentityKey: b, remoteIdentityKey: a);
|
|
expect(fromA.display, fromB.display);
|
|
});
|
|
|
|
test('60 číslic, 12 bloků po 5', () {
|
|
final n = SafetyNumber.compute(
|
|
localIdentityKey: fixedKey(1),
|
|
remoteIdentityKey: fixedKey(2),
|
|
);
|
|
expect(n.display, matches(RegExp(r'^\d{60}$')));
|
|
expect(n.groups, hasLength(12));
|
|
expect(n.groups.every((g) => g.length == 5), isTrue);
|
|
});
|
|
|
|
test('jiný pár klíčů = jiné číslo', () {
|
|
final ab = SafetyNumber.compute(
|
|
localIdentityKey: fixedKey(1), remoteIdentityKey: fixedKey(2));
|
|
final ac = SafetyNumber.compute(
|
|
localIdentityKey: fixedKey(1), remoteIdentityKey: fixedKey(3));
|
|
expect(ab.display, isNot(ac.display));
|
|
});
|
|
|
|
test('QR compare: protistrany si odpovídají, cizí ne', () {
|
|
final a = fixedKey(1);
|
|
final b = fixedKey(2);
|
|
final mine = SafetyNumber.compute(localIdentityKey: a, remoteIdentityKey: b);
|
|
final theirs = SafetyNumber.compute(localIdentityKey: b, remoteIdentityKey: a);
|
|
final stranger = SafetyNumber.compute(
|
|
localIdentityKey: a, remoteIdentityKey: fixedKey(9));
|
|
|
|
expect(SafetyNumber.compare(mine.scannable, theirs.scannable), isTrue);
|
|
expect(SafetyNumber.compare(mine.scannable, stranger.scannable), isFalse);
|
|
// vadný vstup nesmí spadnout
|
|
expect(
|
|
SafetyNumber.compare(mine.scannable, Uint8List.fromList([1, 2, 3])),
|
|
isFalse,
|
|
);
|
|
});
|
|
|
|
test('KAT: zafixovaný výstup — změna = breaking change pro uživatele', () {
|
|
final n = SafetyNumber.compute(
|
|
localIdentityKey: fixedKey(1),
|
|
remoteIdentityKey: fixedKey(2),
|
|
);
|
|
// Vygenerováno libsignal 5.0.9 (iterations=5200, version=2,
|
|
// identifier=BLAKE2b-256(pubkey)). Pokud tenhle test spadne po
|
|
// upgradu, VŠEM uživatelům se změní safety numbers → stop ship.
|
|
expect(
|
|
n.display,
|
|
'289143504470406742912626343937385761080203728630218051783783',
|
|
);
|
|
});
|
|
}
|