Restores the `encrypt`, `bytesToHex`, and `encryptToKrux` functions that were accidentally removed in a previous refactor. These functions are used by other parts of the application (`seedpgp.ts` and tests) and their absence caused a 'binding name not found' build error. This commit restores the original functionality, ensuring the application builds correctly and all features work as intended.
SeedPGP v1.4.4
Secure BIP39 mnemonic backup using PGP encryption and QR codes
A client-side web app for encrypting cryptocurrency seed phrases with OpenPGP and encoding them as QR-friendly Base45 frames with CRC16 integrity checking.
Live App: https://seedpgp-web.pages.dev
✨ Quick Start
🔒 Backup Your Seed (in 30 seconds)
-
Run locally (recommended for maximum security):
git clone https://github.com/kccleoc/seedpgp-web.git cd seedpgp-web bun install bun run dev # Open http://localhost:5173 -
Enter your 12/24-word BIP39 mnemonic
-
Choose encryption method:
- Option A: Upload your PGP public key (
.ascfile or paste) - Option B: Set a strong password (AES-256 encryption)
- Option A: Upload your PGP public key (
-
Click "Generate QR Backup" → Save/print the QR code
🔓 Restore Your Seed
-
Scan the QR code (camera or upload image)
-
Provide decryption key:
- PGP private key + passphrase (if using PGP)
- Password (if using password encryption)
-
Mnemonic appears for 10 seconds → auto-clears for security
🛡️ Explicit Threat Model Documentation
🎯 What SeedPGP Protects Against (Security Guarantees)
SeedPGP is designed to protect against specific threats when used correctly:
| Threat | Protection | Implementation Details |
|---|---|---|
| Accidental browser storage | Real-time monitoring & alerts for localStorage/sessionStorage | StorageDetails component shows all browser storage activity |
| Clipboard exposure | Clipboard tracking with warnings and history clearing | ClipboardDetails tracks all copy operations, shows what/when |
| Network leaks | Strict CSP headers blocking ALL external requests | Cloudflare Pages enforces CSP: default-src 'self'; connect-src 'none' |
| Wrong-key usage | Key fingerprint validation prevents wrong-key decryption | OpenPGP.js validates recipient fingerprints before decryption |
| QR corruption | CRC16-CCITT-FALSE checksum detects scanning/printing errors | Frame format includes 4-digit hex CRC for integrity verification |
| Memory persistence | Session-key encryption with auto-clear timers | AES-GCM-256 session keys, 10-second auto-clear for restored mnemonics |
| Shoulder surfing | Read-only mode blurs sensitive data, disables inputs | Toggle blurs content, disables form inputs, prevents clipboard operations |
⚠️ Critical Limitations & What SeedPGP CANNOT Protect Against
IMPORTANT: Understand these limitations before trusting SeedPGP with significant funds:
| Threat | Reason | Recommended Mitigation |
|---|---|---|
| Browser extensions | Malicious extensions can read DOM, memory, keystrokes | Use dedicated browser with all extensions disabled; consider browser isolation |
| Memory analysis | JavaScript cannot force immediate memory wiping; strings may persist in RAM | Use airgapped device, reboot after use, consider hardware wallets |
| XSS attacks | If hosting server is compromised, malicious JS could be injected | Host locally from verified source, use Subresource Integrity (SRI) checks |
| Hardware keyloggers | Physical device compromise at hardware/firmware level | Use trusted hardware, consider hardware wallets for large amounts |
| Supply chain attacks | Compromised dependencies (OpenPGP.js, React, etc.) | Audit dependencies regularly, verify checksums, consider reproducible builds |
| Quantum computers | Future threat to current elliptic curve cryptography | Store encrypted backups physically, rotate periodically, monitor crypto developments |
| Browser bugs/exploits | Zero-day vulnerabilities in browser rendering engine | Keep browsers updated, use security-focused browsers (Brave, Tor) |
| Screen recording | Malware or built-in OS screen recording | Use privacy screens, be aware of surroundings during sensitive operations |
| Timing attacks | Potential side-channel attacks on JavaScript execution | Use constant-time algorithms where possible, though limited in browser context |
🔬 Technical Security Architecture
Encryption Stack:
- PGP Encryption: OpenPGP.js with AES-256 (OpenPGP standard)
- Session Keys: Web Crypto API AES-GCM-256 with
extractable: false - Key Derivation: PBKDF2 for password-based keys (when used)
- Integrity: CRC16-CCITT-FALSE checksums on all frames
- Encoding: Base45 (RFC 9285) for QR-friendly representation
Memory Management Limitations:
- JavaScript strings are immutable and may persist in memory after "clearing"
- Garbage collection timing is non-deterministic and implementation-dependent
- Browser crash dumps may contain sensitive data in memory
- The best practice is to minimize exposure time and use airgapped devices
🏆 Best Practices for Maximum Security
-
Airgapped Workflow (Recommended for large amounts):
[Online Device] → Generate PGP keypair → Export public key [Airgapped Device] → Run SeedPGP locally → Encrypt with public key [Airgapped Device] → Print QR code → Store physically [Online Device] → Never touches private key or plaintext seed -
Local Execution (Next best):
# Clone and run offline git clone https://github.com/kccleoc/seedpgp-web.git cd seedpgp-web bun install # Disable network, then run bun run dev -- --host 127.0.0.1 -
Cloudflare Pages (Convenient but trust required):
- ✅ Real CSP enforcement (blocks network at browser level)
- ✅ Security headers (X-Frame-Options, X-Content-Type-Options)
- ⚠️ Trusts Cloudflare infrastructure
- ⚠️ Requires HTTPS connection
📚 Simple Usage Examples
Example 1: Password-only Encryption (Simplest)
import { encryptToSeed, decryptFromSeed } from "./lib/seedpgp";
// Backup with password
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const result = await encryptToSeed({
plaintext: mnemonic,
messagePassword: "MyStrongPassword123!",
});
console.log(result.framed); // "SEEDPGP1:0:ABCD:BASE45DATA..."
// Restore with password
const restored = await decryptFromSeed({
frameText: result.framed,
messagePassword: "MyStrongPassword123!",
});
console.log(restored.w); // Original mnemonic
Example 2: PGP Key Encryption (More Secure)
import { encryptToSeed, decryptFromSeed } from "./lib/seedpgp";
const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
... your public key here ...
-----END PGP PUBLIC KEY BLOCK-----`;
const privateKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
... your private key here ...
-----END PGP PRIVATE KEY BLOCK-----`;
// Backup with PGP key
const result = await encryptToSeed({
plaintext: mnemonic,
publicKeyArmored: publicKey,
});
// Restore with PGP key
const restored = await decryptFromSeed({
frameText: result.framed,
privateKeyArmored: privateKey,
privateKeyPassphrase: "your-key-password",
});
Example 3: Krux-Compatible Encryption (Hardware Wallet Users)
import { encryptToSeed, decryptFromSeed } from "./lib/seedpgp";
// Krux mode uses passphrase-only encryption
const result = await encryptToSeed({
plaintext: mnemonic,
messagePassword: "MyStrongPassphrase",
mode: 'krux',
kruxLabel: 'Main Wallet Backup',
kruxIterations: 200000,
});
// Hex format compatible with Krux firmware
console.log(result.framed); // Hex string starting with KEF:
🔧 Installation & Development
Prerequisites
- Bun v1.3.6+ (recommended) or Node.js 18+
- Git
Quick Install
# Clone and install
git clone https://github.com/kccleoc/seedpgp-web.git
cd seedpgp-web
bun install
# Run tests
bun test
# Start development server
bun run dev
# Open http://localhost:5173
Production Build
bun run build # Build to dist/
bun run preview # Preview production build
🔐 Advanced Security Features
Session-Key Encryption
- AES-GCM-256 ephemeral keys for in-memory protection
- Auto-destroys on tab close/navigation
- Manual lock/clear button for immediate wiping
Storage Monitoring
- Real-time tracking of localStorage/sessionStorage
- Alerts for sensitive data detection
- Visual indicators of storage usage
Clipboard Protection
- Tracks all copy operations
- Shows what was copied and when
- One-click history clearing
Read-Only Mode
- Blurs all sensitive data
- Disables all inputs
- Prevents clipboard operations
- Perfect for demonstrations or shared screens
📖 API Reference
Core Functions
encryptToSeed(params)
Encrypts a mnemonic to SeedPGP format.
interface EncryptionParams {
plaintext: string | SeedPgpPlaintext; // Mnemonic or plaintext object
publicKeyArmored?: string; // PGP public key (optional)
messagePassword?: string; // Password (optional)
mode?: 'pgp' | 'krux'; // Encryption mode
kruxLabel?: string; // Label for Krux mode
kruxIterations?: number; // PBKDF2 iterations for Krux
}
const result = await encryptToSeed({
plaintext: "your mnemonic here",
messagePassword: "optional-password",
});
// Returns: { framed: string, pgpBytes?: Uint8Array, recipientFingerprint?: string }
decryptFromSeed(params)
Decrypts a SeedPGP frame.
interface DecryptionParams {
frameText: string; // SEEDPGP1 frame or KEF hex
privateKeyArmored?: string; // PGP private key (optional)
privateKeyPassphrase?: string; // Key password (optional)
messagePassword?: string; // Message password (optional)
mode?: 'pgp' | 'krux'; // Encryption mode
}
const plaintext = await decryptFromSeed({
frameText: "SEEDPGP1:0:ABCD:...",
messagePassword: "your-password",
});
// Returns: SeedPgpPlaintext { v: 1, t: "bip39", w: string, l: "en", pp: number }
Frame Format
SEEDPGP1:FRAME:CRC16:BASE45DATA
└────────┬────────┘ └──┬──┘ └─────┬─────┘
Protocol & Frame CRC16 Base45-encoded
Version Number Check PGP Message
Examples:
• SEEDPGP1:0:ABCD:J9ESODB... # Single frame
• KEF:0123456789ABCDEF... # Krux Encryption Format (hex)
🚀 Deployment Options
Option 1: Localhost (Most Secure)
# Run on airgapped machine
bun run dev -- --host 127.0.0.1
# Browser only connects to localhost, no external traffic
Option 2: Self-Hosted (Balanced)
- Build:
bun run build - Serve
dist/via NGINX/Apache with HTTPS - Set CSP headers (see
public/_headers)
Option 3: Cloudflare Pages (Convenient)
- Auto-deploys from GitHub
- Built-in CDN and security headers
- seedpgp-web.pages.dev
🧪 Testing & Verification
Test Suite
# Run all tests
bun test
# Run specific test categories
bun test --test-name-pattern="Trezor" # BIP39 test vectors
bun test --test-name-pattern="CRC" # Integrity checks
bun test --test-name-pattern="Krux" # Krux compatibility
# Watch mode (development)
bun test --watch
Test Coverage
- ✅ 15 comprehensive tests including edge cases
- ✅ 8 official Trezor BIP39 test vectors
- ✅ CRC16 integrity validation (corruption detection)
- ✅ Wrong key/password rejection testing
- ✅ Frame format parsing (malformed input handling)
📁 Project Structure
seedpgp-web/
├── src/
│ ├── components/ # React UI components
│ │ ├── PgpKeyInput.tsx # PGP key import (drag & drop)
│ │ ├── QrDisplay.tsx # QR code generation
│ │ ├── QRScanner.tsx # Camera + file scanning
│ │ ├── SecurityWarnings.tsx # Threat model display
│ │ ├── StorageDetails.tsx # Storage monitoring
│ │ └── ClipboardDetails.tsx # Clipboard tracking
│ ├── lib/
│ │ ├── seedpgp.ts # Core encryption/decryption
│ │ ├── sessionCrypto.ts # AES-GCM session key management
│ │ ├── krux.ts # Krux KEF compatibility
│ │ ├── bip39.ts # BIP39 validation
│ │ ├── base45.ts # Base45 encoding/decoding
│ │ └── crc16.ts # CRC16-CCITT-FALSE checksums
│ ├── App.tsx # Main application
│ └── main.tsx # React entry point
├── public/
│ └── _headers # Cloudflare security headers
├── package.json
├── vite.config.ts
├── RECOVERY_PLAYBOOK.md # Offline recovery guide
└── README.md # This file
🔄 Version History
v1.4.4 (2026-02-03)
- ✅ Enhanced security documentation with explicit threat model
- ✅ Improved README with simple examples and best practices
- ✅ Better air-gapped usage guidance for maximum security
- ✅ Version bump with security audit improvements
v1.4.3 (2026-01-30)
- ✅ Fixed textarea contrast for readability
- ✅ Fixed overlapping floating boxes
- ✅ Polished UI with modern crypto wallet design
v1.4.2 (2026-01-30)
- ✅ Migrated to Cloudflare Pages for real CSP enforcement
- ✅ Added "Encrypted in memory" badge
- ✅ Improved security header configuration
v1.4.0 (2026-01-29)
- ✅ Extended session-key encryption to Restore flow
- ✅ Added 10-second auto-clear timer for restored mnemonic
- ✅ Added manual Hide button for immediate clearing
🗺️ Roadmap
Short-term (v1.5.x)
- Enhanced BIP39 validation (full wordlist + checksum)
- Multi-frame support for larger payloads
- Hardware wallet integration (Trezor/Keystone)
Medium-term
- Shamir Secret Sharing support
- Mobile companion app (React Native)
- Printable paper backup templates
- Encrypted cloud backup with PBKDF2
Long-term
- BIP85 child mnemonic derivation
- Quantum-resistant algorithm options
- Cross-platform desktop app (Tauri)
⚖️ License
MIT License - see LICENSE file for details.
👤 Author
kccleoc - GitHub
Security Audit: v1.4.4 audited for vulnerabilities, no exploits found
⚠️ Important Disclaimer
CRYPTOGRAPHY IS HARD. USE AT YOUR OWN RISK.
This software is provided as-is, without warranty of any kind. Always:
- Test with small amounts before trusting with significant funds
- Verify decryption works immediately after creating backups
- Keep multiple backup copies in different physical locations
- Consider professional advice for large cryptocurrency holdings
The author is not responsible for lost funds due to software bugs, user error, or security breaches.
🆘 Getting Help
- Issues: GitHub Issues
- Security Concerns: Private disclosure via GitHub security advisory
- Recovery Help: See RECOVERY_PLAYBOOK.md for offline recovery instructions
Remember: Your seed phrase is the key to your cryptocurrency. Guard it with your life.