@turnkey/iframe-stamper package and the export-and-sign iframe. This architecture enables secure transaction and message signing directly in the browser without exposing private keys to your application code. Note that mishandling of exported private keys introduces inherent risks; please proceed with caution.
Overview
Client-side signing allows you to:- Export private keys from Turnkey to a secure iframe
- Sign transactions and messages directly in the browser via iframe
- Maintain multiple keys simultaneously for signing operations
- Keep private keys isolated from your application’s JavaScript context
Architecture
Security Model
- Iframe Isolation: Private keys never touch your application’s JavaScript context
- HPKE Encryption: Export bundles are encrypted end-to-end using RFC 9180
- Enclave Verification: All bundles are signed by Turnkey’s secure enclave
- Sandboxed Iframe: The iframe runs with
allow-scripts allow-same-originsandbox restrictions - Organization Validation: Bundles are validated against your organization ID
Prerequisites
- Node.js v20+
- A Turnkey organization with API credentials
- Wallet accounts to export (Solana addresses for signing support)
@turnkey/iframe-stamper >= 2.7.0
Installation
Environment Variables
Sample Implementation
Step 1: Initialize the IframeStamper
Create a component that initializes the iframe and manages its lifecycle.Step 2: Create the Backend Export Endpoint
Set up a server-side API route to handle the export request.Step 3: Export a Private Key to the Iframe
Step 4: Sign Messages
Step 5: Sign Transactions
Multi-Key Support
One of the key capabilities of client-side signing is the ability to load and manage multiple private keys simultaneously within the iframe.Loading Multiple Keys
Since the embedded key persists across bundle injections, you can export multiple keys using the same embedded key (as long as it hasn’t expired):Signing with Different Keys
Clearing Keys
Key Lifecycle and Expiration
Understanding the key lifecycle is important for building reliable applications.Embedded Key (P-256 ECDH)
- Storage: localStorage within the iframe
- TTL: 48 hours (default)
- Purpose: Decrypt incoming export bundles via HPKE
- Behavior: Persists across bundle injections - the same embedded key can decrypt multiple export bundles until it expires or is explicitly cleared
In-Memory Private Keys
- Storage: JavaScript memory only (never persisted)
- TTL: 24 hours
- Purpose: Sign messages and transactions
- Behavior: Lost on page reload, cleared on expiration
Handling Expiration
Key Formats
| Format | Description | Use Case |
|---|---|---|
KeyFormat.Solana | Base58-encoded 64-byte format (private + public) | Phantom, Solflare, Solana keys |
KeyFormat.Hexadecimal | 64 hexadecimal digits (32 bytes) | Non-Solana |
Complete Example
Here’s a complete React component demonstrating the full flow:Troubleshooting
”Iframe not ready”
Ensureinit() has completed before calling other methods. The iframe needs to load and establish the MessageChannel connection.
”Key not found for address”
- Verify the address is exactly as provided during
injectKeyExportBundle(case-sensitive) - Check if the key has expired (24-hour TTL)
- Ensure the page hasn’t been reloaded (keys are in-memory only)
“Embedded key not found”
The embedded key may have expired (48-hour TTL) or been explicitly cleared. CallinitEmbeddedKey() to create a new one.
”Organization ID does not match”
The bundle was created for a different organization. Ensure your backend uses the same organization ID as passed toinjectKeyExportBundle.
Best Practices
- Always pass the address parameter: When using multi-key support, always specify which address to sign with
- Reuse the embedded key: The embedded key persists across bundle injections, so you can export multiple keys without re-initializing
- Handle page reloads: Implement re-export logic since in-memory keys are lost on reload (the embedded key survives in localStorage)
- Monitor key expiration: Track when keys will expire - embedded key (48h), in-memory keys (24h)
- Use appropriate key formats: Use
KeyFormat.Solanafor Solana keys
Reference
IframeStamper Methods
| Method | Description |
|---|---|
init() | Insert iframe and establish connection |
clear() | Remove iframe and clean up resources |
getEmbeddedPublicKey() | Get current embedded key’s public key |
initEmbeddedKey() | Create new embedded key |
clearEmbeddedKey() | Clear the embedded key |
injectKeyExportBundle(bundle, orgId, format?, address?) | Inject private key into iframe |
signMessage(message, address?) | Sign a message |
signTransaction(transaction, address?) | Sign a transaction |
clearEmbeddedPrivateKey(address?) | Clear in-memory keys |
Supported Operations
| Operation | Solana | Ethereum |
|---|---|---|
| Message signing | Yes | Planned |
| Transaction signing | Yes | Planned |
Additional Resources
- Example: wallet-export-sign - Sample Next.js app
- @turnkey/iframe-stamper - Package source code
- export-and-sign iframe - Iframe implementation