chore: Stage all remaining changes before merge

This commit is contained in:
LC mac
2026-02-07 13:47:52 +08:00
parent cf3412b235
commit 008406ef59
7 changed files with 22 additions and 421 deletions

3
.gitignore vendored
View File

@@ -22,4 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
REFERENCE
REFERENCE
.Ref

View File

@@ -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! 🚀📋

View File

@@ -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

View File

@@ -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

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -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('');

View File

@@ -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<SeedPgp
throw error;
}
}
if (mode === 'seedqr') {
try {
const mnemonic = await decodeSeedQR(params.frameText);
// Convert to SeedPgpPlaintext format for consistency
return {
v: 1,
t: "bip39",
w: mnemonic,
l: "en",
pp: 0,
};
} catch (error) {
if (error instanceof Error) {
throw new Error(`SeedQR decoding failed: ${error.message}`);
}
throw error;
}
}
// Default to PGP mode
return decryptSeedPgp({