2026-01-30 18:39:30 +08:00
2026-01-30 18:39:30 +08:00
2026-01-30 02:13:53 +08:00
2026-01-30 18:39:30 +08:00
2026-01-30 18:39:30 +08:00

SeedPGP v1.4.3

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

Features

  • 🔐 PGP Encryption: Uses cv25519 (Curve25519) for modern elliptic curve cryptography
  • 📱 QR Code Ready: Base45 encoding optimized for QR code generation
  • Integrity Checking: CRC16-CCITT-FALSE checksums prevent corruption
  • 🔑 BIP39 Support: Full support for 12/18/24-word mnemonics with passphrase indicator
  • 🧪 Battle-Tested: Validated against official Trezor BIP39 test vectors
  • Fast: Built with Bun runtime and Vite for optimal performance
  • 🔒 Session-Key Encryption: Ephemeral AES-GCM-256 encryption for in-memory protection
  • 🛡️ CSP Enforcement: Real Content Security Policy headers block all network requests
  • 📸 QR Scanner: Camera and file upload support for scanning encrypted QR codes
  • 👁️ Security Monitoring: Real-time storage monitoring and clipboard tracking

Installation

# Clone repository
git clone https://github.com/kccleoc/seedpgp-web.git
cd seedpgp-web

# Install dependencies
bun install

# Run tests
bun test

# Start development server
bun run dev

Usage

Web Interface

Visit https://seedpgp-web.pages.dev or run locally:

bun run dev
# Open http://localhost:5173

Backup Flow:

  1. Enter your BIP39 mnemonic (12/18/24 words)
  2. Import PGP public key or set encryption password
  3. Click "Backup" to encrypt and generate QR code
  4. Save/print QR code for offline storage

Restore Flow:

  1. Scan QR code or paste encrypted text
  2. Import PGP private key or enter password
  3. Click "Restore" to decrypt mnemonic
  4. Mnemonic auto-clears after 10 seconds

API Usage

import { encryptToSeedPgp, buildPlaintext } from "./lib/seedpgp";

const mnemonic = "legal winner thank year wave sausage worth useful legal winner thank yellow";
const plaintext = buildPlaintext(mnemonic, false); // false = no BIP39 passphrase used

const result = await encryptToSeedPgp({
  plaintext,
  publicKeyArmored: yourPgpPublicKey,
});

console.log(result.framed); // SEEDPGP1:0:ABCD:BASE45DATA...
console.log(result.recipientFingerprint); // Key fingerprint for verification

Decrypt a SeedPGP Frame

import { decryptSeedPgp } from "./lib/seedpgp";

const decrypted = await decryptSeedPgp({
  frameText: "SEEDPGP1:0:ABCD:BASE45DATA...",
  privateKeyArmored: yourPrivateKey,
  privateKeyPassphrase: "your-key-password",
});

console.log(decrypted.w); // Recovered mnemonic
console.log(decrypted.pp); // BIP39 passphrase indicator (0 or 1)

Deployment

Production: Cloudflare Pages (auto-deploys from main branch)
Live URL: https://seedpgp-web.pages.dev

Cloudflare Pages Setup

This project is deployed on Cloudflare Pages for enhanced security features:

  1. Repository: seedpgp-web (private repo)
  2. Build command: bun run build
  3. Output directory: dist/
  4. Security headers: Automatically enforced via public/_headers

Benefits Over GitHub Pages

  • Real CSP header enforcement (blocks network requests at browser level)
  • Custom security headers (X-Frame-Options, X-Content-Type-Options)
  • Auto-deploy on push to main
  • Build preview for PRs
  • Better performance (global CDN)
  • Cost: $0/month

Deployment Workflow

# Commit feature
git add src/
git commit -m "feat(v1.x): description"

# Tag version (triggers auto-deploy to Cloudflare)
git tag v1.x.x
git push origin main --tags

No manual deployment needed! Cloudflare Pages auto-deploys when you push to main.

Frame Format

SEEDPGP1:FRAME:CRC16:BASE45DATA

SEEDPGP1 - Protocol identifier and version
0        - Frame number (0 = single frame)
ABCD     - 4-digit hex CRC16-CCITT-FALSE checksum
BASE45   - Base45-encoded PGP message

API Reference

buildPlaintext(mnemonic, bip39PassphraseUsed, recipientFingerprints?)

Creates a SeedPGP plaintext object.

Parameters:

  • mnemonic (string): BIP39 mnemonic phrase (12/18/24 words)
  • bip39PassphraseUsed (boolean): Whether a BIP39 passphrase was used
  • recipientFingerprints (string[]): Optional array of recipient key fingerprints

Returns: SeedPgpPlaintext object

encryptToSeedPgp(params)

Encrypts a plaintext object to SeedPGP format.

Parameters:

{
  plaintext: SeedPgpPlaintext;
  publicKeyArmored?: string;      // PGP public key (PKESK)
  messagePassword?: string;        // Symmetric password (SKESK)
}

Returns:

{
  framed: string;                  // SEEDPGP1 frame
  pgpBytes: Uint8Array;            // Raw PGP message
  recipientFingerprint?: string;   // Key fingerprint
}

decryptSeedPgp(params)

Decrypts a SeedPGP frame.

Parameters:

{
  frameText: string;                // SEEDPGP1 frame
  privateKeyArmored?: string;       // PGP private key
  privateKeyPassphrase?: string;    // Key unlock password
  messagePassword?: string;         // SKESK password
}

