Recovering a Trezor GPG identity from a seed phrase, no hardware needed
After wiring git-crypt to a Trezor, the obvious next question is disaster recovery: what happens if the Trezor is lost or destroyed? The GPG identity's private key was never on disk — that's the whole point — so is it gone with the device?
No — and the reason why is worth spelling out, because it's not obvious
from the outside. trezor-agent's GPG identity derivation is
fully deterministic. Given the same BIP39 seed, passphrase,
user_id string, curve, and creation-time, it produces the exact
same key — same fingerprint — every single time. trezor-gpg init
even defaults the creation-time to epoch 0, so a
default-configured identity needs nothing but the seed phrase and the
user_id string to reproduce.
This was already asked (and half-answered, via an unmerged gist) in romanz/trezor-agent#335. I packaged a working, tested version.
How it works
libagent's export_public_key() function doesn't
care where the private key comes from — it just calls
device.pubkey() and device.sign() on whatever
Device object it's handed. Normally that's a real Trezor. I
wrote a software Device implementation that derives the same
key from a BIP39 seed via SLIP-0010 instead, and handed it the exact same
export_public_key() call trezor-gpg uses internally. The
output — armored public key, fingerprint — comes out byte-identical,
because it's the same code doing the packet framing either way; only the
source of the private scalar changed.
Verified two ways: the SLIP-0010 derivation is cross-checked against the
official test vector from the SLIP-0010 spec, and the resulting OpenPGP
key parses correctly (including a valid self-signature, meaning
sign() works too, not just pubkey()) under
gpg --list-packets.
What this is (and isn't)
This is a recovery drill tool — run it while you still
have the device, to confirm your recorded parameters (creation-time,
user_id string, curve) actually reproduce the fingerprint you
expect. That's the thing worth verifying before an emergency, not during one.
trezor-gpg init with the original --time
value. Determinism gives the identical hardware-backed identity back.
Usage
python3 recover.py "Name <email>" --time 0 --expect-fingerprint <fingerprint-you-recorded>
Prompts (hidden input) for the mnemonic and BIP39 passphrase. Run air-gapped — the derived private key exists in process memory for the run's duration.
Code / skill
Repo: cyb3ralbert/trezor-gpg-recovery
Claude Code skill: trezor-gpg-recovery
Related: git-crypt + Trezor (the setup this recovers), Trezor as a TOTP vault.