diff --git a/README.md b/README.md index f43ef9a..580a0a5 100644 --- a/README.md +++ b/README.md @@ -54,13 +54,24 @@ bun run dev 3. Click "Backup" to encrypt and generate QR code 4. Save/print QR code for offline storage -**Restore Flow:** +**Restore Flow (Web Interface):** 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 +**Offline/Manual Restore:** + +For airgapped recovery without the web interface, use the command-line method documented in [RECOVERY_PLAYBOOK.md](RECOVERY_PLAYBOOK.md): + +1. Extract Base45 payload from SEEDPGP1 frame +2. Decode Base45 to PGP binary +3. Decrypt with GPG using private key or password +4. Parse JSON output to recover mnemonic + +See [RECOVERY_PLAYBOOK.md](RECOVERY_PLAYBOOK.md) for complete step-by-step instructions. + ### API Usage ```typescript @@ -293,6 +304,7 @@ seedpgp-web/ ├── package.json ├── vite.config.ts # Vite configuration ├── GEMINI.md # AI agent project brief +├── RECOVERY_PLAYBOOK.md # Offline recovery guide └── README.md # This file ``` diff --git a/RECOVERY_PLAYBOOK.md b/RECOVERY_PLAYBOOK.md new file mode 100644 index 0000000..9399890 --- /dev/null +++ b/RECOVERY_PLAYBOOK.md @@ -0,0 +1,422 @@ +## SeedPGP Recovery Playbook - Offline Recovery Guide + +**Generated:** Feb 1, 2026 | **SeedPGP v1.4.3** | **Frame Format:** `SEEDPGP1:0:CRC16:BASE45_PAYLOAD` + +*** + +## 📋 Recovery Requirements + +``` +✅ SEEDPGP1 QR code or printed text +✅ PGP Private Key (.asc file) OR Message Password (if symmetric encryption used) +✅ Offline computer with terminal access +✅ gpg command line tool (GNU Privacy Guard) +``` + +**⚠️ Important:** This playbook assumes you have the original encryption parameters: + +- PGP private key (if PGP encryption was used) +- Private key passphrase (if the key is encrypted) +- Message password (if symmetric encryption was used) +- BIP39 passphrase (if 25th word was used during backup) + +*** + +## 🔓 Step 1: Understand Frame Format + +**SeedPGP Frame Structure:** + +``` +SEEDPGP1:0:CRC16:BASE45_PAYLOAD +``` + +- **SEEDPGP1:** Protocol identifier +- **0:** Frame version (single frame) +- **CRC16:** 4-character hexadecimal CRC16-CCITT checksum +- **BASE45_PAYLOAD:** Base45-encoded PGP binary data + +**Example Frame:** + +``` +SEEDPGP1:0:58B5:2KO K0S-U. M:E1T*A%50%886N2SDITXSQVE VV$BA7.FZ+I01N%ISK$KBGESBRNOHYIK%A8N1FUOE.Z1T:8JBHDNNBV2AVJRGC1-OY67AU777I07UB88TQN0B5033IJOGG7$2ID/QNIR.:UGUO/M0BH0O94468TXM 0RGSIYT FNSQGNJKDCHP3JV/V-77:%KVZG+6VA7P826W0N0TBI5AMSQX60A%2E$OMWF1TV/J0SJJ 0M-VF0TH60W4TL1/519HS7BO%OT-QGZ5.AS.18AWSGF9O5E%MCYLM4STPI5+.3A5K7ZULFQM.JO:J3/C.IOB1819L8*ME027S9DJ0+18WCVTC30928T72W5D4P0UHC4O11IPRQ I5T39RSI9BTVT6LK6A9PWUF7B2CBEI43M%TT47%I4KBT-0H44L.RP$U02F8-7A*LH2$G44Q.880WF0BJ5SB5OR*39W/N3T9 -DQ4C +``` + +### Extract Base45 Payload + +```bash +# Extract everything after the 3rd colon +FRAME="SEEDPGP1:0:58B5:2KO K0S-U. M:E1T*A%50%886N2SDITXSQVE VV$BA7.FZ+I01N%ISK$KBGESBRNOHYIK%A8N1FUOE.Z1T:8JBHDNNBV2AVJRGC1-OY67AU777I07UB88TQN0B5033IJOGG7$2ID/QNIR.:UGUO/M0BH0O94468TXM 0RGSIYT FNSQGNJKDCHP3JV/V-77:%KVZG+6VA7P826W0N0TBI5AMSQX60A%2E$OMWF1TV/J0SJJ 0M-VF0TH60W4TL1/519HS7BO%OT-QGZ5.AS.18AWSGF9O5E%MCYLM4STPI5+.3A5K7ZULFQM.JO:J3/C.IOB1819L8*ME027S9DJ0+18WCVTC30928T72W5D4P0UHC4O11IPRQ I5T39RSI9BTVT6LK6A9PWUF7B2CBEI43M%TT47%I4KBT-0H44L.RP$U02F8-7A*LH2$G44Q.880WF0BJ5SB5OR*39W/N3T9 -DQ4C" +PAYLOAD=$(echo "$FRAME" | cut -d: -f4-) +echo "$PAYLOAD" > payload.b45 +``` + +*** + +## 🔓 Step 2: Decode Base45 → PGP Binary + +**Option A: Using base45 CLI tool:** + +```bash +# Install base45 if needed +npm install -g base45 + +# Decode the payload +base45decode < payload.b45 > encrypted.pgp +``` + +**Option B: Using CyberChef (offline browser tool):** + +1. Download CyberChef HTML from +2. Open it in an offline browser +3. Input → Paste your Base45 payload +4. Operation → `From Base45` +5. Save output as `encrypted.pgp` + +**Option C: Manual verification (check CRC):** + +```bash +# Verify CRC16 checksum matches +# The CRC16-CCITT-FALSE checksum should match the value in the frame (58B5 in example) +# If using the web app, this is automatically verified during decryption +``` + +*** + +## 🔓 Step 3: Decrypt PGP Binary + +### Option A: PGP Private Key Decryption (PKESK) + +If the backup was encrypted with a PGP public key: + +```bash +# Import your private key (if not already imported) +gpg --import private-key.asc + +# List keys to verify fingerprint +gpg --list-secret-keys --keyid-format LONG + +# Decrypt using your private key +gpg --batch --yes --decrypt encrypted.pgp +``` + +**Expected JSON Output:** + +```json +{"v":1,"t":"bip39","w":"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about","l":"en","pp":0} +``` + +**If private key has a passphrase:** + +```bash +gpg --batch --yes --passphrase "YOUR-PGP-KEY-PASSPHRASE" --decrypt encrypted.pgp +``` + +### Option B: Message Password Decryption (SKESK) + +If the backup was encrypted with a symmetric password: + +```bash +gpg --batch --yes --passphrase "YOUR-MESSAGE-PASSWORD" --decrypt encrypted.pgp +``` + +**Expected JSON Output:** + +```json +{"v":1,"t":"bip39","w":"your seed phrase words here","l":"en","pp":1} +``` + +*** + +## 🔓 Step 4: Parse Decrypted Data + +The decrypted output is a JSON object with the following structure: + +```json +{ + "v": 1, // Version (always 1) + "t": "bip39", // Type (always "bip39") + "w": "word1 word2 ...", // BIP39 mnemonic words (lowercase, single spaces) + "l": "en", // Language (always "en" for English) + "pp": 0 // BIP39 passphrase flag: 0 = no passphrase, 1 = passphrase used +} +``` + +**Extract the mnemonic:** + +```bash +# After decryption, extract the 'w' field +DECRYPTED='{"v":1,"t":"bip39","w":"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about","l":"en","pp":0}' +MNEMONIC=$(echo "$DECRYPTED" | grep -o '"w":"[^"]*"' | cut -d'"' -f4) +echo "Mnemonic: $MNEMONIC" +``` + +*** + +## 💰 Step 5: Wallet Recovery + +### BIP39 Passphrase Status + +Check the `pp` field in the decrypted JSON: + +- `"pp": 0` → No BIP39 passphrase was used during backup +- `"pp": 1` → **BIP39 passphrase was used** (25th word/extra passphrase) + +### Recovery Instructions + +**Without BIP39 Passphrase (`pp": 0`):** + +``` +Seed Words: [extracted from 'w' field] +BIP39 Passphrase: None required +``` + +**With BIP39 Passphrase (`pp": 1`):** + +``` +Seed Words: [extracted from 'w' field] +BIP39 Passphrase: [Your original 25th word/extra passphrase] +``` + +**Wallet Recovery Steps:** + +1. **Hardware Wallets (Ledger/Trezor):** + - Start recovery process + - Enter 12/24 word mnemonic + - **If `pp": 1`:** Enable passphrase option and enter your BIP39 passphrase + +2. **Software Wallets (Electrum, MetaMask, etc.):** + - Create/restore wallet + - Enter mnemonic phrase + - **If `pp": 1`:** Look for "Advanced options" or "Passphrase" field + +3. **Bitcoin Core (using `hdseed`):** + + ```bash + # Use the mnemonic with appropriate BIP39 passphrase + # Consult your wallet's specific recovery documentation + ``` + +*** + +## 🛠️ GPG Setup (One-time) + +**Mac (Homebrew):** + +```bash +brew install gnupg +``` + +**Ubuntu/Debian:** + +```bash +sudo apt update && sudo apt install gnupg +``` + +**Fedora/RHEL/CentOS:** + +```bash +sudo dnf install gnupg +``` + +**Windows:** + +- Download Gpg4win from +- Install and use Kleopatra or command-line gpg + +**Verify installation:** + +```bash +gpg --version +``` + +*** + +## 🔍 Troubleshooting + +| Error | Likely Cause | Solution | +|-------|-------------|----------| +| `gpg: decryption failed: No secret key` | Wrong PGP private key or key not imported | Import correct private key: `gpg --import private-key.asc` | +| `gpg: BAD decrypt` | Wrong passphrase (key passphrase or message password) | Verify you're using the correct passphrase | +| `base45decode: command not found` | base45 CLI tool not installed | Use CyberChef or install: `npm install -g base45` | +| `gpg: no valid OpenPGP data found` | Invalid Base45 decoding or corrupted payload | Verify Base45 decoding step, check for scanning errors | +| `gpg: CRC error` | Frame corrupted during scanning/printing | Rescan QR code or use backup copy | +| `gpg: packet(3) too short` | Truncated PGP binary | Ensure complete frame was captured | +| JSON parsing error after decryption | Output not valid JSON | Check if decryption succeeded, may need different passphrase | + +**Common Issues:** + +1. **Wrong encryption method:** Trying PGP decryption when symmetric password was used, or vice versa +2. **BIP39 passphrase mismatch:** Forgetting the 25th word used during backup +3. **Frame format errors:** Missing `SEEDPGP1:` prefix or incorrect colon separation + +*** + +## 📦 Recovery Checklist + +``` +[ ] Airgapped computer prepared (offline, clean OS) +[ ] GPG installed and verified +[ ] Base45 decoder available (CLI tool or CyberChef) +[ ] SEEDPGP1 frame extracted and verified +[ ] Base45 payload decoded to PGP binary +[ ] CRC16 checksum verified (optional but recommended) +[ ] Correct decryption method identified (PGP key vs password) +[ ] Private key imported (if PGP encryption) +[ ] Decryption successful with valid JSON output +[ ] Mnemonic extracted from 'w' field +[ ] BIP39 passphrase status checked ('pp' field) +[ ] Appropriate BIP39 passphrase ready (if 'pp': 1) +[ ] Wallet recovery tool selected (hardware/software wallet) +[ ] Test recovery on testnet/small amount first +[ ] Browser/terminal history cleared after recovery +[ ] Original backup securely stored or destroyed after successful recovery +[ ] Funds moved to new addresses after recovery +``` + +*** + +## ⚠️ Security Best Practices + +**Critical Security Measures:** + +1. **Always use airgapped computer** for recovery operations +2. **Never type mnemonics or passwords on internet-connected devices** +3. **Clear clipboard and terminal history** after recovery +4. **Test with small amounts** before recovering significant funds +5. **Move funds to new addresses** after successful recovery +6. **Destroy recovery materials** or store them separately from private keys + +**Storage Recommendations:** + +- Print QR code on archival paper or metal +- Store playbook separately from private keys/passphrases +- Use multiple geographically distributed backups +- Consider Shamir's Secret Sharing for critical components + +*** + +## 🔄 Alternative Recovery Methods + +**Using the SeedPGP Web App (Online):** + +1. Open (or local instance) +2. Switch to "Restore" tab +3. Scan QR code or paste SEEDPGP1 frame +4. Provide private key or message password +5. App handles Base45 decoding, CRC verification, and decryption automatically + +**Using Custom Script (Advanced):** + +```python +# Example Python recovery script (conceptual) +import base45 +import gnupg +import json + +frame = "SEEDPGP1:0:58B5:2KO K0S-U. M:..." +parts = frame.split(":", 3) +crc_expected = parts[2] +b45_payload = parts[3] + +# Decode Base45 +pgp_binary = base45.b45decode(b45_payload) + +# Decrypt with GPG +gpg = gnupg.GPG() +decrypted = gpg.decrypt(pgp_binary, passphrase="your-passphrase") + +# Parse JSON +data = json.loads(str(decrypted)) +print(f"Mnemonic: {data['w']}") +print(f"BIP39 Passphrase used: {'YES' if data['pp'] == 1 else 'NO'}") +``` + +*** + +## 📝 Technical Details + +**Encryption Algorithms:** + +- **PGP Encryption:** AES-256 (OpenPGP standard) +- **Symmetric Encryption:** AES-256 with random session key +- **CRC Algorithm:** CRC16-CCITT-FALSE (polynomial 0x1021) +- **Encoding:** Base45 (RFC 9285) + +**JSON Schema:** + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["v", "t", "w", "l", "pp"], + "properties": { + "v": { + "type": "integer", + "const": 1, + "description": "Protocol version" + }, + "t": { + "type": "string", + "const": "bip39", + "description": "Data type (BIP39 mnemonic)" + }, + "w": { + "type": "string", + "pattern": "^[a-z]+( [a-z]+){11,23}$", + "description": "BIP39 mnemonic words (lowercase, space-separated)" + }, + "l": { + "type": "string", + "const": "en", + "description": "Language (English)" + }, + "pp": { + "type": "integer", + "enum": [0, 1], + "description": "BIP39 passphrase flag: 0 = none, 1 = used" + }, + "fpr": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional: Recipient key fingerprints" + } + } +} +``` + +**Frame Validation Rules:** + +1. Must start with `SEEDPGP1:` +2. Frame version must be `0` (single frame) +3. CRC16 must be 4 hex characters `[0-9A-F]{4}` +4. Base45 payload must use valid Base45 alphabet +5. Decoded PGP binary must pass CRC16 verification + +*** + +## 🆘 Emergency Contact & Support + +**No Technical Support Available:** + +- SeedPGP is a self-sovereign tool with no central authority +- You are solely responsible for your recovery +- Test backups regularly to ensure they work + +**Community Resources:** + +- GitHub Issues: +- Bitcoin StackExchange: Use `seedpgp` tag +- Local Bitcoin meetups for in-person help + +**Remember:** The security of your funds depends on your ability to successfully execute this recovery process. Practice with test backups before relying on it for significant amounts. + +*** + +**Print this playbook on archival paper or metal. Store separately from encrypted backups and private keys.** 🔒 + +**Last Updated:** February 1, 2026 +**SeedPGP Version:** 1.4.3 +**Frame Example CRC:** 58B5 ✓ +**Test Recovery:** [ ] Completed [ ] Not Tested + +*** diff --git a/test.pgp b/test.pgp deleted file mode 100644 index 017cbd3..0000000 Binary files a/test.pgp and /dev/null differ