Š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:
Julius Joska
2026-07-11 17:06:39 +02:00
commit 8081d98c67
309 changed files with 44102 additions and 0 deletions
+178
View File
@@ -0,0 +1,178 @@
/// Typované chyby, které se zobrazují uživateli.
///
/// Service vrstva nemá přístup k lokalizaci (žádný BuildContext), proto
/// nesmí formulovat texty pro uživatele — vyhazuje tyhle typy a UI je
/// překládá přes `describeError` (core/l10n/error_messages.dart).
/// `toString()` je anglický technický popis pro logy, ne pro UI.
library;
sealed class SepotUserError implements Exception {
const SepotUserError();
}
/// Trust root relaye nesedí s pinovaným — server nelze ověřit.
class TrustRootChangedError extends SepotUserError {
const TrustRootChangedError();
@override
String toString() => 'relay trust root changed — refusing to communicate';
}
/// Relay nedostupný (typicky fetch sender certificate).
class RelayUnavailableError extends SepotUserError {
const RelayUnavailableError();
@override
String toString() => 'relay unavailable';
}
/// Příloha přesahuje serverový limit.
class AttachmentTooLargeError extends SepotUserError {
const AttachmentTooLargeError(this.maxBytes);
final int maxBytes;
int get maxMb => (maxBytes / (1024 * 1024)).round();
@override
String toString() => 'attachment too large (max $maxBytes bytes)';
}
/// Skupina bez jediného dalšího člena nedává smysl.
class GroupNeedsMemberError extends SepotUserError {
const GroupNeedsMemberError();
@override
String toString() => 'group needs at least one member';
}
/// Překročen strop počtu členů skupiny.
class GroupTooLargeError extends SepotUserError {
const GroupTooLargeError(this.maxMembers);
final int maxMembers;
@override
String toString() => 'group exceeds $maxMembers members';
}
/// Člen skupiny nemá doručovací adresu (starý odkaz bez routingu).
class MemberNoDeliveryAddressError extends SepotUserError {
const MemberNoDeliveryAddressError(this.displayName);
final String displayName;
@override
String toString() => 'member "$displayName" has no delivery address';
}
/// Kontakt nemá doručovací adresu (starý odkaz bez routingu).
class ContactNoDeliveryAddressError extends SepotUserError {
const ContactNoDeliveryAddressError();
@override
String toString() => 'contact has no delivery address';
}
/// Identity klíč z relaye nesedí s uloženým kontaktem — možný MITM.
class IdentityKeyMismatchError extends SepotUserError {
const IdentityKeyMismatchError();
@override
String toString() => 'identity key mismatch — possible MITM, message not sent';
}
// === Obnova účtu (mnemonic) ===
/// Fráze má špatný počet slov.
class MnemonicWordCountError extends SepotUserError {
const MnemonicWordCountError(this.count, this.expected);
final int count;
final int expected;
@override
String toString() => 'mnemonic has $count words, expected $expected';
}
/// Fráze neprošla validací BIP39 (neznámé slovo, checksum…).
class MnemonicInvalidError extends SepotUserError {
const MnemonicInvalidError(this.detail);
final String detail;
@override
String toString() => 'invalid mnemonic: $detail';
}
// === Zálohy historie (backup_service) ===
/// Soubor zálohy je poškozený (nejde přečíst/dešifrovat strukturu).
class BackupCorruptedError extends SepotUserError {
const BackupCorruptedError();
@override
String toString() => 'corrupted backup file';
}
/// Soubor není záloha Šepotu.
class BackupUnknownFormatError extends SepotUserError {
const BackupUnknownFormatError();
@override
String toString() => 'unknown backup format';
}
/// Záloha je šifrovaná klíčem jiné identity.
class BackupWrongIdentityError extends SepotUserError {
const BackupWrongIdentityError();
@override
String toString() => 'backup belongs to a different identity';
}
// === Kontakty ===
/// Kontakt s tímto fingerprintem už existuje.
class ContactAlreadyExistsError extends SepotUserError {
const ContactAlreadyExistsError();
@override
String toString() => 'contact already exists';
}
// === Nahlášení zneužití (report_service) ===
/// Hlášení bez popisu nemá co řešit.
class ReportEmptyDescriptionError extends SepotUserError {
const ReportEmptyDescriptionError();
@override
String toString() => 'report has empty description';
}
/// Server hlášení je nedosažitelný.
class ReportConnectionError extends SepotUserError {
const ReportConnectionError();
@override
String toString() => 'report endpoint unreachable';
}
/// Rate limit hlášení (429).
class ReportRateLimitedError extends SepotUserError {
const ReportRateLimitedError();
@override
String toString() => 'report rate limited';
}
/// Server hlášení vrátil chybový kód.
class ReportServerError extends SepotUserError {
const ReportServerError(this.statusCode);
final int statusCode;
@override
String toString() => 'report failed with status $statusCode';
}