Reconstruct your full private key from Dynamic wallet key shares — entirely client-side, no server needed.
Dynamic uses MPC (Multi-Party Computation) with a 2-of-3 threshold signature scheme to secure wallet private keys. No single party ever holds the full key. Instead, three key shares are distributed:
- Server share — held by Dynamic's infrastructure
- Browser share — stored in the user's browser (localStorage / iframe)
- Backup share — backed up to a cloud provider (Google Drive or iCloud)
Any 2 of the 3 shares can reconstruct the full private key. This is the foundation of the offline export flow.
Dynamic's offline recovery feature mints an independent 2-of-2 share set instead of the classic 2-of-3 above:
- Client share — uploaded to the user's Google Drive, encrypted the same way as the classic backup share (password/environment-ID-derived AES-GCM key)
- Server share — delivered to the developer's webhook, encrypted to an RSA public key the developer supplied (
HYBRID-RSA-AES-256: a random AES-256-GCM key encrypts the payload, and that AES key is wrapped with RSA-OAEP for the developer)
This demo also accepts this share set: load the Google Drive share and the webhook-delivered share into the two share slots, and paste the developer's RSA private key (PEM) into the "RSA Private Key" field so the tool can unwrap the webhook share. Format detection is automatic — each slot works with either share type.
Important: the two shares must come from the same key epoch. If the wallet was reshared (e.g. adding a cloud backup after the fact), an older exported share no longer lies on the current polynomial and reconstruction will fail with
The private key is invalideven though both shares reference the same wallet. Re-export both shares after the latest reshare.
This demo takes the two user-controlled shares and reconstructs the full private key:
- Paste or upload the downloaded browser share (plaintext
.json) - Paste or upload the Google Drive / iCloud backup share (encrypted
.json— find it in your Google Drive) - Enter your environment ID (used to decrypt the backup if no password was set)
- Paste or upload the webhook-delivered server share for 2-of-2 offline recovery (or use the second share slot)
- Paste the RSA private key (PEM) if decrypting a 2-of-2 webhook share
- Click Export — the tool decrypts the backup/webhook share, combines both shares using Dynamic's MPC SDK, and displays your private key
Everything runs in the browser. No data leaves your machine.
The chain is auto-detected from the share's derivation path (SLIP-44 coin
type: 60 → EVM, 501 → SVM, 784 → SUI), so there's nothing to select. A
manual selector appears only as a fallback when no share carries a path.
The .json file saved during the backup setup flow:
{
"keyShares": [{ "pubkey": {}, "secretShare": "03000000..." }],
"derivationPath": "{\"0\":44,\"1\":501,\"2\":0,\"3\":0,\"4\":0}"
}- Go to Google Drive
- Search for
dynamicWalletKeyShareBackup - Download the
.jsonfile — it contains an encrypted share:
{
"keyShares": ["eyJzYWx0Ijoi..."],
"metadata": { "encryptionVersion": "v2", "hasPassword": true, ... }
}This is the JSON payload Dynamic POSTs to the developer's webhook. It looks like:
{
"alg": "HYBRID-RSA-AES-256",
"kid": "dynamic_rsa_...",
"iv": "...", "ct": "...", "tag": "...", "ek": "..."
}Decrypting it requires the developer's RSA private key (PKCS8 PEM) — the counterpart to the RSA public key configured for the webhook.
The reconstructed key is displayed in each chain's conventional import format:
- EVM —
0x-prefixed 32-byte hex (importable into MetaMask/viem) - SVM — base58 of the 64-byte ed25519 keypair (matches Solana wallets / the Dynamic dashboard)
- SUI — reconstructed correctly, but shown as raw hex; the
suiprivkey1...bech32 format is not implemented
The encrypted backup share uses AES-256-GCM with a key derived from your
password (or the environment ID as fallback). Parameters default to the
ENCRYPTION_VERSIONS constants for the share's version, but the file's
metadata.encryption block is read when present (Argon2id work factors and
the PBKDF2 iteration count) to ensure correct decryption.
| Version | Key derivation | Parameters |
|---|---|---|
| v1 | PBKDF2-SHA256 | 100,000 iterations |
| v2 | PBKDF2-SHA256 | 1,000,000 iterations |
| v3 | Argon2id | 3 iterations, 64 MB memory, parallelism 2 |
The webhook-delivered server share uses a different scheme entirely: the AES key is wrapped with RSA-OAEP (SHA-256) to the developer's RSA public key. The tool unwraps it with the supplied RSA private key, then decrypts the payload with AES-256-GCM (ciphertext and auth tag are delivered separately and concatenated before decryption).
PBKDF2 (v1/v2) runs on the native Web Crypto API. Argon2id (v3) runs in a Web
Worker via the bundled hash-wasm package — no CDN fetch, so the app works
fully offline.
This project uses pnpm.
pnpm install
pnpm dev # http://localhost:5173For a production build:
pnpm build
pnpm exec vite preview # http://localhost:4173Lint:
pnpm lint- Vite + React + TypeScript — minimal client-side app
@dynamic-labs-wallet/browser— Dynamic's MPC SDK for key reconstruction (offlineExportKey)- Web Crypto API — PBKDF2 key derivation and AES-GCM decryption (native browser, no external crypto libs)
hash-wasm— Argon2id (v3) in a Web Worker, bundled for offline use
- All cryptographic operations happen in the browser. Nothing is sent to any server.
- The private key is displayed on screen and can be copied to clipboard. Treat it like a password.
- Close the tab when you're done. The key is not persisted anywhere.