-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add wallet export guide #62
Changes from all commits
37b0285
d91d3f1
546d1b8
cf16bf5
77987c0
335718f
0760932
46eee23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,9 +19,11 @@ That said, we have built out several example services and applications to help i | |
| [`demo-ethers-passkeys`](https://github.com/tkhq/demo-ethers-passkeys) | A NextJS app that demonstrates how to use `@turnkey/ethers` to build a passkey-powered application | | ||
| [`demo-viem-passkeys`](https://github.com/tkhq/demo-viem-passkeys) | A NextJS app that demonstrates how to use `@turnkey/viem` to build a passkey-powered application | | ||
| [`deployer`](https://github.com/tkhq/sdk/tree/main/examples/deployer/) | Compile and deploy a smart contract | | ||
| [`email-recovery`](https://github.com/tkhq/sdk/tree/main/examples/email-recovery/) | A NextJS app that demonstrates how to use `@turnkey/iframe-stamper` to perform email | | ||
| [`rebalancer`](https://github.com/tkhq/sdk/tree/main/examples/rebalancer/) | A demo application which showcases an example of how to use Turnkey for managing multiple types of keys & users | | ||
| [`sweeper`](https://github.com/tkhq/sdk/tree/main/examples/sweeper/) | Sweep funds from one address to a different address | | ||
| [`trading-runner`](https://github.com/tkhq/sdk/tree/main/examples/trading-runner/) | A sample application demonstrating a trading operation, using various private keys, users, and policies, powered by Uniswap | | ||
| [`wallet-export`](https://github.com/tkhq/sdk/tree/main/examples/wallet-export/) | A NextJS app that demonstrates how to use `@turnkey/iframe-stamper` to export a wallet as a mnemonic | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⭐ ty! i realize this is pretty annoying to update manually, since we ultimately have to do it in two places: SDK readme (i'll include updates in this PR), and here. nice-to-have at some point would be a small script that automatically makes the two consistent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 ! It's okay though, we've been adding quite a number of examples lately but it's a regular occurrence |
||
| [`with-ethers`](https://github.com/tkhq/sdk/tree/main/examples/with-ethers/) | Create a new Ethereum address, then sign and broadcast a transaction using the Ethers signer with Infura | | ||
| [`with-viem`](https://github.com/tkhq/sdk/tree/main/examples/with-viem/) | Sign and broadcast a transaction using the Turnkey Custom Account and Infura | | ||
| [`with-cosmjs`](https://github.com/tkhq/sdk/tree/main/examples/with-cosmjs/) | Create a new Cosmos address, then sign and broadcast a transaction on Celestia testnet using the CosmJS signer | | ||
|
@@ -38,9 +40,11 @@ That said, we have built out several example services and applications to help i | |
|
||
A minimal consumer wallet app powered by Turnkey. Behind the scenes, it uses [`@turnkey/ethers`](https://www.npmjs.com/package/@turnkey/ethers) for signing and WalletConnect (v1) for accessing dapps. | ||
|
||
<video controls width="800px"> | ||
<source src="https://github.com/tkhq/demo-consumer-wallet/assets/127255904/2c3409df-2d7c-4ec3-9aa8-e2944a0b0e0a"/> | ||
</video> | ||
<p style={{ textAlign: "center" }}> | ||
<video controls width="800px"> | ||
<source src="https://github.com/tkhq/demo-consumer-wallet/assets/127255904/2c3409df-2d7c-4ec3-9aa8-e2944a0b0e0a"/> | ||
</video> | ||
</p> | ||
|
||
See https://github.com/tkhq/demo-consumer-wallet for the code. | ||
|
||
|
@@ -49,30 +53,38 @@ See https://github.com/tkhq/demo-consumer-wallet for the code. | |
A wallet application showing how users can register and authenticate using passkeys. | ||
This demo uses the Turnkey API to create a new [Turnkey Sub-Organization](./Sub-Organizations.md) for each user, create a testnet Ethereum address and send a transaction on Sepolia (ETH testnet). | ||
|
||
<img src="/demo-passkey-wallet.png" alt="homepage screenshot" width="800px"/> | ||
<p style={{ textAlign: "center" }}> | ||
<img src="/demo-passkey-wallet.png" alt="demo passkey wallet screenshot" width="800px"/> | ||
</p> | ||
|
||
See https://wallet.tx.xyz (and https://github.com/tkhq/demo-passkey-wallet for the code). | ||
|
||
### Demo Ethers Passkeys ([code](https://github.com/tkhq/demo-ethers-passkeys)) | ||
|
||
A simple application demonstrating how to create sub-organizations, create private keys, and sign with the [`@turnkey/ethers`](https://github.com/tkhq/sdk/tree/main/packages/ethers) signer, using passkeys. | ||
|
||
<img src="/ethers-ui-screenshot.png" alt="homepage screenshot" width="800px"/> | ||
<p style={{ textAlign: "center" }}> | ||
<img src="/ethers-ui-screenshot.png" alt="ethers ui screenshot" width="800px"/> | ||
</p> | ||
|
||
See https://github.com/tkhq/demo-ethers-passkeys for the code. | ||
|
||
### Demo Viem Passkeys ([code](https://github.com/tkhq/demo-viem-passkeys)) | ||
|
||
A similar, simple application demonstrating how to create sub-organizations, create private keys, and sign with the [`@turnkey/viem`](https://github.com/tkhq/sdk/tree/main/packages/viem) signer, using passkeys. | ||
|
||
<img src="/viem-ui-screenshot.png" alt="homepage screenshot" width="800px"/> | ||
<p style={{ textAlign: "center" }}> | ||
<img src="/viem-ui-screenshot.png" alt="viem ui screenshot" width="800px"/> | ||
</p> | ||
|
||
See https://github.com/tkhq/demo-viem-passkeys for the code. | ||
|
||
### Automated ETH staking ([code](https://docs.figment.io/recipes/stake-eth-from-turnkey)) | ||
|
||
A simple example using Turnkey and Figment to easily automate ETH staking. | ||
|
||
<img src="/img/staking_example.png" alt="turnkey figment integration" width="700px"/> | ||
<p style={{ textAlign: "center" }}> | ||
<img src="/img/staking_example.png" alt="turnkey figment integration" width="700px"/> | ||
</p> | ||
|
||
See https://docs.figment.io/recipes/stake-eth-from-turnkey for the code. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
--- | ||
sidebar_position: 3 | ||
description: Learn about Wallet Export on Turnkey | ||
slug: /integration-guides/export-wallets | ||
--- | ||
# Export Wallet | ||
|
||
Turnkey's export functionality allows your end users to backup or transfer a [Wallet](../getting-started/Wallets.md) by securely viewing the wallet's [mnemonic phrase](https://learnmeabitcoin.com/technical/mnemonic). We engineered this feature to ensure that the user can export their mnemonic without exposing the mnemonic itself to Turnkey or your application. | ||
|
||
Follow along with the guide below to set up Wallet Export for your end users. | ||
|
||
## Before you start | ||
Make sure you have created a wallet for your user. Check out our [Quickstart guide](../getting-started/Quickstart.md) if you need help getting started. | ||
|
||
|
||
If you'd like to use a sub-organization as an end-user controlled wallet, follow our [Wallet integration guide](./sub-organizations-as-wallets.md). | ||
|
||
|
||
## Helper packages | ||
|
||
* We have released open-source code to create target encryption keys and decrypt exported wallet mnemonics. We've deployed a static HTML page hosted on `export.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the mnemonics 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 export: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) | ||
|
||
In the rest of this guide we'll assume you are using these helpers. | ||
|
||
## Export step-by-step | ||
|
||
Here's a diagram summarizing the wallet export flow step-by-step ([direct link](/img/wallet_export_steps.png)): | ||
|
||
<p style={{ textAlign: "center" }}> | ||
<img src="/img/wallet_export_steps.png" alt="wallet export steps" height="200" /> | ||
</p> | ||
|
||
Let's review these steps in detail: | ||
|
||
1. When a user on your application clicks "export", display a new export UI. We recommend setting this export UI as a new hosted page of your application that contains language explaining the security best practices users should follow once they've successfully exported their wallet. Remember: once the wallet has been exported, Turnkey can no longer ensure its security. | ||
|
||
While the UI is in a loading state, your application uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element: | ||
```js | ||
const iframeStamper = new IframeStamper({ | ||
iframeUrl: "https://export.turnkey.com", | ||
// Configure how the iframe element is inserted on the page | ||
iframeContainerId: "your-container", | ||
iframeElementId: "turnkey-iframe", | ||
}); | ||
|
||
// Inserts the iframe in the DOM. This creates the new encryption target key | ||
const publicKey = await iframeStamper.init(); | ||
|
||
// Set state to not display iframe | ||
let displayIframe = "none"; | ||
|
||
return ( | ||
// The iframe element can be hidden until the wallet is exported | ||
<div style={{ display: displayIframe }} /> | ||
); | ||
``` | ||
2. Your code receives the iframe public key. Your application prompts the user to sign a new `EXPORT_WALLET` activity with the wallet ID and the iframe public key in the parameters. | ||
3. Your application polls for the activity response, which contains an export bundle. Remember: this export bundle is an encrypted mnemonic which can only be decrypted within the iframe. | ||
|
||
Need help setting up async polling? Checkout our guide and helper [here](https://github.com/tkhq/sdk/tree/main/packages/http#withasyncpolling-helper). | ||
4. Your application injects the export bundle into the iframe for decryption and displays the iframe upon success: | ||
```js | ||
// Inject export bundle into iframe | ||
let success = await iframeStamper.injectWalletExportBundle(exportBundle); | ||
|
||
if (success !== true) { | ||
throw new Error("unexpected error while injecting export bundle"); | ||
} | ||
|
||
// If successfully injected, update the state to display the iframe | ||
iframeDisplay = "block"; | ||
``` | ||
|
||
Export is complete! The iframe now displays a numbered 3-column grid of words that form the mnemonic, directly to your end user. | ||
|
||
<p style={{ textAlign: "center" }}> | ||
<img src="/img/wallet_export_mnemonic.png" alt="wallet mnemonic" height="280" /> | ||
</p> | ||
|
||
The exported wallet will remain stored within Turnkey’s infrastructure. In your Turnkey dashboard, the exported user Wallet will be flagged as “Exported”. | ||
|
||
## UI customization | ||
|
||
Everything is customizable in the export iframe except the 3-column grid of mnemonic words. Here's an example of how you can configure the styling of the iframe. | ||
```js | ||
const iframeCss = ` | ||
iframe { | ||
width: 400px; | ||
height: 330px; | ||
border: none; | ||
} | ||
`; | ||
|
||
return ( | ||
<div style={{ display: iframeDisplay }} id="your-container"> | ||
<style>{iframeCss}</style> | ||
</div> | ||
); | ||
``` | ||
|
||
## Private Keys | ||
|
||
Turnkey also supports exporting raw private keys. To implement export for private keys, follow the same steps above, but instead use the `EXPORT_PRIVATE_KEY` activity and the `injectKeyExportBundle` method from the [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper). At the end of a successful private key export, the iframe displays a hexadecimal-encoded raw private key. | ||
|
||
|
||
## Cryptographic details | ||
|
||
Turnkey's export functionality ensures that neither your application nor Turnkey can view the wallet mnemonic or private key. | ||
|
||
It works by anchoring export in a **target encryption key** (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs. | ||
|
||
The following diagram summarizes the flow: | ||
|
||
<p style={{ textAlign: "center" }}> | ||
<img src="/img/wallet_export_cryptography.png" alt="export cryptography" height="320" /> | ||
</p> | ||
|
||
The public part of this key pair is passed as a parameter inside of a signed `EXPORT_WALLET` or `EXPORT_PRIVATE_KEY` activity. | ||
|
||
Our enclave encrypts the wallet's mnemonic or raw private key to the user's TEK using the **Hybrid Public Key Encryption standard**, also known as **HPKE** or [RFC 9180](https://datatracker.ietf.org/doc/rfc9180/). | ||
|
||
Once the activity succeeds, the encrypted mnemonic or private key can be decrypted by the target public key offline or in an online script. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be confusing to define wallets as a collection of private keys. They're collection of addresses generated from a common seed, not a group of what we (Turnkey) call "Private Keys".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, our docs use this language:
Wallets are collections of cryptographic key pairs typically used for sending and receiving digital assets.
And our internal rust/wallet README has:
A HD Wallet is a collection of cryptographic private/public keypairs that share a common seed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This definition is fine in the abstract / on its own, but I have a feeling that there might be confusion when we say this right below the definition of "private keys" 😬
I don't feel super strongly, we can merge as-is and refine later! Not a big deal at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I agree it's confusing. I think we do need to include it in the list of Resources, especially to avoid users looking for a "Private Keys" page. This definition is pulled from and shortened from
A HD Wallet is a collection of cryptographic private/public key pairs that share a common seed. A Wallet is used to generate Accounts.
in our Getting-Started > Wallets page.Something that might make the wallet definition less confusing is to remove the private key definition right above it. I can see how it'd be confusing to users to mentally model Wallets + Private Keys + Accounts if they read the current definition as
wallets = group(private_keys)
and then they later seewallets = group(accounts)