Šepot — end-to-end encrypted messenger
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).
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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',
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user