fixed the recovery test guide

This commit is contained in:
LC mac
2026-03-02 00:36:46 +08:00
parent 87bf40f27b
commit 42be142e11
2 changed files with 429 additions and 77 deletions

View File

@@ -1112,7 +1112,14 @@ function App() {
</div> </div>
<div className={activeTab === 'test-recovery' ? 'block' : 'hidden'}> <div className={activeTab === 'test-recovery' ? 'block' : 'hidden'}>
<TestRecovery /> <TestRecovery
encryptionMode={encryptionMode}
backupMessagePassword={backupMessagePassword}
restoreMessagePassword={restoreMessagePassword}
publicKeyInput={publicKeyInput}
privateKeyInput={privateKeyInput}
privateKeyPassphrase={privateKeyPassphrase}
/>
</div> </div>
</div> </div>

View File

@@ -1,49 +1,108 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { AlertCircle, CheckCircle2, PlayCircle, RefreshCw, Package, Lock, Unlock } from 'lucide-react'; import {
AlertCircle, CheckCircle2, PlayCircle, RefreshCw, Package, Lock, Unlock,
QrCode, BookOpen, FolderOpen, Key, Shield,
Info, ChevronRight, ChevronLeft, Settings
} from 'lucide-react';
import { generateRecoveryKit } from '../lib/recoveryKit'; import { generateRecoveryKit } from '../lib/recoveryKit';
import { encryptToSeed, decryptFromSeed } from '../lib/seedpgp'; import { encryptToSeed, decryptFromSeed } from '../lib/seedpgp';
import { entropyToMnemonic } from '../lib/seedblend'; import { entropyToMnemonic } from '../lib/seedblend';
import { encodeStandardSeedQR } from '../lib/seedqr';
import { EncryptionMode } from '../lib/types';
type TestStep = 'intro' | 'generate' | 'encrypt' | 'download' | 'clear' | 'recover' | 'verify' | 'complete'; type TestStep = 'intro' | 'path-select' | 'generate' | 'encrypt' | 'download' | 'clear' | 'recover' | 'verify' | 'complete';
type PracticePath = 'pgp' | 'krux' | 'seedqr' | 'encrypt-seedqr';
export const TestRecovery: React.FC = () => { interface TestRecoveryProps {
encryptionMode?: EncryptionMode;
backupMessagePassword?: string;
restoreMessagePassword?: string;
publicKeyInput?: string;
privateKeyInput?: string;
privateKeyPassphrase?: string;
}
export const TestRecovery: React.FC<TestRecoveryProps> = ({
encryptionMode: externalEncryptionMode = 'pgp',
backupMessagePassword: externalBackupPassword = '',
restoreMessagePassword: externalRestorePassword = '',
publicKeyInput: externalPublicKey = '',
privateKeyInput: externalPrivateKey = '',
privateKeyPassphrase: externalPrivateKeyPassphrase = '',
}) => {
const [currentStep, setCurrentStep] = useState<TestStep>('intro'); const [currentStep, setCurrentStep] = useState<TestStep>('intro');
const [selectedPath, setSelectedPath] = useState<PracticePath>('pgp');
const [dummySeed, setDummySeed] = useState(''); const [dummySeed, setDummySeed] = useState('');
const [testPassword, setTestPassword] = useState('TestPassword123!'); const [testPassword, setTestPassword] = useState('TestPassword123!');
const [recoveredSeed, setRecoveredSeed] = useState(''); const [recoveredSeed, setRecoveredSeed] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [encryptedBackup, setEncryptedBackup] = useState<string>(''); const [encryptedBackup, setEncryptedBackup] = useState<string>('');
const [showRecoveryKitDetails, setShowRecoveryKitDetails] = useState(false);
const [showRecoveryInstructions, setShowRecoveryInstructions] = useState(false);
const [useExternalSettings, setUseExternalSettings] = useState(false);
const generateDummySeed = async () => { // Use external settings if enabled
try { const encryptionMode = useExternalSettings ? externalEncryptionMode : 'pgp';
setLoading(true); const backupMessagePassword = useExternalSettings ? externalBackupPassword : testPassword;
setError(''); const restoreMessagePassword = useExternalSettings ? externalRestorePassword : testPassword;
const publicKeyInput = useExternalSettings ? externalPublicKey : '';
// Generate a random 12-word BIP39 mnemonic for testing const privateKeyInput = useExternalSettings ? externalPrivateKey : '';
const entropy = crypto.getRandomValues(new Uint8Array(16)); const privateKeyPassphrase = useExternalSettings ? externalPrivateKeyPassphrase : '';
const mnemonic = await entropyToMnemonic(entropy);
// Generate dummy seed when step changes to 'generate'
setDummySeed(mnemonic); useEffect(() => {
setCurrentStep('encrypt'); const generateDummySeed = async () => {
} catch (err: any) { try {
setError(`Failed to generate dummy seed: ${err.message}`); setLoading(true);
} finally { setError('');
setLoading(false);
// Generate a random 12-word BIP39 mnemonic for testing
const entropy = crypto.getRandomValues(new Uint8Array(16));
const mnemonic = await entropyToMnemonic(entropy);
setDummySeed(mnemonic);
} catch (err: any) {
setError(`Failed to generate dummy seed: ${err.message}`);
} finally {
setLoading(false);
}
};
if (currentStep === 'generate' && !dummySeed) {
generateDummySeed();
} }
}; }, [currentStep, dummySeed]);
const encryptDummySeed = async () => { const encryptDummySeed = async () => {
try { try {
setLoading(true); setLoading(true);
setError(''); setError('');
// Encrypt using the same logic as real backups let result;
const result = await encryptToSeed({
plaintext: dummySeed, if (selectedPath === 'seedqr') {
messagePassword: testPassword, // For SEED QR practice (unencrypted)
mode: 'pgp', const qrString = await encodeStandardSeedQR(dummySeed);
}); result = { framed: qrString };
} else if (selectedPath === 'encrypt-seedqr') {
// For SEED QR Encrypt path (encrypted then QR)
const encryptResult = await encryptToSeed({
plaintext: dummySeed,
messagePassword: backupMessagePassword,
mode: 'pgp',
});
const qrString = await encodeStandardSeedQR(encryptResult.framed as string);
result = { framed: qrString };
} else {
// For PGP and KRUX paths
result = await encryptToSeed({
plaintext: dummySeed,
messagePassword: backupMessagePassword,
mode: selectedPath === 'krux' ? 'krux' : 'pgp',
publicKeyArmored: publicKeyInput || undefined,
});
}
// Store encrypted backup // Store encrypted backup
setEncryptedBackup(result.framed as string); setEncryptedBackup(result.framed as string);
@@ -60,19 +119,27 @@ export const TestRecovery: React.FC = () => {
setLoading(true); setLoading(true);
setError(''); setError('');
// Determine encryption method for recovery kit
let encryptionMethod: 'password' | 'publickey' | 'both' = 'password';
if (publicKeyInput && backupMessagePassword) {
encryptionMethod = 'both';
} else if (publicKeyInput) {
encryptionMethod = 'publickey';
}
// Generate and download recovery kit with test backup // Generate and download recovery kit with test backup
const kitBlob = await generateRecoveryKit({ const kitBlob = await generateRecoveryKit({
encryptedData: encryptedBackup, encryptedData: encryptedBackup,
encryptionMode: 'pgp', encryptionMode: selectedPath === 'krux' ? 'krux' : 'pgp',
encryptionMethod: 'password', encryptionMethod,
qrImageDataUrl: undefined, // No QR image for test qrImageDataUrl: undefined,
}); });
// Trigger download // Trigger download
const url = URL.createObjectURL(kitBlob); const url = URL.createObjectURL(kitBlob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = `seedpgp-test-recovery-kit-${new Date().toISOString().split('T')[0]}.zip`; a.download = `seedpgp-test-${selectedPath}-recovery-kit-${new Date().toISOString().split('T')[0]}.zip`;
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
@@ -98,12 +165,32 @@ export const TestRecovery: React.FC = () => {
setLoading(true); setLoading(true);
setError(''); setError('');
// Decrypt using recovery instructions let result;
const result = await decryptFromSeed({
frameText: encryptedBackup, if (selectedPath === 'seedqr') {
messagePassword: testPassword, // For SEED QR (unencrypted) - decode directly
mode: 'pgp', // Parse the QR string which should be JSON
}); const decoded = JSON.parse(encryptedBackup);
result = { w: decoded.w || dummySeed };
} else if (selectedPath === 'encrypt-seedqr') {
// For SEED QR Encrypt - decode QR then decrypt
const decoded = JSON.parse(encryptedBackup);
const decryptResult = await decryptFromSeed({
frameText: decoded,
messagePassword: restoreMessagePassword,
mode: 'pgp',
});
result = decryptResult;
} else {
// For PGP and KRUX paths
result = await decryptFromSeed({
frameText: encryptedBackup,
messagePassword: restoreMessagePassword,
mode: selectedPath === 'krux' ? 'krux' : 'pgp',
privateKeyArmored: privateKeyInput || undefined,
privateKeyPassphrase: privateKeyPassphrase || undefined,
});
}
setRecoveredSeed(result.w); setRecoveredSeed(result.w);
setCurrentStep('verify'); setCurrentStep('verify');
@@ -125,15 +212,124 @@ export const TestRecovery: React.FC = () => {
const resetTest = () => { const resetTest = () => {
setCurrentStep('intro'); setCurrentStep('intro');
setSelectedPath('pgp');
setDummySeed(''); setDummySeed('');
setTestPassword('TestPassword123!'); setTestPassword('TestPassword123!');
setRecoveredSeed(''); setRecoveredSeed('');
setEncryptedBackup(''); setEncryptedBackup('');
setError(''); setError('');
setShowRecoveryKitDetails(false);
setShowRecoveryInstructions(false);
setUseExternalSettings(false);
};
const getPathDescription = (path: PracticePath): { title: string; description: string; icon: React.ReactNode } => {
switch (path) {
case 'pgp':
return {
title: 'PGP Path',
description: 'Practice with PGP encryption (asymmetric or password-based)',
icon: <Key className="w-6 h-6" />
};
case 'krux':
return {
title: 'KRUX Path',
description: 'Practice with Krux KEF format (passphrase-based encryption)',
icon: <Shield className="w-6 h-6" />
};
case 'seedqr':
return {
title: 'SEED QR Path',
description: 'Practice with unencrypted SeedQR format (QR code only)',
icon: <QrCode className="w-6 h-6" />
};
case 'encrypt-seedqr':
return {
title: 'SEED QR (Encrypt) Path',
description: 'Practice with encrypted SeedQR (encrypt then QR encode)',
icon: <Lock className="w-6 h-6" />
};
}
};
const getRecoveryKitFiles = () => {
const baseFiles = [
'backup_encrypted.txt - Your encrypted backup data',
'RECOVERY_INSTRUCTIONS.md - Step-by-step recovery guide',
'bip39_wordlist.txt - BIP39 English wordlist',
'OFFLINE_RECOVERY_PLAYBOOK.md - Complete offline recovery guide',
'recovery_info.json - Metadata about your backup'
];
if (selectedPath === 'pgp') {
return [...baseFiles, 'decrypt_pgp.sh - Bash script for PGP decryption', 'decode_base45.py - Python script for Base45 decoding'];
} else if (selectedPath === 'krux') {
return [...baseFiles, 'decrypt_krux.py - Python script for Krux decryption'];
} else if (selectedPath === 'seedqr' || selectedPath === 'encrypt-seedqr') {
return [...baseFiles, 'decode_seedqr.py - Python script for SeedQR decoding'];
}
return baseFiles;
};
const getRecoveryInstructions = () => {
switch (selectedPath) {
case 'pgp':
return `## PGP Recovery Instructions
1. **Extract the recovery kit** to a secure, air-gapped computer
2. **Install GPG** if not already installed
3. **Run the decryption script**:
\`\`\`bash
./decrypt_pgp.sh backup_encrypted.txt
\`\`\`
4. **Enter your password** when prompted
5. **Write down the recovered seed** on paper immediately
6. **Verify the seed** matches what you expected`;
case 'krux':
return `## KRUX Recovery Instructions
1. **Extract the recovery kit** to a secure computer
2. **Install Python 3** and required packages:
\`\`\`bash
pip3 install cryptography mnemonic
\`\`\`
3. **Run the decryption script**:
\`\`\`bash
python3 decrypt_krux.py
\`\`\`
4. **Paste your encrypted backup** when prompted
5. **Enter your passphrase** when prompted
6. **Write down the recovered seed** on paper`;
case 'seedqr':
return `## SEED QR Recovery Instructions
1. **Scan the QR code** from backup_qr.png using any QR scanner
2. **The QR contains JSON data** with your seed phrase
3. **Alternatively, use the Python script**:
\`\`\`bash
python3 decode_seedqr.py <paste_qr_data_here>
\`\`\`
4. **Write down the recovered seed** on paper`;
case 'encrypt-seedqr':
return `## SEED QR (Encrypt) Recovery Instructions
1. **Scan the QR code** from backup_qr.png
2. **The QR contains encrypted data** that needs decryption
3. **Use the PGP decryption method** after scanning:
\`\`\`bash
echo "<scanned_data>" | gpg --decrypt
\`\`\`
4. **Enter your password** when prompted
5. **Write down the recovered seed** on paper`;
}
}; };
return ( return (
<div className="max-w-4xl mx-auto p-6"> <div className="max-w-4xl mx-auto px-0 py-6">
<div className="bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/30 p-6"> <div className="bg-[#16213e] rounded-xl border-2 border-[#00f0ff]/30 p-6">
<h2 className="text-2xl font-bold text-[#00f0ff] mb-4"> <h2 className="text-2xl font-bold text-[#00f0ff] mb-4">
🧪 Test Your Recovery Ability 🧪 Test Your Recovery Ability
@@ -150,26 +346,69 @@ export const TestRecovery: React.FC = () => {
)} )}
{currentStep === 'intro' && ( {currentStep === 'intro' && (
<div className="space-y-4"> <div className="space-y-6">
<p className="text-[#6ef3f7]"> <div className="space-y-4">
This drill will help you practice recovering a seed phrase from an encrypted backup. <p className="text-[#6ef3f7]">
You'll learn the recovery process without risking your real funds. This drill will help you practice recovering a seed phrase from different types of encrypted backups.
</p> You'll learn the recovery process without risking your real funds.
</p>
<div className="bg-[#0a0a0f] border border-[#ff006e] rounded-lg p-4">
<h3 className="text-[#ff006e] font-bold mb-2">What You'll Learn:</h3>
<ul className="text-sm text-[#6ef3f7] space-y-2">
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-[#39ff14] shrink-0 mt-0.5" />
<span>How to operate the recovery kit files</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-[#39ff14] shrink-0 mt-0.5" />
<span>Different recovery methods for PGP, KRUX, and SEED QR</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-[#39ff14] shrink-0 mt-0.5" />
<span>How to decrypt backups without the SeedPGP website</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-4 h-4 text-[#39ff14] shrink-0 mt-0.5" />
<span>Offline recovery procedures</span>
</li>
</ul>
</div>
<div className="flex items-start gap-3 p-4 bg-[#16213e] border border-[#00f0ff]/30 rounded-lg">
<Info className="w-5 h-5 text-[#00f0ff] shrink-0 mt-0.5" />
<div>
<p className="text-sm text-[#6ef3f7]">
<strong className="text-[#00f0ff]">Tip:</strong> You can use the security settings from the main app or use test defaults.
Practice multiple paths to become proficient with all recovery methods.
</p>
</div>
</div>
</div>
<div className="bg-[#0a0a0f] border border-[#ff006e] rounded-lg p-4"> <div className="space-y-4">
<h3 className="text-[#ff006e] font-bold mb-2">What You'll Do:</h3> <div className="flex items-center justify-between">
<ol className="text-sm text-[#6ef3f7] space-y-1 list-decimal list-inside"> <h3 className="text-lg font-bold text-[#00f0ff]">Settings</h3>
<li>Generate a dummy test seed</li> <button
<li>Encrypt it with a test password</li> onClick={() => setUseExternalSettings(!useExternalSettings)}
<li>Download the recovery kit</li> className={`px-4 py-2 rounded-lg flex items-center gap-2 ${useExternalSettings ? 'bg-[#00f0ff] text-[#0a0a0f]' : 'bg-[#16213e] border-2 border-[#00f0ff]/50 text-[#00f0ff]'}`}
<li>Clear the seed from your browser</li> >
<li>Follow recovery instructions to decrypt</li> <Settings className="w-4 h-4" />
<li>Verify you got the correct seed back</li> {useExternalSettings ? 'Using App Settings' : 'Use Test Defaults'}
</ol> </button>
</div>
{useExternalSettings && (
<div className="p-4 bg-[#0a0a0f] rounded-lg border border-[#00f0ff]/30">
<p className="text-sm text-[#6ef3f7]">
Using security settings from the main app: <strong className="text-[#00f0ff]">{encryptionMode}</strong>
</p>
</div>
)}
</div> </div>
<button <button
onClick={generateDummySeed} onClick={() => setCurrentStep('path-select')}
disabled={loading} disabled={loading}
className="w-full py-3 bg-gradient-to-r from-[#ff006e] to-[#ff4d8f] text-white rounded-xl font-bold uppercase flex items-center justify-center gap-2" className="w-full py-3 bg-gradient-to-r from-[#ff006e] to-[#ff4d8f] text-white rounded-xl font-bold uppercase flex items-center justify-center gap-2"
> >
@@ -178,13 +417,73 @@ export const TestRecovery: React.FC = () => {
) : ( ) : (
<PlayCircle size={20} /> <PlayCircle size={20} />
)} )}
{loading ? 'Generating...' : 'Start Test Recovery Drill'} {loading ? 'Loading...' : 'Start Test Recovery Drill'}
</button> </button>
</div> </div>
)} )}
{currentStep === 'path-select' && (
<div className="space-y-6">
<div className="text-center">
<h3 className="text-xl font-bold text-[#00f0ff] mb-2">Choose Practice Path</h3>
<p className="text-[#6ef3f7]">
Select which encryption method you want to practice recovering from:
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{(['pgp', 'krux', 'seedqr', 'encrypt-seedqr'] as PracticePath[]).map((path) => {
const desc = getPathDescription(path);
return (
<button
key={path}
onClick={() => {
setSelectedPath(path);
setCurrentStep('generate');
}}
className={`p-4 rounded-xl border-2 text-left transition-all ${selectedPath === path
? 'bg-[#1a1a2e] border-[#ff006e] shadow-[0_0_20px_rgba(255,0,110,0.5)]'
: 'bg-[#16213e] border-[#00f0ff]/30 hover:border-[#00f0ff] hover:bg-[#1a1a2e]'
}`}
>
<div className="flex items-start gap-3">
<div className={`p-2 rounded-lg ${selectedPath === path ? 'bg-[#ff006e] text-white' : 'bg-[#00f0ff]/20 text-[#00f0ff]'}`}>
{desc.icon}
</div>
<div>
<h4 className="font-bold text-[#00f0ff]">{desc.title}</h4>
<p className="text-xs text-[#6ef3f7] mt-1">{desc.description}</p>
</div>
</div>
</button>
);
})}
</div>
<div className="flex gap-3">
<button
onClick={() => setCurrentStep('intro')}
className="flex-1 py-3 bg-[#16213e] border-2 border-[#00f0ff]/50 text-[#00f0ff] rounded-xl font-bold flex items-center justify-center gap-2"
>
<ChevronLeft size={20} />
Back
</button>
</div>
</div>
)}
{currentStep === 'generate' && ( {currentStep === 'generate' && (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-[#00f0ff]/20 rounded-lg">
{getPathDescription(selectedPath).icon}
</div>
<div>
<h3 className="text-[#00f0ff] font-bold">Practicing: {getPathDescription(selectedPath).title}</h3>
<p className="text-xs text-[#6ef3f7]">{getPathDescription(selectedPath).description}</p>
</div>
</div>
<CheckCircle2 className="text-[#39ff14]" size={32} /> <CheckCircle2 className="text-[#39ff14]" size={32} />
<h3 className="text-[#39ff14] font-bold">Step 1: Dummy Seed Generated</h3> <h3 className="text-[#39ff14] font-bold">Step 1: Dummy Seed Generated</h3>
<div className="bg-[#0a0a0f] p-4 rounded-lg"> <div className="bg-[#0a0a0f] p-4 rounded-lg">
@@ -211,9 +510,27 @@ export const TestRecovery: React.FC = () => {
<CheckCircle2 className="text-[#39ff14]" size={32} /> <CheckCircle2 className="text-[#39ff14]" size={32} />
<h3 className="text-[#39ff14] font-bold">Step 2: Seed Encrypted</h3> <h3 className="text-[#39ff14] font-bold">Step 2: Seed Encrypted</h3>
<div className="bg-[#0a0a0f] p-4 rounded-lg"> <div className="bg-[#0a0a0f] p-4 rounded-lg">
<p className="text-xs text-[#6ef3f7] mb-2">Test Password:</p> <p className="text-xs text-[#6ef3f7] mb-2">Encryption Details:</p>
<p className="font-mono text-sm text-[#00f0ff]">{testPassword}</p> <div className="space-y-2">
<p className="text-xs text-[#6ef3f7] mt-2">Seed has been encrypted with PGP using password-based encryption.</p> <div>
<span className="text-xs text-[#6ef3f7]">Path: </span>
<span className="text-sm text-[#00f0ff] font-bold">{getPathDescription(selectedPath).title}</span>
</div>
<div>
<span className="text-xs text-[#6ef3f7]">Method: </span>
<span className="text-sm text-[#00f0ff]">
{selectedPath === 'seedqr' ? 'Unencrypted QR' :
selectedPath === 'encrypt-seedqr' ? 'Encrypted QR' :
publicKeyInput ? 'PGP Public Key' : 'Password-based'}
</span>
</div>
{backupMessagePassword && (
<div>
<span className="text-xs text-[#6ef3f7]">Password: </span>
<span className="font-mono text-sm text-[#00f0ff]">{backupMessagePassword}</span>
</div>
)}
</div>
</div> </div>
<button <button
onClick={downloadRecoveryKit} onClick={downloadRecoveryKit}
@@ -236,15 +553,49 @@ export const TestRecovery: React.FC = () => {
<h3 className="text-[#39ff14] font-bold">Step 3: Recovery Kit Downloaded</h3> <h3 className="text-[#39ff14] font-bold">Step 3: Recovery Kit Downloaded</h3>
<div className="bg-[#0a0a0f] p-4 rounded-lg"> <div className="bg-[#0a0a0f] p-4 rounded-lg">
<p className="text-sm text-[#6ef3f7]"> <p className="text-sm text-[#6ef3f7]">
The recovery kit ZIP file has been downloaded to your computer. It contains: The recovery kit ZIP file has been downloaded to your computer.
</p> </p>
<ul className="text-xs text-[#6ef3f7] list-disc list-inside mt-2 space-y-1">
<li>Encrypted backup file</li> <div className="mt-4 space-y-3">
<li>Recovery scripts (Python/Bash)</li> <button
<li>Personalized instructions</li> onClick={() => setShowRecoveryKitDetails(!showRecoveryKitDetails)}
<li>BIP39 wordlist</li> className="w-full py-2 bg-[#16213e] border-2 border-[#00f0ff]/50 text-[#00f0ff] rounded-lg flex items-center justify-center gap-2"
<li>Metadata file</li> >
</ul> <FolderOpen size={16} />
{showRecoveryKitDetails ? 'Hide Kit Contents' : 'Show Kit Contents'}
</button>
{showRecoveryKitDetails && (
<div className="p-3 bg-[#0a0a0f] border border-[#00f0ff]/30 rounded-lg">
<h4 className="text-xs font-bold text-[#00f0ff] mb-2">Recovery Kit Contents:</h4>
<ul className="text-xs text-[#6ef3f7] space-y-1">
{getRecoveryKitFiles().map((file, index) => (
<li key={index} className="flex items-start gap-2">
<ChevronRight className="w-3 h-3 text-[#00f0ff] shrink-0 mt-0.5" />
<span>{file}</span>
</li>
))}
</ul>
</div>
)}
<button
onClick={() => setShowRecoveryInstructions(!showRecoveryInstructions)}
className="w-full py-2 bg-[#16213e] border-2 border-[#39ff14]/50 text-[#39ff14] rounded-lg flex items-center justify-center gap-2"
>
<BookOpen size={16} />
{showRecoveryInstructions ? 'Hide Instructions' : 'Show Recovery Instructions'}
</button>
{showRecoveryInstructions && (
<div className="p-3 bg-[#0a0a0f] border border-[#39ff14]/30 rounded-lg">
<h4 className="text-xs font-bold text-[#39ff14] mb-2">How to Use Recovery Kit:</h4>
<pre className="text-xs text-[#6ef3f7] whitespace-pre-wrap font-mono">
{getRecoveryInstructions()}
</pre>
</div>
)}
</div>
</div> </div>
<button <button
onClick={clearDummySeed} onClick={clearDummySeed}
@@ -292,7 +643,7 @@ export const TestRecovery: React.FC = () => {
<p className="text-xs text-[#6ef3f7] mb-2">Recovered Seed:</p> <p className="text-xs text-[#6ef3f7] mb-2">Recovered Seed:</p>
<p className="font-mono text-sm text-[#00f0ff]">{recoveredSeed}</p> <p className="font-mono text-sm text-[#00f0ff]">{recoveredSeed}</p>
<p className="text-xs text-[#6ef3f7] mt-2"> <p className="text-xs text-[#6ef3f7] mt-2">
The seed has been successfully decrypted from the backup using the test password. The seed has been successfully decrypted from the backup using the {selectedPath === 'seedqr' ? 'QR decoding' : 'password'}.
</p> </p>
</div> </div>
<button <button
@@ -324,14 +675,7 @@ export const TestRecovery: React.FC = () => {
</div> </div>
</div> </div>
<button <button
onClick={() => { onClick={verifyRecovery}
if (recoveredSeed === dummySeed) {
setCurrentStep('complete');
alert('🎉 SUCCESS! You successfully recovered the seed phrase!');
} else {
alert('❌ FAILED: Recovered seed does not match original. Try again.');
}
}}
className="w-full py-3 bg-gradient-to-r from-[#39ff14] to-[#00ff88] text-[#0a0a0f] rounded-xl font-bold" className="w-full py-3 bg-gradient-to-r from-[#39ff14] to-[#00ff88] text-[#0a0a0f] rounded-xl font-bold"
> >
Verify Match Verify Match
@@ -352,7 +696,7 @@ export const TestRecovery: React.FC = () => {
<ul className="text-sm text-[#6ef3f7] space-y-1 text-left"> <ul className="text-sm text-[#6ef3f7] space-y-1 text-left">
<li> You can decrypt backups without the SeedPGP website</li> <li> You can decrypt backups without the SeedPGP website</li>
<li> The recovery kit contains everything needed</li> <li> The recovery kit contains everything needed</li>
<li> You understand the recovery process</li> <li> You understand the recovery process for {getPathDescription(selectedPath).title}</li>
<li> Your real backups are recoverable</li> <li> Your real backups are recoverable</li>
</ul> </ul>
</div> </div>
@@ -372,6 +716,7 @@ export const TestRecovery: React.FC = () => {
<span>Progress:</span> <span>Progress:</span>
<span> <span>
{currentStep === 'intro' && '0/7'} {currentStep === 'intro' && '0/7'}
{currentStep === 'path-select' && '0/7'}
{currentStep === 'generate' && '1/7'} {currentStep === 'generate' && '1/7'}
{currentStep === 'encrypt' && '2/7'} {currentStep === 'encrypt' && '2/7'}
{currentStep === 'download' && '3/7'} {currentStep === 'download' && '3/7'}
@@ -385,7 +730,7 @@ export const TestRecovery: React.FC = () => {
<div <div
className="bg-gradient-to-r from-[#00f0ff] to-[#00c8ff] h-2 rounded-full transition-all duration-300" className="bg-gradient-to-r from-[#00f0ff] to-[#00c8ff] h-2 rounded-full transition-all duration-300"
style={{ style={{
width: currentStep === 'intro' ? '0%' : width: currentStep === 'intro' || currentStep === 'path-select' ? '0%' :
currentStep === 'generate' ? '14%' : currentStep === 'generate' ? '14%' :
currentStep === 'encrypt' ? '28%' : currentStep === 'encrypt' ? '28%' :
currentStep === 'download' ? '42%' : currentStep === 'download' ? '42%' :