Upgrade any Ethereum wallet to post-quantum security in one transaction using ZK proofs with a hidden public key

Post-Quantum Ethereum Wallets via Hidden Public Keys and EIP-7702 Delegation

Thanks to @gnosed for his contributions.

Any Ethereum EOA that has transacted once has its secp256k1 public key permanently on-chain, making it extractable by a quantum computer via Shor’s algorithm. We show how to retrofit any existing EOA to post-quantum security in a single EIP-7702 transaction, with no address change, no asset migration, and no consensus change. The EOA delegates execution to a GatedWallet contract that accepts only ZK proofs of ECDSA knowledge under a hidden public key. That key never appears on-chain.

Working implementation: longfellow-zk-hiddenpk.
Benchmarks: 87 ms prove / 65 ms verify / 226 KB proof on Apple M1.

This extends the 2022 post Quantum Proof Keypairs with ECDSA-ZK, which identified the right goal but left the delegation and verification flow open.

Why existing approaches fall short

Approach Address stable No asset migration No consensus change PK never in mempool
Migrate to PQ address No No No Yes (pq-secure)
Ephemeral key rotation [1] Yes Yes Yes No
New PQ smart wallet No No Yes Yes
This work Yes Yes Yes Yes

Ephemeral key rotation is the closest prior construction. Each rotation transaction broadcasts the current public key in the mempool, leaving a window where a CRQC can extract the private key. That window does not exist here.

Why not just switch to post-quantum signatures

Falcon, Dilithium (ML-DSA), and SPHINCS+ are the correct long-term answer. They are unavailable to most Ethereum wallets today.

The bottleneck is the infrastructure layer below the wallet software. Most institutional wallets run on HSMs or MPC protocols. Current-generation HSMs support ECDSA, RSA, and EdDSA only. The hardware refresh cycle runs two to five years. MPC wallets face the same problem: threshold ECDSA has audited production protocols (GG18, CGGMP21); threshold ML-DSA does not. On top of that, a PQ migration requires new address schemes, consensus changes, and a renegotiation of calldata economics for larger signatures (ML-DSA is 2.4-3.3 KB vs 65 bytes for ECDSA).

The hidden-PK construction does not replace ECDSA. It wraps it. The HSM or MPC layer continues running ECDSA internally. The ZK proof layer sits above it, in wallet software, and the on-chain verifier only sees a proof that ECDSA signing happened under a committed key. This is the fastest available path to quantum safety for MPC and HSM wallets in production today.

Construction

Setup. The user’s EOA A signs one EIP-7702 SetCode authorization pointing at a GatedWallet contract. This is the last time sk_A is used. The contract stores one value: pkHash_B = H(pkx_B || pky_B), where pk_B is the user’s new hidden keypair, generated off-chain and never broadcast.

(Note: the current implementation uses SHA-256 for pkHash_B because Longfellow-ZK does not yet support Keccak inside circuits. Adding Keccak to align with Ethereum’s address scheme is in progress.)

Steady state. For every subsequent transaction, the user’s wallet generates a ZK proof \pi on-device:

\exists\,(pk_B, r, s) \;\text{ s.t. }\; \mathrm{ECDSA.verify}(pk_B,\,(r,s),\,e) = 1 \;\wedge\; H(pk_B) = pkHash_B

where e = H(\mathtt{userOpHash} \;|\!|\; \mathtt{chainid} \;|\!|\; \mathtt{nonce}) binds the proof to this specific action. The proof goes into the ERC-4337 UserOperation signature field and is forwarded by a public bundler. GatedWallet.execute verifies and, on success, executes the action against addr_A’s assets.

contract GatedWallet {
    bytes32 public immutable pkHash;
    IZKVerifier public immutable zkVerifier;
    uint256 public nonce;

    function execute(
        address to, uint256 value, bytes calldata data, bytes calldata proof
    ) external {
        require(msg.sender == address(this), "self only"); // EIP-7702 idiom
        bytes32 e = keccak256(abi.encode(keccak256(data), block.chainid, nonce));
        require(zkVerifier.verify(proof, pkHash, e), "bad proof");
        nonce++;
        (bool ok,) = to.call{value: value}(data);
        require(ok);
    }
}

The msg.sender == address(this) guard is the standard EIP-7702 pattern: the EOA’s own signed transaction enters execute with msg.sender = addr_A = address(this). Any direct external call is rejected before reaching the ZK check.

Hybrid transition: dual-signature safety net

During an initial deployment period, a soundness bug in the circuit would be a critical risk. The GatedWallet can require both signatures simultaneously:

\text{authorize} \iff \underbrace{\mathrm{ECDSA.verify}(pk_1,\,sig_1,\,e)}_{\text{classical guard, } pk_1 \text{ exposed}} \;\wedge\; \underbrace{\mathrm{ZK.verify}(\pi,\,pkHash_2,\,e)}_{\text{hidden-key guard, PQ-safe}}

This gives independent protection against two failure modes. A quantum attacker who extracts sk_1 via Shor’s algorithm still cannot spend because pk_2 is hidden and Shor cannot be applied to it. A ZK soundness bug lets an attacker forge a proof, but they cannot produce a valid sig_1 without sk_1. Either guard defeats either failure.

After years of deployment and independent auditing, the ecrecover check is removed by a fresh EIP-7702 SetCode transaction pointing at a GatedWallet without the classical guard.

Proof system: why Ligero and not Groth16

Groth16 and KZG-PLONK rely on discrete log hardness of a structured reference string. They are not post-quantum sound. Ligero and WHIR reduce soundness to hash collision resistance and require no trusted setup. The current implementation uses Longfellow-ZK (C++, Google), a Ligero variant using CRT-based Reed-Solomon to handle secp256k1’s FFT-unfriendly field (v_2(p^2-1) = 5). An independent Rust port is at zk-cred-longfellow.

