Recovering a Trezor GPG identity from a seed phrase, no hardware needed

cyb3ralbert · 2026-07-19 · ← other posts

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.

BIP39 seed → SLIP-0010 (nist256p1) → libagent's own packet code → identical fingerprint

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.

Deliberately not built: a persistent software gpg-agent that decrypts on demand using the recovered key. Materializing the raw private key routinely on a host is exactly the risk the whole git-crypt+Trezor setup exists to avoid. For actual day-to-day recovery after losing a device, the simplest path needs no software here at all: restore the same seed onto any Trezor via its normal recovery flow, then re-run 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.