diff --git a/.gitignore b/.gitignore index e3f3986..fe7feff 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? -REFERENCE \ No newline at end of file +REFERENCE +.Ref \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index 05655b7..0000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,291 +0,0 @@ -Here's your `DEVELOPMENT.md`: - -```markdown -# Development Guide - SeedPGP v1.4.4 - -## Architecture Quick Reference - -### Core Types - -```typescript -// src/lib/types.ts -interface SeedPgpPlaintext { - v: number; // Version (always 1) - t: string; // Type ("bip39") - w: string; // Mnemonic words (normalized) - l: string; // Language ("en") - pp: number; // BIP39 passphrase used? (0 or 1) - fpr?: string[]; // Optional recipient fingerprints -} - -interface ParsedSeedPgpFrame { - kind: "single"; // Frame type - crc16: string; // 4-digit hex checksum - b45: string; // Base45 payload -} -``` - -### Frame Format - -``` -SEEDPGP1:0:ABCD:BASE45DATA - -SEEDPGP1 - Protocol identifier + version -0 - Frame number (single frame) -ABCD - CRC16-CCITT-FALSE checksum (4 hex digits) -BASE45 - Base45-encoded PGP binary message -``` - -### Key Functions - -#### Encryption Flow -```typescript -buildPlaintext(mnemonic, bip39PassphraseUsed, recipientFingerprints?) - → SeedPgpPlaintext - -encryptToSeedPgp({ plaintext, publicKeyArmored?, messagePassword? }) - → { framed: string, pgpBytes: Uint8Array, recipientFingerprint?: string } -``` - -#### Decryption Flow -```typescript -decryptSeedPgp({ frameText, privateKeyArmored?, privateKeyPassphrase?, messagePassword? }) - → SeedPgpPlaintext - -frameDecodeToPgpBytes(frameText) - → Uint8Array (with CRC16 validation) -``` - -#### Encoding/Decoding -```typescript -frameEncode(pgpBinary: Uint8Array) → "SEEDPGP1:0:CRC16:BASE45" -frameParse(text: string) → ParsedSeedPgpFrame -frameDecodeToPgpBytes(frameText: string) → Uint8Array -``` - -### Dependencies - -```json -{ - "openpgp": "^6.3.0", // PGP encryption (curve25519Legacy) - "bun-types": "latest", // Bun runtime types - "react": "^18.x", // UI framework - "vite": "^5.x" // Build tool -} -``` - -### OpenPGP.js v6 Quirks - -⚠️ **Important compatibility notes:** - -1. **Empty password array bug**: Never pass `passwords: []` to `decrypt()`. Only include if non-empty: - ```typescript - if (msgPw) { - decryptOptions.passwords = [msgPw]; - } - ``` - -2. **Curve naming**: Use `curve25519Legacy` (not `curve25519`) in `generateKey()` - -3. **Key validation**: Always call `getEncryptionKey()` to verify public key has usable subkeys - -## Project Structure - -``` -seedpgp-web/ -├── src/ -│ ├── lib/ -│ │ ├── seedpgp.ts # Core encrypt/decrypt logic -│ │ ├── seedpgp.test.ts # Test vectors (15 tests) -│ │ ├── base45.ts # Base45 encoder/decoder -│ │ ├── crc16.ts # CRC16-CCITT-FALSE -│ │ └── types.ts # TypeScript interfaces -│ ├── App.tsx # React UI entry -│ └── main.tsx # Vite bootstrap -├── package.json -├── tsconfig.json -├── vite.config.ts -├── README.md -└── DEVELOPMENT.md # This file -``` - -## Development Workflow - -### Running Tests - -```bash -# All tests -bun test - -# Watch mode -bun test --watch - -# Verbose output -bun test --verbose -``` - -### Development Server - -```bash -bun run dev # Start Vite dev server -bun run build # Production build -bun run preview # Preview production build -``` - -### Adding Features - -1. **Write tests first** in `seedpgp.test.ts` -2. **Implement in** `src/lib/seedpgp.ts` -3. **Update types** in `types.ts` if needed -4. **Run full test suite**: `bun test` -5. **Commit with conventional commits**: `feat: add QR generation` - -## Feature Agenda - -### 🚧 v1.2.0 - QR Code Round-Trip - -**Goal**: Read back QR code and decrypt with user-provided credentials - -**Tasks**: -- [ ] Add QR code generation from `encrypted.framed` - - Library: `qrcode` or `qr-code-styling` - - Input: SEEDPGP1 frame string - - Output: QR code image/canvas/SVG - -- [ ] Add QR code scanner UI - - Library: `html5-qrcode` or `jsqr` - - Camera/file upload input - - Parse scanned text → `frameText` - -- [ ] Build decrypt UI form - - Input fields: - - Scanned QR text (auto-filled) - - Private key (file upload or paste) - - Key passphrase (password input) - - OR message password (alternative) - - Call `decryptSeedPgp()` - - Display recovered mnemonic + metadata - -- [ ] Add visual feedback - - CRC16 validation status - - Key fingerprint match indicator - - Decryption success/error states - -**API Usage**: -```typescript -// Generate QR -import QRCode from 'qrcode'; -const { framed } = await encryptToSeedPgp({ ... }); -const qrDataUrl = await QRCode.toDataURL(framed); - -// Scan and decrypt -const scannedText = "SEEDPGP1:0:ABCD:..."; // from scanner -const decrypted = await decryptSeedPgp({ - frameText: scannedText, - privateKeyArmored: userKey, - privateKeyPassphrase: userPassword, -}); -console.log(decrypted.w); // Recovered mnemonic -``` - -**Security Notes**: -- Never log decrypted mnemonics in production -- Clear sensitive data from memory after use -- Validate CRC16 before attempting decrypt -- Show key fingerprint for user verification - ---- - -### 🔮 Future Ideas (v1.3+) - -- [ ] Multi-frame support (for larger payloads) -- [ ] Password-only (SKESK) encryption flow -- [ ] Shamir Secret Sharing integration -- [ ] Hardware wallet key generation -- [ ] Mobile companion app (React Native) -- [ ] Printable paper backup templates -- [ ] Encrypted cloud backup with PBKDF2 -- [ ] BIP85 child mnemonic derivation - -## Debugging Tips - -### Enable verbose PGP logging - -Uncomment in `seedpgp.ts`: -```typescript -console.log("Raw PGP hex:", Array.from(pgpBytes).map(...)); -console.log("SeedPGP: message packets:", ...); -console.log("SeedPGP: encryption key IDs:", ...); -``` - -### Test with known vectors - -Use Trezor vectors from test file: -```bash -bun test "Trezor" # Run only Trezor tests -``` - -### Validate frame manually - -```typescript -import { frameParse } from "./lib/seedpgp"; -const parsed = frameParse("SEEDPGP1:0:ABCD:..."); -console.log(parsed); // Check structure -``` - -## Code Style - -- **Functions**: Async by default, explicit return types -- **Errors**: Throw descriptive Error objects with context -- **Naming**: `camelCase` for functions, `PascalCase` for types -- **Comments**: Only for non-obvious crypto/encoding logic -- **Testing**: One test per edge case, descriptive names - -## Git Workflow - -```bash -# Feature branch -git checkout -b feat/qr-generation - -# Conventional commits -git commit -m "feat(qr): add QR code generation" -git commit -m "test(qr): add QR round-trip test" - -# Tag releases -git tag -a v1.2.0 -m "Release v1.2.0 - QR round-trip" -git push origin main --tags -``` - -## Questions for Next Session - -When continuing development, provide: - -1. **Feature context**: "Adding QR code generation for v1.2.0" -2. **Current code**: Paste relevant files you're modifying -3. **Specific question**: "How should I structure the QR scanner component?" - -Example starter prompt: -``` -I'm working on seedpgp-web v1.1.0 (BIP39 PGP encryption tool). - -[Paste this DEVELOPMENT.md section] -[Paste relevant source files] - -I want to add QR code generation. Here's my current seedpgp.ts... -``` - ---- - -**Last Updated**: 2026-01-28 -**Maintainer**: @kccleoc -``` - -Now commit it: - -```bash -git add DEVELOPMENT.md -git commit -m "docs: add development guide with v1.2.0 QR agenda" -git push origin main -``` - -Ready for your next feature sprint! 🚀📋 diff --git a/public/README.md b/public/README.md deleted file mode 100644 index 84fcad4..0000000 --- a/public/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# SeedPGP Web App - -**Secure BIP39 mnemonic backup tool using OpenPGP encryption** - -🔗 **Live App**: https://kccleoc.github.io/seedpgp-web-app/ - -## About - -Client-side web application for encrypting cryptocurrency seed phrases (BIP39 mnemonics) using OpenPGP encryption with QR code generation and scanning capabilities. - -### ✨ Features - -- 🔐 **OpenPGP Encryption** - Curve25519Legacy (cv25519) encryption -- 📱 **QR Code Generation** - High-quality 512x512px PNG with download -- 📸 **QR Code Scanner** - Camera or image upload with live preview -- 🔄 **Round-trip Flow** - Encrypt → QR → Scan → Decrypt seamlessly -- ✅ **BIP39 Support** - 12/18/24-word mnemonics with optional passphrase -- 🔒 **Symmetric Encryption** - Optional password-only encryption (SKESK) -- 🎯 **CRC16 Validation** - Frame integrity checking -- 📦 **Base45 Encoding** - Compact QR-friendly format (RFC 9285) -- 🌐 **100% Client-Side** - No backend, no data transmission - -## 🔒 Security Notice - -⚠️ **Your private keys and seed phrases never leave your browser** - -- Static web app with **no backend server** -- All cryptographic operations run **locally in your browser** -- **No data transmitted** to any server -- Camera access requires **HTTPS or localhost** -- Always verify you're on the correct URL before use - -### For Maximum Security - -For production use with real funds: -- 🏠 Download and run locally (\`bun run dev\`) -- 🔐 Use on airgapped device -- 📥 Self-host on your own domain -- 🔍 Source code: https://github.com/kccleoc/seedpgp-web (private) - -## 📖 How to Use - -### Backup Flow -1. **Enter** your 12/24-word BIP39 mnemonic -2. **Add** PGP public key and/or message password (optional) -3. **Generate** encrypted QR code -4. **Download** or scan QR code for backup - -### Restore Flow -1. **Scan QR Code** using camera or upload image -2. **Provide** private key and/or message password -3. **Decrypt** to recover your mnemonic - -### QR Scanner Features -- 📷 **Camera Mode** - Live scanning with environment camera (iPhone Continuity Camera supported on macOS) -- 📁 **Upload Mode** - Scan from saved images or screenshots -- ✅ **Auto-validation** - Verifies SEEDPGP1 format before accepting - -## 🛠 Technical Stack - -- **TypeScript** - Type-safe development -- **React 18** - Modern UI framework -- **Vite 6** - Lightning-fast build tool -- **OpenPGP.js v6** - RFC 4880 compliant encryption -- **html5-qrcode** - QR scanning library -- **TailwindCSS** - Utility-first styling -- **Lucide React** - Beautiful icons - -## 📋 Protocol Format - -\`\`\` -SEEDPGP1:0:ABCD:BASE45DATA - -SEEDPGP1 - Protocol identifier + version -0 - Frame number (single frame) -ABCD - CRC16-CCITT-FALSE checksum -BASE45 - Base45-encoded OpenPGP binary message -\`\`\` - -## 🔐 Encryption Details - -- **Algorithm**: AES-256 (preferred symmetric cipher) -- **Curve**: Curve25519Legacy for modern security -- **Key Format**: OpenPGP RFC 4880 compliant -- **Error Correction**: QR Level M (15% recovery) -- **Integrity**: CRC16-CCITT-FALSE frame validation - -## 📱 Browser Compatibility - -- ✅ Chrome/Edge (latest) -- ✅ Safari 16+ (macOS/iOS) -- ✅ Firefox (latest) -- 📷 Camera requires HTTPS or localhost - -## 📦 Version - -**Current deployment: v1.2.0** - -### Changelog - -#### v1.2.0 (2026-01-29) -- ✨ Added QR scanner with camera/upload support -- 📥 Added QR code download with auto-naming -- 🔧 Split state for backup/restore tabs -- 🎨 Improved QR generation quality -- 🐛 Fixed Safari camera permissions -- 📱 Added Continuity Camera support - -#### v1.1.0 (2026-01-28) -- 🎉 Initial public release -- 🔐 OpenPGP encryption/decryption -- 📱 QR code generation -- ✅ BIP39 validation - ---- - -**Last updated**: 2026-01-29 - -**Built with** ❤️ using TypeScript, React, Vite, and OpenPGP.js - -**License**: Private source code - deployment only diff --git a/public/_headers b/public/_headers deleted file mode 100644 index 146475c..0000000 --- a/public/_headers +++ /dev/null @@ -1,6 +0,0 @@ -/* - Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'none'; form-action 'none'; base-uri 'self'; - X-Frame-Options: DENY - X-Content-Type-Options: nosniff - X-XSS-Protection: 1; mode=block - Referrer-Policy: strict-origin-when-cross-origin diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/SeedBlender.tsx b/src/components/SeedBlender.tsx index c2dfc78..07713b9 100644 --- a/src/components/SeedBlender.tsx +++ b/src/components/SeedBlender.tsx @@ -161,7 +161,6 @@ export function SeedBlender({ onDirtyStateChange, setMnemonicForBackup, requestT const handleScanSuccess = useCallback(async (scannedData: string | Uint8Array) => { if (scanTargetIndex === null) return; - // Convert binary data to hex string if necessary const scannedText = typeof scannedData === 'string' ? scannedData : Array.from(scannedData).map(b => b.toString(16).padStart(2, '0')).join(''); diff --git a/src/lib/seedpgp.ts b/src/lib/seedpgp.ts index 4cd3b3a..0bfcbe4 100644 --- a/src/lib/seedpgp.ts +++ b/src/lib/seedpgp.ts @@ -2,6 +2,7 @@ import * as openpgp from "openpgp"; import { base45Encode, base45Decode } from "./base45"; import { crc16CcittFalse } from "./crc16"; import { encryptToKrux, decryptFromKrux } from "./krux"; +import { decodeSeedQR } from './seedqr'; import type { SeedPgpPlaintext, ParsedSeedPgpFrame, @@ -302,6 +303,25 @@ export async function decryptFromSeed(params: DecryptionParams): Promise