Offline desktop app to reconstruct an MPC wallet's private keys from threshold shares. Built with Wails (Go backend + React/TS frontend).
⚠️ Reconstructing a key materializes the full private key and defeats the wallet's threshold security. Run only offline, on an air-gapped machine, for recovery or migration.
| Format | Extension | Unlock with | Source |
|---|---|---|---|
| RSA export bundle | .enc |
RSA private key (PEM) | Fystack web "Export Share", or cosigner export-share on a self-hosted server cosigner |
| Passphrase share | .mshare |
6-word passphrase | cosigner-mobile recovery export |
Both decrypt to tss-lib share data, then Lagrange interpolation reconstructs the ECDSA and EdDSA keys, each verified against the wallet's public key.
- Add & unlock shares — add each participant's file, unlock with its RSA key or passphrase. Needs ≥2 shares per protocol.
- Recover — confirm, then combine the shares.
- Result — reveal / copy (clipboard auto-clears) / export the ECDSA + EdDSA keys.
The same recovery engine (internal/recovery) is also exposed as a headless CLI,
so GUI and CLI never diverge.
go build -o build/bin/recover-cli ./cmd/recover-cli
recover-cli [--json] \
--share wallet.enc --rsa-key recipient_priv.pem \
--share cos11.mshare --passphrase "six-word-phrase"Each --share is followed by its secret: --rsa-key for .enc bundles,
--passphrase for .mshare files. Prints ECDSA/EdDSA private keys, derived
addresses, and verification status. Needs at least 2 shares per protocol.
Exporting an HD master from the UI gives two files: the share (share-*.enc) and the
derivation paths (hd-paths-*.enc: chain code, master pubkeys, every child path).
recover-cli --share share-master.enc --rsa-key priv.pem --share cos.mshare --passphrase "..." \
--hd-bundle hd-paths-master.enc --children-out children.csv
# EdDSA (Solana) children can sign directly; batch = [{"wallet_id": "...", "message_hex": "..."}]
recover-cli ... --hd-bundle hd-paths-master.enc --sign-eddsa batch.json --sign-out sigs.jsonIn the desktop app: on step 1 click "+ Add child wallet list" (the hd-paths-*.enc file), then
"Select RSA private key…" to unlock it, like a share. After recovering, the result screen's
"Export all child keys…" writes the master and every child key to one file (.json for
recover-cli, .csv to import elsewhere).
To sign for many EdDSA wallets later, no shares are needed, only the exported JSON:
recover-cli --children-in child-keys.json --sign-eddsa batch.json --sign-out sigs.jsonTo import EVM keys into MetaMask and other wallets, export password-protected keystore (v3) files, one per wallet. In the app use "Export keystore files…" on the result screen; in the CLI:
echo "a strong password" > pw.txt
recover-cli --children-in child-keys.json --keystore-dir keystores --keystore-password-file pw.txtDelete pw.txt afterwards. Solana keys cannot be exported this way (see below).
ECDSA child keys are plain secp256k1 keys (importable anywhere). EdDSA child keys are group
scalars, not ed25519 seeds, so external wallets can't import them — use --sign-eddsa instead.
In the desktop app, pick "Transfer / sweep (Solana)" on the home screen: load the child keys .json,
enter the destination, scan, review, then "Sign & send" (you must retype the destination) or
"Sign & save to file…" to broadcast later with --sol-broadcast. The same steps are available in the CLI:
Moves every SPL token, then all SOL, from each Solana wallet in the exported child keys to one address.
Wallets are found on-chain (getTokenAccountsByOwner for Token and Token-2022), so no token list is needed.
Empty token accounts are closed to reclaim rent. Dry run is the default; nothing is sent without --send.
# One machine that has the keys and network access
recover-cli --children-in child-keys.json --sol-sweep <destination> # dry run
recover-cli --children-in child-keys.json --sol-sweep <destination> --send
# Air-gapped: keys never touch the online machine
recover-cli --children-in child-keys.json --sol-plan <destination> --plan-out plan.json # online, keys not needed
recover-cli --children-in child-keys.json --sol-sign-plan plan.json --signed-out signed.json # offline
recover-cli --sol-broadcast signed.json --send # onlineFor the online planning step you can pass --sol-addresses addresses.json (an array of addresses or
{wallet_id, address}) instead of the keys file. The signing step re-checks that every transaction
only pays the destination, so a tampered plan file is rejected. The blockhash expires about 60-90 seconds
after planning, so plan and broadcast close together. Use --rpc for your own endpoint.
Not covered: compressed NFTs, stake accounts, DeFi positions, priority fees, and Token-2022 mints with transfer hooks. Wallets without enough SOL for fees are skipped with a note.
wails dev # hot-reload dev
wails build # produce build/bin/recovery.app
go test ./internal/recovery/ # backend recovery testsRecovery users usually don't have a Go/Node toolchain, so ship a prebuilt binary.
macOS (build on macOS + Xcode):
wails build -platform darwin/universal # Intel + Apple Silicon .appUnsigned .app triggers Gatekeeper. Either sign + notarize with an Apple
Developer ID, or tell users to right-click → Open once.
Windows:
wails build -platform windows/amd64 -nsis # .exe installerSign with a code-signing cert to avoid SmartScreen. macOS/Linux can
cross-compile the Windows build; a macOS .app must be built on macOS.
Air-gapped handoff (this is a key-recovery tool):
- Build on a clean machine, then publish a checksum with the binary:
shasum -a 256 build/bin/recovery.app/Contents/MacOS/recovery
- Have users verify the checksum and disconnect from the network before opening.
Requires Go, Node, and the Wails CLI.
tss-lib and agl/ed25519 use
replace in go.mod to point at the Fystack forks.
