mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-06 09:27:51 +08:00
fixed the recovery test guide
This commit is contained in:
@@ -1112,7 +1112,14 @@ function App() {
|
||||
</div>
|
||||
|
||||
<div className={activeTab === 'test-recovery' ? 'block' : 'hidden'}>
|
||||
<TestRecovery />
|
||||
<TestRecovery
|
||||
encryptionMode={encryptionMode}
|
||||
backupMessagePassword={backupMessagePassword}
|
||||
restoreMessagePassword={restoreMessagePassword}
|
||||
publicKeyInput={publicKeyInput}
|
||||
privateKeyInput={privateKeyInput}
|
||||
privateKeyPassphrase={privateKeyPassphrase}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,49 +1,108 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AlertCircle, CheckCircle2, PlayCircle, RefreshCw, Package, Lock, Unlock } from 'lucide-react';
|
||||
import React, { useState, useEffect } from '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 { encryptToSeed, decryptFromSeed } from '../lib/seedpgp';
|
||||
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 [selectedPath, setSelectedPath] = useState<PracticePath>('pgp');
|
||||
const [dummySeed, setDummySeed] = useState('');
|
||||
const [testPassword, setTestPassword] = useState('TestPassword123!');
|
||||
const [recoveredSeed, setRecoveredSeed] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [encryptedBackup, setEncryptedBackup] = useState<string>('');
|
||||
const [showRecoveryKitDetails, setShowRecoveryKitDetails] = useState(false);
|
||||
const [showRecoveryInstructions, setShowRecoveryInstructions] = useState(false);
|
||||
const [useExternalSettings, setUseExternalSettings] = useState(false);
|
||||
|
||||
const generateDummySeed = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
// Generate a random 12-word BIP39 mnemonic for testing
|
||||
const entropy = crypto.getRandomValues(new Uint8Array(16));
|
||||
const mnemonic = await entropyToMnemonic(entropy);
|
||||
|
||||
setDummySeed(mnemonic);
|
||||
setCurrentStep('encrypt');
|
||||
} catch (err: any) {
|
||||
setError(`Failed to generate dummy seed: ${err.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Use external settings if enabled
|
||||
const encryptionMode = useExternalSettings ? externalEncryptionMode : 'pgp';
|
||||
const backupMessagePassword = useExternalSettings ? externalBackupPassword : testPassword;
|
||||
const restoreMessagePassword = useExternalSettings ? externalRestorePassword : testPassword;
|
||||
const publicKeyInput = useExternalSettings ? externalPublicKey : '';
|
||||
const privateKeyInput = useExternalSettings ? externalPrivateKey : '';
|
||||
const privateKeyPassphrase = useExternalSettings ? externalPrivateKeyPassphrase : '';
|
||||
|
||||
// Generate dummy seed when step changes to 'generate'
|
||||
useEffect(() => {
|
||||
const generateDummySeed = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
// 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 () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
// Encrypt using the same logic as real backups
|
||||
const result = await encryptToSeed({
|
||||
plaintext: dummySeed,
|
||||
messagePassword: testPassword,
|
||||
mode: 'pgp',
|
||||
});
|
||||
let result;
|
||||
|
||||
if (selectedPath === 'seedqr') {
|
||||
// For SEED QR practice (unencrypted)
|
||||
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
|
||||
setEncryptedBackup(result.framed as string);
|
||||
@@ -60,19 +119,27 @@ export const TestRecovery: React.FC = () => {
|
||||
setLoading(true);
|
||||
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
|
||||
const kitBlob = await generateRecoveryKit({
|
||||
encryptedData: encryptedBackup,
|
||||
encryptionMode: 'pgp',
|
||||
encryptionMethod: 'password',
|
||||
qrImageDataUrl: undefined, // No QR image for test
|
||||
encryptionMode: selectedPath === 'krux' ? 'krux' : 'pgp',
|
||||
encryptionMethod,
|
||||
qrImageDataUrl: undefined,
|
||||
});
|
||||
|
||||
// Trigger download
|
||||
const url = URL.createObjectURL(kitBlob);
|
||||
const a = document.createElement('a');
|
||||
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();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
@@ -98,12 +165,32 @@ export const TestRecovery: React.FC = () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
// Decrypt using recovery instructions
|
||||
const result = await decryptFromSeed({
|
||||
frameText: encryptedBackup,
|
||||
messagePassword: testPassword,
|
||||
mode: 'pgp',
|
||||
});
|
||||
let result;
|
||||
|
||||
if (selectedPath === 'seedqr') {
|
||||
// For SEED QR (unencrypted) - decode directly
|
||||
// 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);
|
||||
setCurrentStep('verify');
|
||||
@@ -125,15 +212,124 @@ export const TestRecovery: React.FC = () => {
|
||||
|
||||
const resetTest = () => {
|
||||
setCurrentStep('intro');
|
||||
setSelectedPath('pgp');
|
||||
setDummySeed('');
|
||||
setTestPassword('TestPassword123!');
|
||||
setRecoveredSeed('');
|
||||
setEncryptedBackup('');
|
||||
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 (
|
||||
<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">
|
||||
<h2 className="text-2xl font-bold text-[#00f0ff] mb-4">
|
||||
🧪 Test Your Recovery Ability
|
||||
@@ -150,26 +346,69 @@ export const TestRecovery: React.FC = () => {
|
||||
)}
|
||||
|
||||
{currentStep === 'intro' && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-[#6ef3f7]">
|
||||
This drill will help you practice recovering a seed phrase from an encrypted backup.
|
||||
You'll learn the recovery process without risking your real funds.
|
||||
</p>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<p className="text-[#6ef3f7]">
|
||||
This drill will help you practice recovering a seed phrase from different types of encrypted backups.
|
||||
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">
|
||||
<h3 className="text-[#ff006e] font-bold mb-2">What You'll Do:</h3>
|
||||
<ol className="text-sm text-[#6ef3f7] space-y-1 list-decimal list-inside">
|
||||
<li>Generate a dummy test seed</li>
|
||||
<li>Encrypt it with a test password</li>
|
||||
<li>Download the recovery kit</li>
|
||||
<li>Clear the seed from your browser</li>
|
||||
<li>Follow recovery instructions to decrypt</li>
|
||||
<li>Verify you got the correct seed back</li>
|
||||
</ol>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-[#00f0ff]">Settings</h3>
|
||||
<button
|
||||
onClick={() => setUseExternalSettings(!useExternalSettings)}
|
||||
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]'}`}
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
{useExternalSettings ? 'Using App Settings' : 'Use Test Defaults'}
|
||||
</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>
|
||||
|
||||
<button
|
||||
onClick={generateDummySeed}
|
||||
onClick={() => setCurrentStep('path-select')}
|
||||
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"
|
||||
>
|
||||
@@ -178,13 +417,73 @@ export const TestRecovery: React.FC = () => {
|
||||
) : (
|
||||
<PlayCircle size={20} />
|
||||
)}
|
||||
{loading ? 'Generating...' : 'Start Test Recovery Drill'}
|
||||
{loading ? 'Loading...' : 'Start Test Recovery Drill'}
|
||||
</button>
|
||||
</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' && (
|
||||
<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} />
|
||||
<h3 className="text-[#39ff14] font-bold">Step 1: Dummy Seed Generated</h3>
|
||||
<div className="bg-[#0a0a0f] p-4 rounded-lg">
|
||||
@@ -211,9 +510,27 @@ export const TestRecovery: React.FC = () => {
|
||||
<CheckCircle2 className="text-[#39ff14]" size={32} />
|
||||
<h3 className="text-[#39ff14] font-bold">Step 2: Seed Encrypted</h3>
|
||||
<div className="bg-[#0a0a0f] p-4 rounded-lg">
|
||||
<p className="text-xs text-[#6ef3f7] mb-2">Test Password:</p>
|
||||
<p className="font-mono text-sm text-[#00f0ff]">{testPassword}</p>
|
||||
<p className="text-xs text-[#6ef3f7] mt-2">Seed has been encrypted with PGP using password-based encryption.</p>
|
||||
<p className="text-xs text-[#6ef3f7] mb-2">Encryption Details:</p>
|
||||
<div className="space-y-2">
|
||||
<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>
|
||||
<button
|
||||
onClick={downloadRecoveryKit}
|
||||
@@ -236,15 +553,49 @@ export const TestRecovery: React.FC = () => {
|
||||
<h3 className="text-[#39ff14] font-bold">Step 3: Recovery Kit Downloaded</h3>
|
||||
<div className="bg-[#0a0a0f] p-4 rounded-lg">
|
||||
<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>
|
||||
<ul className="text-xs text-[#6ef3f7] list-disc list-inside mt-2 space-y-1">
|
||||
<li>Encrypted backup file</li>
|
||||
<li>Recovery scripts (Python/Bash)</li>
|
||||
<li>Personalized instructions</li>
|
||||
<li>BIP39 wordlist</li>
|
||||
<li>Metadata file</li>
|
||||
</ul>
|
||||
|
||||
<div className="mt-4 space-y-3">
|
||||
<button
|
||||
onClick={() => setShowRecoveryKitDetails(!showRecoveryKitDetails)}
|
||||
className="w-full py-2 bg-[#16213e] border-2 border-[#00f0ff]/50 text-[#00f0ff] rounded-lg flex items-center justify-center gap-2"
|
||||
>
|
||||
<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>
|
||||
<button
|
||||
onClick={clearDummySeed}
|
||||
@@ -292,7 +643,7 @@ export const TestRecovery: React.FC = () => {
|
||||
<p className="text-xs text-[#6ef3f7] mb-2">Recovered Seed:</p>
|
||||
<p className="font-mono text-sm text-[#00f0ff]">{recoveredSeed}</p>
|
||||
<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>
|
||||
</div>
|
||||
<button
|
||||
@@ -324,14 +675,7 @@ export const TestRecovery: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (recoveredSeed === dummySeed) {
|
||||
setCurrentStep('complete');
|
||||
alert('🎉 SUCCESS! You successfully recovered the seed phrase!');
|
||||
} else {
|
||||
alert('❌ FAILED: Recovered seed does not match original. Try again.');
|
||||
}
|
||||
}}
|
||||
onClick={verifyRecovery}
|
||||
className="w-full py-3 bg-gradient-to-r from-[#39ff14] to-[#00ff88] text-[#0a0a0f] rounded-xl font-bold"
|
||||
>
|
||||
Verify Match
|
||||
@@ -352,7 +696,7 @@ export const TestRecovery: React.FC = () => {
|
||||
<ul className="text-sm text-[#6ef3f7] space-y-1 text-left">
|
||||
<li>✅ You can decrypt backups without the SeedPGP website</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>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -372,6 +716,7 @@ export const TestRecovery: React.FC = () => {
|
||||
<span>Progress:</span>
|
||||
<span>
|
||||
{currentStep === 'intro' && '0/7'}
|
||||
{currentStep === 'path-select' && '0/7'}
|
||||
{currentStep === 'generate' && '1/7'}
|
||||
{currentStep === 'encrypt' && '2/7'}
|
||||
{currentStep === 'download' && '3/7'}
|
||||
@@ -385,7 +730,7 @@ export const TestRecovery: React.FC = () => {
|
||||
<div
|
||||
className="bg-gradient-to-r from-[#00f0ff] to-[#00c8ff] h-2 rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: currentStep === 'intro' ? '0%' :
|
||||
width: currentStep === 'intro' || currentStep === 'path-select' ? '0%' :
|
||||
currentStep === 'generate' ? '14%' :
|
||||
currentStep === 'encrypt' ? '28%' :
|
||||
currentStep === 'download' ? '42%' :
|
||||
|
||||
Reference in New Issue
Block a user