feat: secure account sharing via public-key encryption (#107)

Replace the insecure plaintext QR export/import flow with an
end-to-end-encrypted account-transfer mechanism:

- Receiver generates an ephemeral X25519 key pair (20-minute lifetime,
  stored in the new share_keys DB table at schema v31) and displays it
  as a QR code (sharedinbox.de:pubkey:v1:…).
- Sender scans the public-key QR, selects accounts (or auto-selects
  when only one exists), encrypts them with ECIES (X25519-ECDH +
  HKDF-SHA256 + AES-256-GCM) and displays an encrypted QR
  (sharedinbox.de:encrypted-accounts:v1:…).
- Receiver scans the encrypted QR, decrypts, verifies the 20-minute
  expiry and MAC authentication tag, then imports the accounts.

New screens: AccountReceiveScreen (/accounts/receive) and
AccountSendScreen (/accounts/send), accessible from the account-list
drawer and per-account popup menu respectively.

Remove the old insecure AccountExportScreen and AccountImportScreen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas SharedInbox
2026-05-16 01:19:01 +02:00
co-authored by Claude Sonnet 4.6
parent c21d198a25
commit 04e65d2fba
21 changed files with 1543 additions and 430 deletions
+3 -2
View File
@@ -14,7 +14,7 @@ void main() {
group('Migration', () {
test('schemaVersion matches expected value', () async {
final db = AppDatabase(NativeDatabase.memory());
expect(db.schemaVersion, 30);
expect(db.schemaVersion, 31);
await db.close();
});
@@ -382,7 +382,7 @@ void main() {
if (dbFile.existsSync()) dbFile.deleteSync();
});
test('fresh install creates all tables at schemaVersion 30', () async {
test('fresh install creates all tables at schemaVersion 31', () async {
final db = AppDatabase(NativeDatabase.memory());
await db.select(db.accounts).get();
@@ -407,6 +407,7 @@ void main() {
'undo_actions',
'search_history_entries',
'local_sieve_scripts', // v29
'share_keys', // v31
]),
);