Files
seedpgp-web/DEVELOPMENT.md
LC mac 4353ec0cc2 docs: enhance documentation with threat model, limitations, air-gapped guidance
- Update version to v1.4.4
- Add explicit threat model documentation
- Document known limitations prominently
- Include air-gapped usage recommendations
- Polish all documentation for clarity and examples
- Update README, DEVELOPMENT.md, GEMINI.md, RECOVERY_PLAYBOOK.md
2026-02-03 02:24:59 +08:00

7.2 KiB

Here's your DEVELOPMENT.md:

# 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

buildPlaintext(mnemonic, bip39PassphraseUsed, recipientFingerprints?)
   SeedPgpPlaintext

encryptToSeedPgp({ plaintext, publicKeyArmored?, messagePassword? })
   { framed: string, pgpBytes: Uint8Array, recipientFingerprint?: string }

Decryption Flow

decryptSeedPgp({ frameText, privateKeyArmored?, privateKeyPassphrase?, messagePassword? })
   SeedPgpPlaintext

frameDecodeToPgpBytes(frameText)
   Uint8Array (with CRC16 validation)

Encoding/Decoding

frameEncode(pgpBinary: Uint8Array)  "SEEDPGP1:0:CRC16:BASE45"
frameParse(text: string)  ParsedSeedPgpFrame
frameDecodeToPgpBytes(frameText: string)  Uint8Array

Dependencies

{
  "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:

    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

# All tests
bun test

# Watch mode
bun test --watch

# Verbose output
bun test --verbose

Development Server

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:

// 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:

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:

bun test "Trezor"  # Run only Trezor tests

Validate frame manually

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

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