Skip to main content

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

PropertyValue
AlgorithmML-DSA-65 (FIPS 204)
Security levelNIST Level 3 (~192-bit)
Public key size1,952 bytes
Secret key size4,032 bytes
Signature size~3,309 bytes
Quantum resistantYes

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.