Skip to main content

Import Wallets and Keys

Turnkey's import functionality allows your end users to securely transfer a Wallet or a Private Key onto the Turnkey platform via CLI or an embedded iframe. We engineered this feature to ensure that the user can import their mnemonic or private key into a Turnkey secure enclave without exposing it to Turnkey or your application.

Follow along with the CLI or Embedded iframe guides.

CLI

Install the latest version of Turnkey CLI to access the new import functionality. You can find detailed instructions for installation here.

Steps

  1. Initialize import:
turnkey wallets init-import \
--user $USER_ID \
--import-bundle-output "./import_bundle.txt" \
--key-name demo
  • The --user flag (required) is the id of the user importing the private key; this is required because the underlying encryption keys used for import are scoped to each user.
  • The --import-bundle-output (required) flag is the desired output file path for the “import bundle” that will be received from Turnkey. The “import bundle” contains the ephemeral public key generated by the Turnkey signer enclave for the specified user. The private key plaintext is encrypted to this public key in Step 2.
  • Reminder: The --key-name (optional) flag specifies the name of API key with which to interact with the Turnkey API service. This should be the name of a previously created key. If you do not have one, visit the quickstart guide for help creating one.
  1. Encrypt without saving plaintext to filesystem. This can be done offline:
turnkey encrypt \
--import-bundle-input "./import_bundle.txt" \
--plaintext-input /dev/fd/3 3<<<"$MNEMONIC_1" \
--encrypted-bundle-output "./encrypted_bundle.txt"
  • The --import-bundle-input flag (required) is the desired input file path for the “import bundle”.
  • The --plaintext-input flag is the desired input file path for the private key plaintext. You can pass a filename here or feed the plaintext string directly into the standard input as shown above.
  • The --encrypted-bundle-output (required) flag is the desired output file path for the “encrypted bundle” that will be sent to Turnkey in Step 3. The “encrypted bundle” contains the ephemeral public key generated by the CLI as part of the shared secret computation with the Turnkey signer enclave. It also contains the ciphertext, which is the plaintext input encrypted by the Turnkey signer’s ephemeral public key.
  1. Import private key:
turnkey wallets import \
--user $USER_ID \
--name "demo key" \
--encrypted-bundle-input "./encrypted_bundle.txt" \
--address-format ADDRESS_FORMAT_ETHEREUM \
--curve CURVE_SECP256K1 \
--key-name demo
  • The --encrypted-bundle-input (required) flag is the desired input file path for the “encrypted bundle” that will be sent to Turnkey.
  • Options are listed here for the --curve and --address-format flags.

Private Key support

Turnkey CLI also supports importing private keys. Follow the same steps as importing a wallet via CLI but use the turnkey private-keys commands instead. In Step 2 (encrypt), pass a --key-format flag for key-specific formatting; the options for private keys are:

  • hexadecimal: Used for Ethereum. Examples: 0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e, 13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e
  • solana: Used for Solana. It’s a base58-encoding of the concatenation of the private key and public key bytes. Example: 2P3qgS5A18gGmZJmYHNxYrDYPyfm6S3dJgs8tPW6ki6i2o4yx7K8r5N8CF7JpEtQiW8mx1kSktpgyDG1xuWNzfsM
turnkey encrypt \
--import-bundle-input "./import_bundle.txt" \
--plaintext-input /dev/fd/3 3<<<"$RAW_KEY_1" \
--key-format “hexadecimal” \
--encrypted-bundle-output "./encrypted_bundle.txt"

Embedded iframe

  • We have released open-source code to create target encryption keys and encrypt wallet mnemonics for import. We've deployed a static HTML page hosted on import.turnkey.com meant to be embedded as an iframe element (see the code here). This ensures the mnemonics and keys are encrypted to keys that the user has access to, but that your organization does not (because they live in the iframe, on a separate domain).
  • We have also built a package to help you insert this iframe and interact with it in the context of import: @turnkey/iframe-stamper

In the rest of this guide we'll assume you are using these helpers.

Steps

Here's a diagram summarizing the wallet import flow step-by-step (direct link):

wallet import steps

