@@ -15,6 +15,7 @@ import { useHeaderStore } from "@/contexts/header-store-context";
1515import { exportHeaderStore , importHeaderStore } from "@/core/headers/store" ;
1616import { markExportPerformed , saveDossiersBatch } from "@/core/dossier/store" ;
1717import type { UtxoDossier , ProofArchive , Bucket } from "@/core/dossier/types" ;
18+ import JSZip from "jszip" ;
1819
1920type ArchiveData = {
2021 version : number ;
@@ -147,17 +148,48 @@ export default function ExportPage() {
147148 result . set ( iv , salt . length ) ;
148149 result . set ( new Uint8Array ( encrypted ) , salt . length + iv . length ) ;
149150
150- const blob = new Blob ( [ result ] , { type : "application/octet-stream" } ) ;
151+ // Create zip with encrypted file + decryption tool
152+ const zip = new JSZip ( ) ;
153+ const baseName = `chronicle-archive-${ new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) } ` ;
154+ zip . file ( `${ baseName } .enc` , result ) ;
155+
156+ // Fetch and include the decryption tool
157+ const decryptToolResponse = await fetch ( "/decrypt-tool.html" ) ;
158+ const decryptToolHtml = await decryptToolResponse . text ( ) ;
159+ zip . file ( "decrypt-tool.html" , decryptToolHtml ) ;
160+
161+ // Add a README
162+ zip . file ( "README.txt" , `Chronicle Cold Vault - Encrypted Full Backup
163+ =============================================
164+
165+ This archive contains:
166+ - ${ baseName } .enc - Your encrypted Chronicle data (dossiers, BEEF proofs, headers)
167+ - decrypt-tool.html - Standalone decryption tool
168+
169+ To decrypt:
170+ 1. Open decrypt-tool.html in any modern web browser
171+ 2. Select the .enc file
172+ 3. Enter your passphrase
173+ 4. Download the decrypted JSON
174+
175+ Encryption: AES-256-GCM with PBKDF2 (100,000 iterations, SHA-256)
176+
177+ Exported: ${ new Date ( ) . toISOString ( ) }
178+ Dossiers: ${ dossiers . length }
179+ BEEF Archives: ${ archives . length }
180+ ` ) ;
181+
182+ const blob = await zip . generateAsync ( { type : "blob" } ) ;
151183 const url = URL . createObjectURL ( blob ) ;
152184 const a = document . createElement ( "a" ) ;
153185 a . href = url ;
154- a . download = `chronicle-archive- ${ new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) } .enc ` ;
186+ a . download = `${ baseName } -encrypted.zip ` ;
155187 a . click ( ) ;
156188 URL . revokeObjectURL ( url ) ;
157189
158190 // Mark export performed for reminder tracking
159191 markExportPerformed ( dossiers . length ) ;
160- setStatus ( "Encrypted archive exported successfully." ) ;
192+ setStatus ( "Encrypted archive exported successfully (zip with decryption tool) ." ) ;
161193 setPassphrase ( "" ) ;
162194 } catch ( e ) {
163195 setStatus ( `Encryption error: ${ e instanceof Error ? e . message : "Unknown" } ` ) ;
@@ -434,6 +466,13 @@ export default function ExportPage() {
434466 ) }
435467 </ div >
436468 < Button onClick = { handleExportEncrypted } > Export Encrypted</ Button >
469+ < p className = "text-xs text-muted-foreground" >
470+ 📦 Encrypted exports are bundled as a .zip with a standalone{ " " }
471+ < a href = "/decrypt-tool.html" target = "_blank" className = "text-primary underline" >
472+ decryption tool
473+ </ a > { " " }
474+ for future-proof access without Chronicle.
475+ </ p >
437476 </ CardContent >
438477 </ Card >
439478
0 commit comments