- Update package.json version to v1.4.7 - Update README.md header to v1.4.7 - Update GEMINI.md version references to v1.4.7 - Update RECOVERY_PLAYBOOK.md version to v1.4.7 - Update SECURITY_AUDIT_REPORT.md version to v1.4.7 - Move documentation files to doc/ directory for better organization - Add new documentation files: LOCAL_TESTING_GUIDE.md, SERVE.md, TAILS_OFFLINE_PLAYBOOK.md - Add Makefile and serve.ts for improved development workflow
12 KiB
SeedPGP Recovery Playbook - Offline Recovery Guide
Generated: Feb 3, 2026 | SeedPGP v1.4.7 | 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
# 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:
# Install base45 if needed
npm install -g base45
# Decode the payload
base45decode < payload.b45 > encrypted.pgp
Option B: Using CyberChef (offline browser tool):
- Download CyberChef HTML from https://gchq.github.io/CyberChef/
- Open it in an offline browser
- Input → Paste your Base45 payload
- Operation →
From Base45 - Save output as
encrypted.pgp
Option C: Manual verification (check CRC):
# 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:
# 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:
{"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:
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:
gpg --batch --yes --passphrase "YOUR-MESSAGE-PASSWORD" --decrypt encrypted.pgp
Expected JSON Output:
{"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:
{
"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:
# 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:
-
Hardware Wallets (Ledger/Trezor):
- Start recovery process
- Enter 12/24 word mnemonic
- If
pp": 1: Enable passphrase option and enter your BIP39 passphrase
-
Software Wallets (Electrum, MetaMask, etc.):
- Create/restore wallet
- Enter mnemonic phrase
- If
pp": 1: Look for "Advanced options" or "Passphrase" field
-
Bitcoin Core (using
hdseed):# Use the mnemonic with appropriate BIP39 passphrase # Consult your wallet's specific recovery documentation
🛠️ GPG Setup (One-time)
Mac (Homebrew):
brew install gnupg
Ubuntu/Debian:
sudo apt update && sudo apt install gnupg
Fedora/RHEL/CentOS:
sudo dnf install gnupg
Windows:
- Download Gpg4win from https://www.gpg4win.org/
- Install and use Kleopatra or command-line gpg
Verify installation:
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:
- Wrong encryption method: Trying PGP decryption when symmetric password was used, or vice versa
- BIP39 passphrase mismatch: Forgetting the 25th word used during backup
- 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:
- Always use airgapped computer for recovery operations
- Never type mnemonics or passwords on internet-connected devices
- Clear clipboard and terminal history after recovery
- Test with small amounts before recovering significant funds
- Move funds to new addresses after successful recovery
- 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):
- Open https://seedpgp.com (or local instance)
- Switch to "Restore" tab
- Scan QR code or paste SEEDPGP1 frame
- Provide private key or message password
- App handles Base45 decoding, CRC verification, and decryption automatically
Using Custom Script (Advanced):
# 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:
{
"$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:
- Must start with
SEEDPGP1: - Frame version must be
0(single frame) - CRC16 must be 4 hex characters
[0-9A-F]{4} - Base45 payload must use valid Base45 alphabet
- 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: https://github.com/kccleoc/seedpgp-web/issues
- Bitcoin StackExchange: Use
seedpgptag - 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 3, 2026
SeedPGP Version: 1.4.7
Frame Example CRC: 58B5 ✓
Test Recovery: [ ] Completed [ ] Not Tested