Let's review these steps in detail:

  1. When a user on your application clicks "import", display a new import UI. We recommend setting this import UI as a new hosted page of your application that contains the iframe element that the user will enter their mnemonic into and an import button.

    While the UI is in a loading state, your application uses @turnkey/iframe-stamper to insert a new iframe element:

    const iframeStamper = new IframeStamper({
    iframeUrl: "https://import.turnkey.com",
    // Configure how the iframe element is inserted on the page
    iframeContainer: yourContainer,
    iframeElementId: "turnkey-iframe",
    });

    // Inserts the iframe in the DOM.
    await iframeStamper.init();

    // Set state to not display iframe
    let displayIframe = "none";

    return (
    // The iframe element does not need to be displayed yet
    <div style={{ display: displayIframe }} />
    );
  2. Your application prompts the user to sign a new INIT_IMPORT_WALLET activity with the ID of the user importing the wallet.

  3. Your application polls for the activity response, which contains an import bundle.

    Need help setting up async polling? Checkout our guide and helper here.

  4. Your application injects the import bundle into the iframe and displays the iframe upon success:

    // Inject import bundle into iframe
    let success = await iframeStamper.injectImportBundle(importBundle);

    if (success !== true) {
    throw new Error("unexpected error while injecting import bundle");
    }

    // If successfully injected, update the state to display the iframe
    iframeDisplay = "block";
  5. When a user clicks on the import button on your web page, your application can extract the encrypted mnemonic bundle from the iframe:

    // Extract the encrypted bundle from the iframe
    let encryptedBundle = await iframeStamper.extractWalletEncryptedBundle(importBundle);

    Your applications passes the encrypted bundle as a parameter in a new IMPORT_WALLET activity and prompts the user to sign it.

Import is complete!

In your Turnkey dashboard, the imported user Wallet will be flagged as “Imported”.

UI customization

Use iframeStamper.applySettings(settings) where settings currently consists of the styles object with the following accepted CSS style properties in the example:

{
"styles": {
"padding": "10px",
"margin": "10px",
"borderWidth": "1px",
"borderStyle": "solid",
"borderColor": "transparent",
"borderRadius": "5px",
"fontSize": "16px",
"fontWeight": "bold",
"fontFamily": "SFMono-Regular, Menlo, Monaco, Consolas, monospace",
"color": "#000000",
"backgroundColor": "rgb(128, 0, 128)",
"width": "100%",
"height": "auto",
"maxWidth": "100%",
"maxHeight": "100%",
"lineHeight": "1.25rem",
"boxShadow": "0px 0px 10px #aaa",
"textAlign": "center",
"overflowWrap": "break-word",
"wordWrap": "break-word",
"resize": "none"
}
}

Each accepted styles property is strictly validated and santizied before being applied to the textbox containing the plaintext.

Private Key support

Turnkey also supports importing Private Keys. Follow the same steps above for importing Wallets as mnemonics, but instead use the INIT_IMPORT_PRIVATE_KEY and IMPORT_PRIVATE_KEY activities and the extractKeyEncryptedBundle method from the @turnkey/iframe-stamper. You can pass an optional keyFormat to extractKeyEncryptedBundle(keyFormat) that will apply either Hexadecimal or Solana formatting to the private key that is entered in the iframe. The default key format is hexadecimal, which is used by MetaMask, MyEtherWallet, Phantom, Ledger, and Trezor for Ethereum keys. For Solana keys, you will need to pass the solana key format.

Cryptographic details

Turnkey's import functionality ensures that neither your application nor Turnkey can view the wallet mnemonic or private key.

It works by anchoring import in a target encryption key (TEK). This target encryption key is a standard P-256 key pair and is generated in the Turnkey secure enclave via a INIT_IMPORT_WALLET or INIT_IMPORT_PRIVATE_KEY activity. This TEK is encrypted to the enclave's quorum key and the TEK public key is returned in the activity response.

The following diagram summarizes the flow:

import cryptography

The iframe encrypts the wallet's mnemonic or private key to the secure enclave's TEK using the Hybrid Public Key Encryption standard, also known as HPKE or RFC 9180. The encrypted key material is then passed as a parameter inside of a signed IMPORT_WALLET or IMPORT_PRIVATE_KEY activity. During this activity, the Turnkey enclave decrypts the TEK and uses it to decrypt the encrypted key material to create a new wallet or private key.