feat(backup): sealed backups + printable recovery sheet

'Save a backup' now writes a sealed .tanemaki file (AES-256-GCM under a
key HKDF-derived from the root seed; format TANEBK v1, open and stable).
Restoring on the same identity is silent; a copy sealed by another
identity asks for the printed recovery code (TANE1 base32, typo-tolerant)
and adopts that seed — recovering your bank recovers you. Legacy plain
exports still restore. Settings gains a recovery sheet (QR + code PDF).
BackupBox/RecoveryCode live in commons_core, pure Dart, TDD.
This commit is contained in:
vjrj 2026-07-09 22:32:13 +02:00
parent ba87bf2719
commit d6781870d9
19 changed files with 1016 additions and 81 deletions

View file

@ -183,6 +183,15 @@ class _Translations$backup$es extends Translations$backup$en {
@override String importCsvDone({required Object count}) => 'Añadidas ${count} entradas';
@override String get importFailed => 'Este fichero no se pudo leer como una copia de Tanemaki';
@override String get failed => 'Algo ha salido mal';
@override String get recoveryTitle => 'Tu código de recuperación';
@override String get recoverySubtitle => 'Imprímelo y guárdalo bien: abre tus copias en otro dispositivo';
@override String get recoveryIntro => 'Este código abre tus copias guardadas y recupera tu banco en cualquier dispositivo. Guarda dos copias en papel en sitios seguros, como tu mejor semilla. Quien lo tenga puede leer tus copias, así que no lo compartas con nadie.';
@override String get recoveryCopy => 'Copiar';
@override String get recoverySave => 'Guardar la hoja';
@override String get recoverySheetTitle => 'Tanemaki — tu hoja de recuperación';
@override String get recoveryPromptTitle => 'Escribe tu código de recuperación';
@override String get recoveryPromptBody => 'Esta copia se guardó con otro código. Escribe el código de tu hoja de recuperación para abrirla.';
@override String get recoveryWrongCode => 'Ese código no abre esta copia';
}
// Path: about
@ -970,6 +979,15 @@ extension on TranslationsEs {
'backup.importCsvDone' => ({required Object count}) => 'Añadidas ${count} entradas',
'backup.importFailed' => 'Este fichero no se pudo leer como una copia de Tanemaki',
'backup.failed' => 'Algo ha salido mal',
'backup.recoveryTitle' => 'Tu código de recuperación',
'backup.recoverySubtitle' => 'Imprímelo y guárdalo bien: abre tus copias en otro dispositivo',
'backup.recoveryIntro' => 'Este código abre tus copias guardadas y recupera tu banco en cualquier dispositivo. Guarda dos copias en papel en sitios seguros, como tu mejor semilla. Quien lo tenga puede leer tus copias, así que no lo compartas con nadie.',
'backup.recoveryCopy' => 'Copiar',
'backup.recoverySave' => 'Guardar la hoja',
'backup.recoverySheetTitle' => 'Tanemaki — tu hoja de recuperación',
'backup.recoveryPromptTitle' => 'Escribe tu código de recuperación',
'backup.recoveryPromptBody' => 'Esta copia se guardó con otro código. Escribe el código de tu hoja de recuperación para abrirla.',
'backup.recoveryWrongCode' => 'Ese código no abre esta copia',
'about.title' => 'Acerca de',
'about.kanji' => '種まき',
'about.tagline' => 'Una app local-first y descentralizada para gestionar y compartir semillas y plantones tradicionales.',