Returns: SeedPgpPlaintext object

Testing

# Run all tests
bun test

# Run with verbose output
bun test --verbose

# Watch mode (auto-rerun on changes)
bun test --watch

Test Coverage

  • 15 comprehensive tests
  • 8 official Trezor BIP39 test vectors
  • Edge cases (wrong key, wrong passphrase)
  • Frame format validation
  • CRC16 integrity checking

Security Considerations

Best Practices

  • Uses AES-256 for symmetric encryption
  • cv25519 provides ~128-bit security level
  • CRC16 detects QR scan errors (not cryptographic)
  • Key fingerprint validation prevents wrong-key usage
  • Session-key encryption: Ephemeral AES-GCM-256 for in-memory protection
  • CSP headers: Browser-enforced network blocking via Cloudflare Pages

⚠️ Important Notes

  • Never share your private key or encrypted QR codes publicly
  • Store backup QR codes in secure physical locations (safe, safety deposit box)
  • Use a strong PGP key passphrase (20+ characters)
  • Test decryption immediately after generating backups
  • Consider password-only (SKESK) encryption as additional fallback

🔒 Production Deployment Warning

The Cloudflare Pages deployment at https://seedpgp-web.pages.dev is for:

  • Personal use with enhanced security
  • CSP enforcement blocks all network requests
  • Convenient access from any device
  • ⚠️ Always verify the URL before use

For maximum security with real funds:

  • Run locally: bun run dev
  • Or self-host on your own domain with HTTPS
  • Use an airgapped device for critical operations

Threat Model (Honest)

What we protect against:

  • Accidental persistence to localStorage/sessionStorage
  • Plaintext secrets lingering in React state after use
  • Clipboard history exposure (with warnings)

What we DON'T protect against:

  • Active XSS or malicious browser extensions
  • Memory dumps or browser crash reports
  • JavaScript garbage collection timing (non-deterministic)

Project Structure

seedpgp-web/
├── src/
│   ├── components/
│   │   ├── PgpKeyInput.tsx         # PGP key import UI
│   │   ├── QrDisplay.tsx           # QR code generation
│   │   ├── QrScanner.tsx           # Camera + file scanner
│   │   ├── ReadOnly.tsx            # Read-only mode toggle
│   │   ├── StorageIndicator.tsx    # Storage monitoring
│   │   ├── SecurityWarnings.tsx    # Context alerts
│   │   └── ClipboardTracker.tsx    # Clipboard monitoring
│   ├── lib/
│   │   ├── seedpgp.ts              # Core encryption/decryption
│   │   ├── seedpgp.test.ts         # Test vectors
│   │   ├── sessionCrypto.ts        # Ephemeral session keys
│   │   ├── base45.ts               # Base45 codec
│   │   ├── crc16.ts                # CRC16-CCITT-FALSE
│   │   ├── qr.ts                   # QR utilities
│   │   └── types.ts                # TypeScript definitions
│   ├── App.tsx                     # Main application
│   └── main.tsx                    # React entry point
├── public/
│   └── _headers                    # Cloudflare CSP headers
├── package.json
├── vite.config.ts                  # Vite configuration
├── GEMINI.md                       # AI agent project brief
└── README.md                       # This file

Tech Stack

  • Runtime: Bun v1.3.6+
  • Language: TypeScript (strict mode)
  • Crypto: OpenPGP.js v6.3.0
  • Framework: React + Vite
  • UI: Tailwind CSS
  • Icons: lucide-react
  • QR: html5-qrcode, qrcode
  • Testing: Bun test runner
  • Deployment: Cloudflare Pages

Version History

v1.4.3 (2026-01-30)

  • Fixed textarea contrast for readability
  • Fixed overlapping floating boxes
  • Polished UI with modern crypto wallet design
  • Updated background color to be lighter

v1.4.2 (2026-01-30)

  • Migrated to Cloudflare Pages for real CSP enforcement
  • Added "Encrypted in memory" badge when mnemonic locked
  • Improved security header configuration
  • Updated deployment documentation

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
  • Removed debug console logs from production

v1.3.0 (2026-01-28)

  • Implemented ephemeral session-key encryption (AES-GCM-256)
  • Auto-clear mnemonic after QR generation (Backup flow)
  • Encrypted cache for sensitive state
  • Manual Lock/Clear functionality

v1.2.0 (2026-01-27)

  • Added storage monitoring (StorageIndicator)
  • Added security warnings (context-aware)
  • Added clipboard tracking
  • Implemented read-only mode

v1.1.0 (2026-01-26)

  • Initial public release
  • QR code generation and scanning
  • Full BIP39 mnemonic support
  • Trezor test vector validation
  • Production-ready implementation

Roadmap

  • UI polish (modern crypto wallet design)
  • Multi-frame support for larger payloads
  • Hardware wallet integration
  • Mobile scanning app
  • Shamir Secret Sharing support
  • Reproducible builds with git hash verification

License

MIT License - see LICENSE file for details

Author

kccleoc - GitHub


⚠️ Disclaimer: This software is provided as-is. Always test thoroughly before trusting with real funds. The author is not responsible for lost funds due to software bugs or user error.

Description
No description provided
Readme 4 MiB
Languages
JavaScript 84.1%
TypeScript 15.2%
Makefile 0.5%