MetaMask Snap integration
The MetaMask Snap exposes post-quantum signing to dApps inside MetaMask. It generates and stores ML-DSA-65 keypairs in the snap sandbox, signs arbitrary messages and ERC-4337 UserOperation hashes with domain separation, and supports encrypted key backup / restore.
Status: Available for MetaMask Flask (the developer build). Production MetaMask support depends on Snap registry approval, which is out of our control. The standalone Quantum Wallet is the recommended path until then.
Security properties
| Property | Value |
|---|---|
| Algorithm | ML-DSA-65 (FIPS 204) |
| Security level | NIST Level 3 (~192-bit) |
| Public key size | 1,952 bytes |
| Secret key size | 4,032 bytes |
| Signature size | ~3,309 bytes |
| Quantum resistant | Yes |
RPC methods
pq_getPublicKey
Get or generate a post-quantum keypair.
// Parameters
{ create?: boolean } // Generate new keypair if none exists (default: true)
// Response
{
publicKey: string; // Hex-encoded public key (1,952 bytes)
level: string; // Security level ("ml_dsa65")
created: boolean; // Whether a new keypair was generated
}
pq_signMessage
Sign an arbitrary message.
// Parameters
{ message: string } // Hex-encoded message to sign
// Response
{
signature: string; // Hex-encoded signature (~3,309 bytes)
nonce: number; // Signing nonce used
}
pq_signUserOp
Sign an ERC-4337 UserOperation hash with domain separation.
// Parameters
{
userOpHash: string; // 32-byte UserOperation hash (hex)
chainId: number; // Chain ID for domain separation
}
// Response
{
signature: string; // Hex-encoded signature (~3,309 bytes)
nonce: number; // Signing nonce used
}
Domain separation. The actual signed message is:
SHA3-256(userOpHash || chainId || nonce)
This prevents cross-chain replay attacks (via chainId) and session replay attacks (via nonce).
pq_exportKey
Export the keypair as an encrypted backup.
// Parameters
{ password: string } // Encryption password (min 8 characters)
// Response
{ backup: string } // Hex-encoded encrypted backup
pq_importKey
Import a keypair from an encrypted backup.
// Parameters
{
backup: string; // Encrypted backup from pq_exportKey
password: string; // Decryption password
}
// Response
{
publicKey: string;
level: string;
created: boolean;
}
pq_getInfo
Get snap status information.
// Response
{
hasKeypair: boolean;
level: string | null;
nonce: number;
publicKeyPrefix: string | null;
}
NIST KAT validation
The implementation is validated against official NIST ACVP test vectors (FIPS 204):
✓ ML-DSA-44 Keygen: 25/25 passed
✓ ML-DSA-65 Keygen: 25/25 passed
✓ ML-DSA-87 Keygen: 25/25 passed
✓ Cross-verification: 30/30 passed
Test vectors are sourced from usnistgov/ACVP-Server.
Security considerations
Key storage
- Private keys are stored encrypted in MetaMask's secure storage.
- Encryption uses MetaMask's entropy-derived key.
- Keys never leave the snap sandbox.
Signing
- All signatures require explicit user confirmation via a MetaMask dialog.
- UserOperation signing includes domain separation (
chainId+nonce). - The nonce prevents replay attacks across signing sessions.
Backup
- Backups are encrypted with PBKDF2 (100,000 iterations) + AES-256-GCM.
- The backup password must be at least 8 characters.
- The user must confirm before export.
Sandbox
- The snap runs in an SES (Secure EcmaScript) compartment.
- No network access — exfiltration of keys via the snap itself is not possible.
- The snap has no access to MetaMask's Ethereum keys.
Installation
The snap is installed automatically when a dApp that uses Quantum Wallet requests it via wallet_requestSnaps. To try it with MetaMask Flask, visit wallet.quantum.systems.