PSE’s zkID project is solving the same circuit primitives from the identity angle: ECDSA-ZK credentials for privacy-preserving identity on Ethereum. Their Circom circuits, on-chain verifiers, and mobile proving libraries are directly reusable for the hidden-PK wallet pattern. The maturity of that stack is a large part of why this construction is deployable today.

Designated-prover ECDSA optimization

Standard ECDSA verification requires:
R = u_1 \cdot G + u_2 \cdot pk, \quad u_1 = e \cdot s^{-1}, \quad u_2 = r \cdot s^{-1}

Two variable-base scalar multiplications, approximately 70% of current circuit constraints.

Because the prover generated the signature and holds sk and the nonce k, they can prove the signing equations directly:

pk = sk \cdot G, \qquad r = (k \cdot G).x, \qquad s = k^{-1}(e + r \cdot sk) \bmod n

Two fixed-base scalar multiplications instead (the base point G is constant, so precomputed tables cut constraint count by 4-8x), plus cheap modular arithmetic. Estimated 3-5x overall constraint reduction, bringing proof size from ~226 KB to ~50-80 KB and gas from ~3 M to ~800 K without any protocol changes.

One important point: dropping ECDSA entirely and proving only pk = sk \cdot G does not work. That statement has no binding to any transaction. ECDSA is the transaction integrity mechanism: (r, s) commits to e = H(\mathtt{userOpHash} \;|\!|\; \mathtt{chainid} \;|\!|\; \mathtt{nonce}), making each proof valid for exactly one action. The designated-prover optimization changes only the circuit path; it preserves the transaction binding.

Benchmarks

Measured on Apple M1, single core, release build, kLigeroRate = 7, kLigeroNreq = 132.

Metric Value
Circuit inputs 7,694
Public inputs 258
Total prove time ~87 ms
Verification time ~65 ms
Proof size 226 KB
On-chain gas ~3 M

Proof size breakdown: 32 B Merkle root, 17.6 KB sumcheck, 213.9 KB RS column openings (132 columns). On L2s with calldata compression, gas drops 10-50x. With the designated-prover optimization, ~800 K without any protocol change.

Open questions

  • Keccak in-circuit. Adding Keccak to Longfellow removes the SHA-256 / Keccak mismatch with Ethereum’s address scheme.
  • Designated-prover circuit implementation. The constraint reduction estimate needs an actual constraint count for fixed-base MSM under CRT-Reed-Solomon.
  • WHIR backend. WHIR offers better proof sizes at equivalent security. Benchmarking the ECDSA circuit with WHIR is a direct next step.
  • Proof aggregation. Multiple users spending in the same block could share amortized calldata overhead. Ligero’s linear structure supports batch verification.

References

  1. mvicari et al., Achieving Quantum Safety Through Ephemeral Key Pairs and Account Abstraction, ethresear.ch, 2026.
  2. Quantum Proof Keypairs with ECDSA-ZK, ethresear.ch, 2022.
  3. Frigo and Shelat, Anonymous Credentials from ECDSA, IACR ePrint 2024/2010.
  4. Gaborit et al., WHIR: Reed-Solomon Proximity Testing with Super-Fast Verification, IACR ePrint 2024/1586.
  5. EIP-7702: Set EOA Account Code
  6. ERC-4337: Account Abstraction Using Alt Mempool
  7. PSE zkID
  8. longfellow-zk-hiddenpk (this work)
  9. Longfellow-ZK C++ reference
  10. Longfellow-ZK Rust port (independent)
1 Like

Very cool to see a working version of ECDSA-in-ZK working on chain!

However, EIP-7702 is not a permanent set-code type transaction, it can be overwritten by a future EIP-7702 authorization which only relies on an ECDSA signature, so if an attacker found the private key, they could just upgrade the authorization to their own contract? So I can’t see how this is a retrofittable idea for long term PQ resistance. Perhaps if there’s a version or option of 7702 in future to make the upgrade irreversible then this would work great.

This is a strong construction—particularly in how it cleanly separates key exposure from authorization and enforces that boundary with ZK.

What you’ve built materially improves who is allowed to act under post-quantum assumptions, without requiring address changes or infrastructure overhaul. That’s a meaningful step forward.

I’d frame it as solving:

secure authorization under evolving cryptographic constraints


There’s a second boundary that remains open—one layer downstream.

Even with:

  • hidden public keys

  • ZK-proven ECDSA validity

  • on-chain verification

a third party trying to answer:

“Did this specific action actually occur?”

still has to rely on:

  • RPC endpoints or indexers

  • client-specific decoding of calldata, logs, or receipts

  • reconstruction of execution context

So verification of outcomes remains system-dependent, even when execution itself is cryptographically sound.


This is where I’ve been working on something complementary:

OCP (Observation Commitment Protocol)

A minimal primitive for turning any execution outcome into a portable, independently verifiable claim.

Core idea:

data → digest → on-chain commitment  
verification → recompute → compare → confirm inclusion

No API.
No indexer.
No dependency on the originating system.


In terms of layering:

  • This proposal defines who can act (authorization)

  • ZK ensures the computation is valid

  • Ethereum ensures data availability and execution correctness

What’s still missing is:

a standard way for anyone to verify what happened—without re-entering the system that produced it


These approaches don’t compete—they stack.

A ZK-gated wallet like this produces an execution outcome.
OCP binds that outcome into a portable verification artifact.

At that point, you’re not just proving the system behaved correctly—you’re making the result independently verifiable across contexts.

That’s the layer I believe is still missing.