Smart contracts present a unique quantum vulnerability. When you write application logic that hardcodes signature verification against ECDSA public keys, you are embedding a quantum-breakable assumption into code that may be deployed and trusted for years. Unlike a centralized service, you cannot patch a deployed smart contract by pushing a software update. The upgrade path must be designed into the contract before deployment.
This post covers the design patterns that make smart contracts defensible in a post-quantum world. It focuses on code structure and architecture rather than audit methodology. For audit methodology, see the companion post on auditing smart contracts for quantum safety. Each pattern here addresses a specific vulnerability class with a concrete implementation approach.
Pattern 1: Avoid Storing Raw Public Keys On-Chain
The safest smart contract pattern for quantum resistance is to store cryptographic commitments (hashes) of public keys rather than raw public keys, and to require users to reveal the preimage only when authorizing a transaction. This limits the window in which a quantum adversary can extract a private key from a known public key, even if the underlying signature scheme is broken.
In ECDSA-based systems, a common pattern is to store user public keys directly in contract state so the contract can verify signatures against them. This is convenient but creates a persistent quantum attack surface: once a sufficiently powerful quantum computer exists, an attacker can scan all stored public keys, recover the corresponding private keys using Shor's algorithm, and drain any account whose public key is on-chain.
The alternative is to store a hash of the public key instead:
# Commitment pattern (pseudocode)
# AVOID:
# mapping(address => bytes) public publicKeys # Raw ECDSA pubkeys in storage
# PREFER:
# mapping(address => bytes32) public pubkeyCommitments # SHA-256 hash of pubkey
# User reveals pubkey only when authorizing a transaction:
# 1. Contract checks keccak256(revealedPubkey) == pubkeyCommitments[caller]
# 2. Contract verifies signature against revealedPubkey
# 3. Raw pubkey is never stored; only present on-chain during execution
The reveal-and-verify pattern ensures that raw public key bytes are only on-chain during the specific block where a transaction executes, not permanently stored in state. A quantum adversary scanning chain state cannot find raw public keys to attack; they would need to observe a transaction in flight during the few seconds it is in the mempool, which is a much harder attack requiring real-time quantum computation at the moment of the transaction.
Quick Win
Audit every mapping and struct in your Solidity contracts for fields of type bytes or bytes32 that store cryptographic keys. If the stored value is a raw public key rather than a hash, it is a candidate for the commitment pattern. A one-day audit pass over your storage layout can identify all instances without running a full formal verification.
Pattern 2: Time-Locked Signature Schemes
A time-locked signature scheme is a variation on the commitment pattern where a user commits to a transaction hash ahead of time and can only execute it after a waiting period. The delay serves two purposes: it allows fraud detection systems to flag unusual activity before the transaction executes, and it ensures that any public key revealed during the commit phase is not usable by a quantum adversary in real time (because the waiting period exceeds any plausible quantum computation time for key recovery).
# Time-lock pattern (pseudocode)
# Phase 1 - Commit:
# User submits H(tx_hash || nonce) on-chain
# commitments[user] = (H(tx_hash || nonce), block.timestamp)
# Phase 2 - Reveal (after delay):
# After TIMELOCK_BLOCKS blocks have passed:
# User submits tx_hash, nonce, pubkey, signature
# Contract verifies:
# 1. keccak256(tx_hash || nonce) == commitments[user].commitment
# 2. block.number >= commitments[user].committed_at + TIMELOCK_BLOCKS
# 3. pubkey matches stored commitment hash
# 4. signature is valid under pubkey
The timelock value is a security parameter. A 24-hour timelock (approximately 7,200 Ethereum blocks) is impractical for real-time payments but appropriate for large withdrawals from multisig treasury contracts. A 10-minute timelock (approximately 50 blocks) provides meaningful delay for mid-value transactions without severely impacting usability. Size the timelock to the transaction value: high-value operations warrant longer delays.
Pattern 3: Migrating ECDSA Verification Logic
If your contract includes hardcoded ECDSA verification (such as Ethereum's ecrecover precompile or a custom ECDSA library call in Rust), you need to migrate that logic to support ML-DSA. The cleanest approach is to abstract the verification call behind an interface and make the implementing contract upgradeable:
# Solidity interface pattern (pseudocode)
# interface ISignatureVerifier {
# function verify(
# bytes32 messageHash,
# bytes calldata signature,
# bytes calldata publicKey
# ) external view returns (bool);
# }
#
# Deploy ECDSAVerifier and MLDSAVerifier as separate contracts.
# Main contract holds a verifier address; admin can replace it post-migration.
# During transition: use a HybridVerifier that calls both and applies policy.
In Rust (for Solana or Substrate-based chains), the equivalent pattern uses a trait abstraction:
trait SignatureVerifier {
fn verify(
&self,
message_hash: &[u8],
signature: &[u8],
public_key: &[u8],
) -> Result<bool, VerifyError>;
}
struct ECDSAVerifier;
struct MLDSAVerifier;
impl SignatureVerifier for ECDSAVerifier { /* ... */ }
impl SignatureVerifier for MLDSAVerifier { /* ... */ }
// Contract state holds a boxed verifier
// Governance can replace it with MLDSAVerifier after migration
The key principle is that the signature verification algorithm should not be hardcoded into the contract's core logic. Any contract that calls ecrecover directly without an abstraction layer will require a full redeploy to migrate to ML-DSA, which means migrating user state as well. Build the abstraction before deployment, and you get a clean upgrade path later.
Pattern 4: Hash Function Choices
Not all of your cryptographic dependencies are on the signature scheme. Hash functions are also used in Merkle trees, commitment schemes, content addressing, and key derivation. The good news is that SHA-256 is considered quantum-safe for pre-image resistance with its full 256-bit output: Grover's algorithm reduces the effective security to 128 bits, which is still above the practical threshold for well-resourced attackers.
Hash functions you should not use in new contract code:
- MD5: Broken classically. Never use for any security purpose.
- SHA-1: Practically broken for collision resistance. Do not use for commitment schemes or Merkle roots.
- Keccak-256 (SHA-3 variant): Used widely in Ethereum. Considered secure against quantum attackers at the 128-bit post-quantum security level with its 256-bit output. Acceptable for continued use.
- SHA-256: The same analysis as Keccak-256. 128-bit post-quantum security. Acceptable.
- SHA-512 or SHA3-512: 256-bit post-quantum security. Use where you want the maximum hash security margin.
Quick Win
Search your contract code for any calls to keccak256 used in commitment schemes or Merkle trees. These are safe to keep as-is for now. Then search for any imports of MD5 or SHA-1 libraries and remove them immediately, regardless of quantum considerations, since both are classically broken for the security properties contracts depend on.
Pattern 5: Key Rotation Mechanisms
A contract that holds funds long-term needs a key rotation mechanism that allows the controlling address or public key to be updated without losing access to the contract state. Without this, a user whose private key is compromised has no recovery path.
The pattern requires a time-delay and a guardian structure:
# Key rotation pattern (pseudocode)
# State:
# currentSigningKey: bytes32 (hash of active public key)
# pendingSigningKey: bytes32 (hash of proposed new key)
# rotationRequestedAt: uint256
# guardian: address (separate account or multisig for emergency override)
#
# Step 1: requestRotation(newPubkeyHash)
# Sets pendingSigningKey = newPubkeyHash
# Sets rotationRequestedAt = block.timestamp
#
# Step 2: confirmRotation() (after ROTATION_DELAY seconds)
# Requires block.timestamp >= rotationRequestedAt + ROTATION_DELAY
# Sets currentSigningKey = pendingSigningKey
# Clears pendingSigningKey
The rotation delay (typically 48 to 72 hours for high-value contracts) prevents an attacker who has compromised the current signing key from immediately rotating to a key they control. The guardian address provides an emergency override path if the current signing key is lost entirely. For maximum security, the guardian should be a different key type, for example a multisig of ML-DSA keys controlled by a hardware security module.
Pattern 6: What Not to Do
Several patterns are common in deployed contracts but will create serious problems in a post-quantum world:
- Storing raw ECDSA public keys permanently in contract state. As discussed in Pattern 1, this creates a permanent attack surface. Every stored public key is a target once quantum computers are available.
- Using the sender address as an implicit authorization check. Ethereum addresses are derived from ECDSA public keys. If a quantum adversary can recover private keys from addresses, then an address-based authorization check provides no protection. Upgrade to signature-based authorization using the commitment pattern.
- Hardcoding the signature algorithm version with no upgrade path. Any contract that calls ecrecover directly with no mechanism to replace that call is permanently locked to ECDSA. Deploying such a contract today means a full redeploy and state migration when the transition arrives.
- Assuming the contract owner will always be reachable for an emergency upgrade. Contracts with an owner-only upgrade function and no timelock or guardian can be permanently bricked if the owner key is lost or compromised. Build multi-party governance into upgrade mechanisms from day one.
For a broader view of the smart contract changes needed across your application, see the post on quantum-safe smart contracts. For the audit methodology to verify that your contracts meet these patterns, see the smart contract quantum safety audit guide. For the signature standard to migrate to, see CRYSTALS-Dilithium explained.
Build on a Quantum-Safe Chain
QuanChain is the only blockchain built with post-quantum cryptography at the protocol layer. Explore the developer docs to start building.
View Developer Docs



